/*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * copyright (C) 2005-2007 * * Umbrello UML Modeller Authors * ***************************************************************************/ // own header #include "adaimport.h" #include // qt/kde includes #include #include // app includes #include "import_utils.h" #include "../uml.h" #include "../umldoc.h" #include "../package.h" #include "../folder.h" #include "../classifier.h" #include "../enum.h" #include "../operation.h" #include "../attribute.h" #include "../association.h" AdaImport::AdaImport() : NativeImportBase("--") { initVars(); } AdaImport::~AdaImport() { } void AdaImport::initVars() { m_inGenericFormalPart = false; m_classesDefinedInThisScope.clear(); m_renaming.clear(); } /// Split the line so that a string is returned as a single element of the list, /// when not in a string then split at white space. TQStringList AdaImport::split(const TQString& lin) { TQStringList list; TQString listElement; bool inString = false; bool seenSpace = false; TQString line = lin.stripWhiteSpace(); uint len = line.length(); for (uint i = 0; i < len; i++) { const TQChar& c = line[i]; if (inString) { listElement += c; if (c == '"') { if (i < len - 1 && line[i + 1] == '"') { i++; // escaped quotation mark continue; } list.append(listElement); listElement = TQString(); inString = false; } } else if (c == '"') { inString = true; if (!listElement.isEmpty()) list.append(listElement); listElement = TQString(c); seenSpace = false; } else if (c == '\'') { if (i < len - 2 && line[i + 2] == '\'') { // character constant if (!listElement.isEmpty()) list.append(listElement); listElement = line.mid(i, 3); i += 2; list.append(listElement); listElement = TQString(); continue; } listElement += c; seenSpace = false; } else if (c.isSpace()) { if (seenSpace) continue; seenSpace = true; if (!listElement.isEmpty()) { list.append(listElement); listElement = TQString(); } } else { listElement += c; seenSpace = false; } } if (!listElement.isEmpty()) list.append(listElement); return list; } void AdaImport::fillSource(const TQString& word) { TQString lexeme; const uint len = word.length(); for (uint i = 0; i < len; i++) { TQChar c = word[i]; if (c.isLetterOrNumber() || c == '_' || c == '.' || c == '#') { lexeme += c; } else { if (!lexeme.isEmpty()) { m_source.append(lexeme); lexeme = TQString(); } if (c == ':' && word[i + 1] == '=') { m_source.append(":="); i++; } else { m_source.append(TQString(c)); } } } if (!lexeme.isEmpty()) m_source.append(lexeme); } TQString AdaImport::expand(const TQString& name) { TQRegExp pfxRegExp("^(\\w+)\\."); pfxRegExp.setCaseSensitive(false); int pos = pfxRegExp.search(name); if (pos == -1) return name; TQString result = name; TQString pfx = pfxRegExp.cap(1); if (m_renaming.contains(pfx)) { result.remove(pfxRegExp); result.prepend(m_renaming[pfx] + '.'); } return result; } void AdaImport::parseStems(const TQStringList& stems) { if (stems.isEmpty()) return; TQString base = stems.first(); uint i = 0; while (1) { TQString filename = base + ".ads"; if (! m_parsedFiles.contains(filename)) { // Save current m_source and m_srcIndex. TQStringList source(m_source); uint srcIndex = m_srcIndex; m_source.clear(); parseFile(filename); // Restore m_source and m_srcIndex. m_source = source; m_srcIndex = srcIndex; // Also reset m_currentAccess. // CHECK: need to reset more stuff? m_currentAccess = Uml::Visibility::Public; } if (++i >= stems.count()) break; base += '-' + stems[i]; } } bool AdaImport::parseStmt() { const uint srcLength = m_source.count(); TQString keyword = m_source[m_srcIndex]; UMLDoc *umldoc = UMLApp::app()->getDocument(); //kDebug() << '"' << keyword << '"' << endl; if (keyword == "with") { if (m_inGenericFormalPart) { // mapping of generic formal subprograms or packages is not yet implemented return false; } while (++m_srcIndex < srcLength && m_source[m_srcIndex] != ";") { TQStringList components = TQStringList::split(".", m_source[m_srcIndex].lower()); const TQString& prefix = components.first(); if (prefix == "system" || prefix == "ada" || prefix == "gnat" || prefix == "interfaces" || prefix == "text_io" || prefix == "unchecked_conversion" || prefix == "unchecked_deallocation") { if (advance() != ",") break; continue; } parseStems(components); if (advance() != ",") break; } return true; } if (keyword == "generic") { m_inGenericFormalPart = true; return true; } if (keyword == "package") { const TQString& name = advance(); TQStringList parentPkgs = TQStringList::split(".", name.lower()); parentPkgs.pop_back(); // exclude the current package parseStems(parentPkgs); UMLObject *ns = NULL; if (advance() == "is") { ns = Import_Utils::createUMLObject(Uml::ot_Package, name, m_scope[m_scopeIndex], m_comment); if (m_source[m_srcIndex + 1] == "new") { m_srcIndex++; TQString pkgName = advance(); UMLObject *gp = Import_Utils::createUMLObject(Uml::ot_Package, pkgName, m_scope[m_scopeIndex]); gp->setStereotype("generic"); // Add binding from instantiator to instantiatee UMLAssociation *assoc = new UMLAssociation(Uml::at_Dependency, ns, gp); assoc->setUMLPackage(umldoc->getRootFolder(Uml::mt_Logical)); assoc->setStereotype("bind"); // Work around missing display of stereotype in AssociationWidget: assoc->setName(assoc->getStereotype(true)); umldoc->addAssociation(assoc); skipStmt(); } else { m_scope[++m_scopeIndex] = static_cast(ns); } } else if (m_source[m_srcIndex] == "renames") { m_renaming[name] = advance(); } else { kError() << "AdaImport::parseStmt: unexpected: " << m_source[m_srcIndex] << endl; skipStmt("is"); } if (m_inGenericFormalPart) { ns->setStereotype("generic"); m_inGenericFormalPart = false; } return true; } if (m_inGenericFormalPart) return false; // skip generic formal parameter (not yet implemented) if (keyword == "subtype") { TQString name = advance(); advance(); // "is" TQString base = expand(advance()); base.remove("Standard.", false); UMLObject *type = umldoc->findUMLObject(base, Uml::ot_UMLObject, m_scope[m_scopeIndex]); if (type == NULL) { type = Import_Utils::createUMLObject(Uml::ot_Datatype, base, m_scope[m_scopeIndex]); } UMLObject *subtype = Import_Utils::createUMLObject(type->getBaseType(), name, m_scope[m_scopeIndex], m_comment); UMLAssociation *assoc = new UMLAssociation(Uml::at_Dependency, subtype, type); assoc->setUMLPackage(umldoc->getRootFolder(Uml::mt_Logical)); assoc->setStereotype("subtype"); // Work around missing display of stereotype in AssociationWidget: assoc->setName(assoc->getStereotype(true)); umldoc->addAssociation(assoc); skipStmt(); return true; } if (keyword == "type") { TQString name = advance(); TQString next = advance(); if (next == "(") { kDebug() << "AdaImport::parseStmt(" << name << "): " << "discriminant handling is not yet implemented" << endl; // @todo Find out how to map discriminated record to UML. // For now, we just create a pro forma empty record. Import_Utils::createUMLObject(Uml::ot_Class, name, m_scope[m_scopeIndex], m_comment, "record"); skipStmt("end"); if ((next = advance()) == "case") m_srcIndex += 2; // skip "case" ";" skipStmt(); return true; } if (next == ";") { // forward declaration Import_Utils::createUMLObject(Uml::ot_Class, name, m_scope[m_scopeIndex], m_comment); return true; } if (next != "is") { kError() << "AdaImport::parseStmt: expecting \"is\"" << endl; return false; } next = advance(); if (next == "(") { // enum type UMLObject *ns = Import_Utils::createUMLObject(Uml::ot_Enum, name, m_scope[m_scopeIndex], m_comment); UMLEnum *enumType = static_cast(ns); while ((next = advance()) != ")") { Import_Utils::addEnumLiteral(enumType, next, m_comment); m_comment = TQString(); if (advance() != ",") break; } skipStmt(); return true; } bool isTaggedType = false; if (next == "abstract") { m_isAbstract = true; next = advance(); } if (next == "tagged") { isTaggedType = true; next = advance(); } if (next == "limited" || next == "task" || next == "protected" || next == "synchronized") { next = advance(); // we can't (yet?) represent that } if (next == "private" || next == "interface" || next == "record" || (next == "null" && m_source[m_srcIndex+1] == "record")) { Uml::Object_Type t = (next == "interface" ? Uml::ot_Interface : Uml::ot_Class); UMLObject *ns = Import_Utils::createUMLObject(t, name, m_scope[m_scopeIndex], m_comment); if (t == Uml::ot_Interface) { while ((next = advance()) == "and") { UMLClassifier *klass = static_cast(ns); TQString base = expand(advance()); UMLObject *p = Import_Utils::createUMLObject(Uml::ot_Interface, base, m_scope[m_scopeIndex]); UMLClassifier *parent = static_cast(p); Import_Utils::createGeneralization(klass, parent); } } else { ns->setAbstract(m_isAbstract); } m_isAbstract = false; if (isTaggedType) { if (! m_classesDefinedInThisScope.contains(ns)) m_classesDefinedInThisScope.append(ns); } else { ns->setStereotype("record"); } if (next == "record") m_klass = static_cast(ns); else skipStmt(); return true; } if (next == "new") { TQString base = expand(advance()); TQStringList baseInterfaces; while ((next = advance()) == "and") { baseInterfaces.append(expand(advance())); } const bool isExtension = (next == "with"); Uml::Object_Type t; if (isExtension || m_isAbstract) { t = Uml::ot_Class; } else { base.remove("Standard.", false); UMLObject *known = umldoc->findUMLObject(base, Uml::ot_UMLObject, m_scope[m_scopeIndex]); t = (known ? known->getBaseType() : Uml::ot_Datatype); } UMLObject *ns = Import_Utils::createUMLObject(t, base, NULL); UMLClassifier *parent = static_cast(ns); ns = Import_Utils::createUMLObject(t, name, m_scope[m_scopeIndex], m_comment); if (isExtension) { next = advance(); if (next == "null" || next == "record") { UMLClassifier *klass = static_cast(ns); Import_Utils::createGeneralization(klass, parent); if (next == "record") { // Set the m_klass for attributes. m_klass = klass; } if (baseInterfaces.count()) { t = Uml::ot_Interface; TQStringList::Iterator end(baseInterfaces.end()); for (TQStringList::Iterator bi(baseInterfaces.begin()); bi != end; ++bi) { ns = Import_Utils::createUMLObject(t, *bi, m_scope[m_scopeIndex]); parent = static_cast(ns); Import_Utils::createGeneralization(klass, parent); } } } } skipStmt(); return true; } // Datatypes: TO BE DONE return false; } if (keyword == "private") { m_currentAccess = Uml::Visibility::Private; return true; } if (keyword == "end") { if (m_klass) { if (advance() != "record") { kError() << "end: expecting \"record\" at " << m_source[m_srcIndex] << endl; } m_klass = NULL; } else if (m_scopeIndex) { if (advance() != ";") { TQString scopeName = m_scope[m_scopeIndex]->getFullyQualifiedName(); if (scopeName.lower() != m_source[m_srcIndex].lower()) kError() << "end: expecting " << scopeName << ", found " << m_source[m_srcIndex] << endl; } m_scopeIndex--; m_currentAccess = Uml::Visibility::Public; // @todo make a stack for this } else { kError() << "importAda: too many \"end\"" << endl; } skipStmt(); return true; } // subprogram if (keyword == "not") keyword = advance(); if (keyword == "overriding") keyword = advance(); if (keyword == "function" || keyword == "procedure") { const TQString& name = advance(); TQString returnType; if (advance() != "(") { // Unlike an Ada package, a UML package does not support // subprograms. // In order to map those, we would need to create a UML // class with stereotype <> for the Ada package. kDebug() << "ignoring parameterless " << keyword << " " << name << endl; skipStmt(); return true; } UMLClassifier *klass = NULL; UMLOperation *op = NULL; const uint MAX_PARNAMES = 16; while (m_srcIndex < srcLength && m_source[m_srcIndex] != ")") { TQString parName[MAX_PARNAMES]; uint parNameCount = 0; do { if (parNameCount >= MAX_PARNAMES) { kError() << "MAX_PARNAMES is exceeded at " << name << endl; break; } parName[parNameCount++] = advance(); } while (advance() == ","); if (m_source[m_srcIndex] != ":") { kError() << "importAda: expecting ':'" << endl; skipStmt(); break; } const TQString &direction = advance(); TQString typeName; Uml::Parameter_Direction dir = Uml::pd_In; if (direction == "access") { // Oops, we have to improvise here because there // is no such thing as "access" in UML. // So we use the next best thing, "inout". // Better ideas, anyone? dir = Uml::pd_InOut; typeName = advance(); } else if (direction == "in") { if (m_source[m_srcIndex + 1] == "out") { dir = Uml::pd_InOut; m_srcIndex++; } typeName = advance(); } else if (direction == "out") { dir = Uml::pd_Out; typeName = advance(); } else { typeName = direction; // In Ada, the default direction is "in" } typeName.remove("Standard.", false); typeName = expand(typeName); if (op == NULL) { // In Ada, the first parameter indicates the class. UMLObject *type = Import_Utils::createUMLObject(Uml::ot_Class, typeName, m_scope[m_scopeIndex]); Uml::Object_Type t = type->getBaseType(); if ((t != Uml::ot_Interface && (t != Uml::ot_Class || type->getStereotype() == "record")) || !m_classesDefinedInThisScope.contains(type)) { // Not an instance bound method - we cannot represent it. skipStmt(")"); break; } klass = static_cast(type); op = Import_Utils::makeOperation(klass, name); // The controlling parameter is suppressed. parNameCount--; if (parNameCount) { for (uint i = 0; i < parNameCount; i++) parName[i] = parName[i + 1]; } } for (uint i = 0; i < parNameCount; i++) { UMLAttribute *att = Import_Utils::addMethodParameter(op, typeName, parName[i]); att->setParmKind(dir); } if (advance() != ";") break; } if (keyword == "function") { if (advance() != "return") { if (klass) kError() << "importAda: expecting \"return\" at function " << name << endl; return false; } returnType = expand(advance()); returnType.remove("Standard.", false); } bool isAbstract = false; if (advance() == "is" && advance() == "abstract") isAbstract = true; if (klass != NULL && op != NULL) Import_Utils::insertMethod(klass, op, m_currentAccess, returnType, false, isAbstract, false, false, m_comment); skipStmt(); return true; } if (keyword == "task" || keyword == "protected") { // Can task and protected objects/types be mapped to UML? bool isType = false; TQString name = advance(); if (name == "type") { isType = true; name = advance(); } TQString next = advance(); if (next == "(") { skipStmt(")"); // skip discriminant next = advance(); } if (next == "is") skipStmt("end"); skipStmt(); return true; } if (keyword == "for") { // rep spec TQString typeName = advance(); TQString next = advance(); if (next == "'") { advance(); // skip qualifier next = advance(); } if (next == "use") { if (advance() == "record") skipStmt("end"); } else { kError() << "importAda: expecting \"use\" at rep spec of " << typeName << endl; } skipStmt(); return true; } // At this point we're only interested in attribute declarations. if (m_klass == NULL || keyword == "null") { skipStmt(); return true; } const TQString& name = keyword; if (advance() != ":") { kError() << "adaImport: expecting \":\" at " << name << " " << m_source[m_srcIndex] << endl; skipStmt(); return true; } TQString nextToken = advance(); if (nextToken == "aliased") nextToken = advance(); TQString typeName = expand(nextToken); TQString initialValue; if (advance() == ":=") { initialValue = advance(); TQString token; while ((token = advance()) != ";") { initialValue.append(' ' + token); } } UMLObject *o = Import_Utils::insertAttribute(m_klass, m_currentAccess, name, typeName, m_comment); UMLAttribute *attr = static_cast(o); attr->setInitialValue(initialValue); skipStmt(); return true; }