//========================================================================== // MSGANALYZER.CC - part of // // OMNeT++/OMNEST // Discrete System Simulation in C++ // //========================================================================== /*--------------------------------------------------------------* Copyright (C) 2002-2017 Andras Varga Copyright (C) 2006-2017 OpenSim Ltd. This file is distributed WITHOUT ANY WARRANTY. See the file `license' for details on this and other legal matters. *--------------------------------------------------------------*/ #include #include #include #include #include "common/stringtokenizer.h" #include "common/fileutil.h" #include "common/stringutil.h" #include "common/stlutil.h" #include "common/opp_ctype.h" #include "msgcompiler.h" #include "msganalyzer.h" #include "exception.h" //TODO field's props should be produced by merging the type's props into it? (to be accessible from inspectors) using namespace omnetpp::common; namespace omnetpp { namespace nedxml { inline std::string str(const char *s) { return s ? s : ""; } static char charToNameFilter(char ch) { return isalnum(ch) ? ch : '_'; } inline bool isQualified(const std::string& qname) { return qname.find("::") != qname.npos; } static std::string makeIdentifier(const std::string& qname) { std::string tmp = qname; std::transform(tmp.begin(), tmp.end(), tmp.begin(), charToNameFilter); return tmp; } template //TODO move these templates into common/? P check_and_cast(T *p) { assert(p); P ret = dynamic_cast

(p); assert(ret); return ret; } template P check_and_cast_nullable(T *p) { if (p == nullptr) return nullptr; P ret = dynamic_cast

(p); assert(ret); return ret; } MsgAnalyzer::StringSet MsgAnalyzer::RESERVED_WORDS = { // C++ keywords "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", "compl", "concept", "const", "constexpr", "const_cast", "continue", "co_await", "co_return", "co_yield", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq", // MSG keywords that are not also C++ keywords "cplusplus", "message", "packet", "noncobject", "extends", "abstract" }; MsgAnalyzer::MsgAnalyzer(const MsgCompilerOptions& opts, MsgTypeTable *typeTable, ErrorStore *errors) : errors(errors), typeTable(typeTable), opts(opts) { } MsgAnalyzer::~MsgAnalyzer() { } MsgAnalyzer::ClassInfo MsgAnalyzer::extractClassInfo(ASTNode *node, const std::string& namespaceName, bool isImported) { ClassInfo classInfo; classInfo.astNode = node; classInfo.props = extractProperties(node); std::string actually = getProperty(classInfo.props, PROP_ACTUALLY, ""); classInfo.keyword = node->getTagName(); classInfo.name = actually != "" ? actually : node->getAttribute(ATT_NAME); classInfo.namespaceName = namespaceName; classInfo.qname = prefixWithNamespace(classInfo.name, namespaceName); classInfo.extendsName = node->getAttribute(ATT_EXTENDS_NAME); classInfo.isClass = (classInfo.keyword != "struct"); classInfo.isEnum = false; classInfo.isImported = isImported; return classInfo; } MsgAnalyzer::Properties MsgAnalyzer::extractProperties(ASTNode *node) { const char *usage = node->getTagCode()==MSG_FIELD ? "field" : node->getTagCode()==MSG_MSG_FILE ? "file" : "class"; Properties props; for (PropertyElement *propElem = check_and_cast_nullable(node->getFirstChildWithTag(MSG_PROPERTY)); propElem; propElem = propElem->getNextPropertySibling()) { Property property = extractProperty(propElem); if (props.contains(property.getName(), property.getIndex())) errors->addError(node, "duplicate property '%s'", property.getIndexedName().c_str()); validateProperty(property, usage); props.add(property); } return props; } MsgAnalyzer::Property MsgAnalyzer::extractProperty(PropertyElement *propElem) { std::string propName = propElem->getName(); std::string propIndex = propElem->getIndex(); Property property(propName, propIndex, propElem); for (PropertyKeyElement *keyElem = propElem->getFirstPropertyKeyChild(); keyElem; keyElem = keyElem->getNextPropertyKeySibling()) { std::string keyName = keyElem->getName(); for (LiteralElement *lit = keyElem->getFirstLiteralChild(); lit; lit = lit->getNextLiteralSibling()) property.addValue(keyName, lit->getValue()); } return property; } void MsgAnalyzer::extractFields(ClassInfo& classInfo) { ASTNode *node = classInfo.astNode; for (FieldElement *fieldElem = check_and_cast_nullable(node->getFirstChildWithTag(MSG_FIELD)); fieldElem; fieldElem = fieldElem->getNextFieldSibling()) { FieldInfo field; field.astNode = fieldElem; field.name = fieldElem->getName(); field.dataType = fieldElem->getDataType(); field.type = fieldElem->getDataType(); field.value = fieldElem->getDefaultValue(); field.isAbstract = fieldElem->getIsAbstract(); field.isConst = fieldElem->getIsConst(); field.isPointer = fieldElem->getIsPointer(); field.isArray = fieldElem->getIsVector(); field.arraySize = fieldElem->getVectorSize(); field.props = extractProperties(fieldElem); FieldInfo *duplicate = findField(classInfo, field.name); if (duplicate != nullptr) errors->addError(field.astNode, "Field '%s' already exists, see %s", field.name.c_str(), duplicate->astNode->getSourceLocation()); else if (field.type.empty()) classInfo.baseclassFieldlist.push_back(field); else classInfo.fieldList.push_back(field); } } void MsgAnalyzer::ensureAnalyzed(ClassInfo& classInfo) { if (!classInfo.classInfoComplete) { if (classInfo.classBeingAnalyzed) { errors->addError(classInfo.astNode, "Recursive definition near '%s'", classInfo.name.c_str()); return; } classInfo.classBeingAnalyzed = true; extractFields(classInfo); analyzeClassOrStruct(classInfo, classInfo.namespaceName); classInfo.classBeingAnalyzed = false; classInfo.classInfoComplete = true; } } void MsgAnalyzer::ensureFieldsAnalyzed(ClassInfo& classInfo) { if (!classInfo.fieldsComplete) { if (classInfo.fieldsBeingAnalyzed) { errors->addError(classInfo.astNode, "Recursive nesting near '%s'", classInfo.name.c_str()); return; } classInfo.fieldsBeingAnalyzed = true; analyzeFields(classInfo, classInfo.namespaceName); classInfo.fieldsBeingAnalyzed = false; classInfo.fieldsComplete = true; } } bool MsgAnalyzer::hasSuperclass(ClassInfo& classInfo, const std::string& superclassQName) { ClassInfo *currentClass = &classInfo; while (currentClass->extendsQName != "") { currentClass = &typeTable->getClassInfo(currentClass->extendsQName); if (currentClass->qname == superclassQName) return true; ensureAnalyzed(*currentClass); } return false; } void MsgAnalyzer::analyzeClassOrStruct(ClassInfo& classInfo, const std::string& namespaceName) { // determine base class if (classInfo.extendsName == "") { if (classInfo.keyword == "message") classInfo.extendsQName = "omnetpp::cMessage"; else if (classInfo.keyword == "packet") classInfo.extendsQName = "omnetpp::cPacket"; else classInfo.extendsQName = ""; } else { classInfo.extendsQName = lookupExistingClassName(classInfo.extendsName, namespaceName); if (classInfo.extendsQName.empty()) errors->addError(classInfo.astNode, "'%s': unknown base class '%s'", classInfo.name.c_str(), classInfo.extendsName.c_str()); } // resolve base class/struct ClassInfo *baseClassInfo = nullptr; if (classInfo.extendsQName != "") { baseClassInfo = &typeTable->getClassInfo(classInfo.extendsQName); ensureAnalyzed(*baseClassInfo); if (!baseClassInfo->subclassable) errors->addError(classInfo.astNode, "'%s': type '%s' cannot be used as base class", classInfo.name.c_str(), classInfo.extendsName.c_str()); } // determine class type classInfo.iscObject = classInfo.iscNamedObject = classInfo.iscOwnedObject = false; if (classInfo.isClass) { if (classInfo.qname == "omnetpp::cObject") classInfo.iscObject = true; else if (classInfo.qname == "omnetpp::cNamedObject") classInfo.iscObject = classInfo.iscNamedObject = true; else if (classInfo.qname == "omnetpp::cOwnedObject") classInfo.iscObject = classInfo.iscNamedObject = classInfo.iscOwnedObject = true; else if (baseClassInfo != nullptr) { if (!baseClassInfo->isClass) errors->addError(classInfo.astNode, "%s: base type is not a class", classInfo.name.c_str()); classInfo.iscObject = baseClassInfo->iscObject; classInfo.iscNamedObject = baseClassInfo->iscNamedObject; classInfo.iscOwnedObject = baseClassInfo->iscOwnedObject; } else { // assume non-cObject } } else { // struct if (baseClassInfo != nullptr && baseClassInfo->isClass) errors->addError(classInfo.astNode, "%s: base type is not a struct", classInfo.name.c_str()); } // message must subclass from cMessage, packet ditto std::string requiredSuperClass; if (classInfo.keyword == "message") requiredSuperClass = "omnetpp::cMessage"; else if (classInfo.keyword == "packet") requiredSuperClass = "omnetpp::cPacket"; if (!requiredSuperClass.empty() && !hasSuperclass(classInfo, requiredSuperClass)) errors->addError(classInfo.astNode, "%s: base class is not a %s (must be derived from %s)", classInfo.name.c_str(), classInfo.keyword.c_str(), requiredSuperClass.c_str()); // isOpaque, byValue, data types, etc. bool isPrimitive = getPropertyAsBool(classInfo.props, PROP_PRIMITIVE, false); // @primitive is a shortcut for @opaque @byValue @editable @subclassable(false) @supportsPtr(false) classInfo.isOpaque = getPropertyAsBool(classInfo.props, PROP_OPAQUE, isPrimitive); classInfo.byValue = getPropertyAsBool(classInfo.props, PROP_BYVALUE, isPrimitive); classInfo.subclassable = getPropertyAsBool(classInfo.props, PROP_SUBCLASSABLE, !isPrimitive); classInfo.supportsPtr = getPropertyAsBool(classInfo.props, PROP_SUPPORTSPTR, !isPrimitive); classInfo.isEditable = getPropertyAsBool(classInfo.props, PROP_EDITABLE, isPrimitive); classInfo.defaultValue = getProperty(classInfo.props, PROP_DEFAULTVALUE, ""); classInfo.dataTypeBase = getProperty(classInfo.props, PROP_CPPTYPE, ""); classInfo.argTypeBase = getProperty(classInfo.props, PROP_ARGTYPE, ""); classInfo.returnTypeBase = getProperty(classInfo.props, PROP_RETURNTYPE, ""); classInfo.toString = getProperty(classInfo.props, PROP_TOSTRING, ""); classInfo.fromString = getProperty(classInfo.props, PROP_FROMSTRING, ""); classInfo.getterConversion = getProperty(classInfo.props, PROP_GETTERCONVERSION, "$"); classInfo.clone = getProperty(classInfo.props, PROP_CLONE, ""); classInfo.str = getProperty(classInfo.props, PROP_STR, ""); // generation gap bool existingClass = getPropertyAsBool(classInfo.props, PROP_EXISTINGCLASS, false); classInfo.generateClass = opts.generateClasses && !existingClass; classInfo.generateDescriptor = opts.generateDescriptors && !classInfo.isOpaque && getPropertyAsBool(classInfo.props, PROP_DESCRIPTOR, true); // opaque also means no descriptor classInfo.generateSettersInDescriptor = opts.generateSettersInDescriptors && (getProperty(classInfo.props, PROP_DESCRIPTOR) != "readonly"); if (!existingClass && isQualified(classInfo.name)) errors->addError(classInfo.astNode, "class name may only contain '::' when generating descriptor for an existing class"); classInfo.customize = getPropertyAsBool(classInfo.props, PROP_CUSTOMIZE, false); if (classInfo.customize) { classInfo.className = classInfo.name + "_Base"; classInfo.realClass = classInfo.name; classInfo.descriptorClass = makeIdentifier(classInfo.realClass) + "Descriptor"; } else { classInfo.className = classInfo.name; classInfo.realClass = classInfo.name; classInfo.descriptorClass = makeIdentifier(classInfo.className) + "Descriptor"; } classInfo.baseClass = classInfo.extendsQName; // omitGetVerb / fieldNameSuffix classInfo.omitGetVerb = getPropertyAsBool(classInfo.props, PROP_OMITGETVERB, false); classInfo.fieldNameSuffix = getProperty(classInfo.props, PROP_FIELDNAMESUFFIX, ""); if (classInfo.omitGetVerb && classInfo.fieldNameSuffix.empty()) { errors->addWarning(classInfo.astNode, "@omitGetVerb(true) and (implicit) @fieldNameSuffix(\"\") collide: " "adding '_var' suffix to data members to prevent name conflict between them and getter methods"); classInfo.fieldNameSuffix = "_var"; } // beforeChange classInfo.beforeChange = getProperty(classInfo.props, PROP_BEFORECHANGE, ""); if (classInfo.beforeChange.empty() && baseClassInfo != nullptr) classInfo.beforeChange = baseClassInfo->beforeChange; // additional base classes (interfaces) std::string s = getProperty(classInfo.props, PROP_IMPLEMENTS); if (!s.empty()) classInfo.implements = StringTokenizer(s.c_str(), ",").asVector(); } void MsgAnalyzer::analyzeFields(ClassInfo& classInfo, const std::string& namespaceName) { for (auto& field : classInfo.fieldList) analyzeField(classInfo, &field, namespaceName); for (auto& field : classInfo.baseclassFieldlist) analyzeInheritedField(classInfo, &field); } void MsgAnalyzer::analyzeField(ClassInfo& classInfo, FieldInfo *field, const std::string& namespaceName) { Assert(!field->type.empty()); if (!classInfo.isClass && field->isAbstract) errors->addError(field->astNode, "A struct cannot have abstract fields"); if (field->isAbstract && !field->value.empty()) errors->addError(field->astNode, "An abstract field cannot be assigned a default value"); if (!classInfo.isClass && field->isArray && field->arraySize.empty()) errors->addError(field->astNode, "A struct cannot have dynamic array fields"); if (field->isAbstract && !classInfo.customize) errors->addError(field->astNode, "abstract fields need '@customize(true)' property in '%s'", classInfo.name.c_str()); if (field->isArray && field->isConst && !field->isPointer) errors->addError(field->astNode, "Arrays of const values/objects are not supported"); field->typeQName = lookupExistingClassName(field->type, namespaceName, &classInfo); if (field->typeQName.empty()) { errors->addError(field->astNode, "unknown type '%s' for field '%s' in '%s'", field->type.c_str(), field->name.c_str(), classInfo.name.c_str()); field->typeQName = "omnetpp::cObject"; } field->symbolicConstant = std::string("FIELD_")+field->name; ClassInfo& fieldClassInfo = typeTable->getClassInfo(field->typeQName); ensureAnalyzed(fieldClassInfo); field->isClass = fieldClassInfo.isClass; field->iscObject = fieldClassInfo.iscObject; field->iscNamedObject = fieldClassInfo.iscNamedObject; field->iscOwnedObject = fieldClassInfo.iscOwnedObject; if (classInfo.generateClass) if (field->iscOwnedObject && !classInfo.iscObject) errors->addError(field->astNode, "cannot use cOwnedObject field '%s %s' in struct or non-cObject class '%s'", field->type.c_str(), field->name.c_str(), classInfo.name.c_str()); if (field->isPointer && field->value.empty()) field->value = "nullptr"; if (field->value.empty() && !fieldClassInfo.defaultValue.empty()) field->value = fieldClassInfo.defaultValue; field->isDynamicArray = field->isArray && field->arraySize.empty(); field->isFixedArray = field->isArray && !field->arraySize.empty(); field->nopack = getPropertyAsBool(field->props, PROP_NOPACK, false); bool isEditableDefault = fieldClassInfo.isEditable && !field->isConst && !field->isPointer && classInfo.generateSettersInDescriptor; field->isEditable = getPropertyAsBool(field->props, PROP_EDITABLE, isEditableDefault); field->isOpaque = getPropertyAsBool(field->props, PROP_OPAQUE, fieldClassInfo.isOpaque); field->overrideGetter = getPropertyAsBool(field->props, PROP_OVERRIDEGETTER, false) || getPropertyAsBool(field->props, "override", false); field->overrideSetter = getPropertyAsBool(field->props, PROP_OVERRIDESETTER, false) || getPropertyAsBool(field->props, "override", false); // resolve enum field->enumName = getProperty(field->props, PROP_ENUM); if (!field->enumName.empty()) { StringVector found = typeTable->lookupExistingEnumName(field->enumName, namespaceName); if (found.size() == 1) { field->enumQName = found[0]; } else if (found.empty()) { errors->addError(field->astNode, "undeclared enum '%s' in field '%s' in '%s'", field->enumName.c_str(), field->name.c_str(), classInfo.name.c_str()); field->enumQName = ""; } else { errors->addWarning(field->astNode, "ambiguous enum '%s' in field '%s' in '%s'; possibilities: %s", field->enumName.c_str(), field->name.c_str(), classInfo.name.c_str(), opp_join(found, ", ").c_str()); field->enumQName = found[0]; } // need to overwrite it in props, because Qtenv will look up the enum by qname Property newProp(PROP_ENUM, "", field->astNode); newProp.addValue("", field->enumQName); field->props.add(newProp); } if (fieldClassInfo.isEnum) { Property newProp(PROP_ENUM, "", field->astNode); newProp.addValue("", field->typeQName); field->props.add(newProp); } bool supportsPtr = getPropertyAsBool(field->props, PROP_SUPPORTSPTR, fieldClassInfo.supportsPtr); if (field->isPointer && !supportsPtr) errors->addError(field->astNode, "'%s *' pointers are not allowed", field->type.c_str()); field->byValue = getPropertyAsBool(field->props, PROP_BYVALUE, fieldClassInfo.byValue); field->isOwnedPointer = field->isPointer && getPropertyAsBool(field->props, PROP_OWNED, false); if (hasProperty(field->props, PROP_OWNED) && !field->isPointer) errors->addWarning(field->astNode, "ignoring @owned property for non-pointer field '%s'", field->name.c_str()); // fromstring/tostring field->fromString = fieldClassInfo.fromString; field->toString = fieldClassInfo.toString; if (!field->enumName.empty()) { field->toString = str("enum2string($, \"") + field->enumQName + "\")"; field->fromString = str("(") + field->enumQName + ")string2enum($, \"" + field->enumQName + "\")"; } field->fromString = getProperty(field->props, PROP_FROMSTRING, field->fromString); field->toString = getProperty(field->props, PROP_TOSTRING, field->toString); // default method names if (classInfo.isClass) { std::string capfieldname = field->name; capfieldname[0] = toupper(capfieldname[0]); field->setter = str("set") + capfieldname; field->dropper = str("drop") + capfieldname; field->inserter = str("insert") + capfieldname; field->eraser = str("erase") + capfieldname; field->sizeSetter = str("set") + capfieldname + "ArraySize"; if (classInfo.omitGetVerb) { field->getter = field->name; field->sizeGetter = field->name + "ArraySize"; } else { std::string fname = field->name; bool is = fname.length() >= 3 && fname[0] == 'i' && fname[1] == 's' && opp_isupper(fname[2]); bool has = fname.length() >= 4 && fname[0] == 'h' && fname[1] == 'a' && fname[1] == 's' && opp_isupper(fname[3]); bool omitGet = (field->dataType == "bool") && (is || has); field->getter = omitGet ? fname : (str("get") + capfieldname); field->sizeGetter = str("get") + capfieldname + "ArraySize"; } field->getterForUpdate = str("get") + capfieldname + "ForUpdate"; field->allowReplace = getPropertyAsBool(field->props, PROP_ALLOWREPLACE, false); // allow customization of names if (getProperty(field->props, PROP_SETTER) != "") field->setter = getProperty(field->props, PROP_SETTER); if (getProperty(field->props, PROP_GETTER) != "") field->getter = getProperty(field->props, PROP_GETTER); if (getProperty(field->props, PROP_GETTERFORUPDATE) != "") field->getterForUpdate = getProperty(field->props, PROP_GETTERFORUPDATE); if (getProperty(field->props, PROP_SIZESETTER) != "") field->sizeSetter = getProperty(field->props, PROP_SIZESETTER); if (getProperty(field->props, PROP_SIZEGETTER) != "") field->sizeGetter = getProperty(field->props, PROP_SIZEGETTER); if (getProperty(field->props, PROP_INSERTER) != "") field->inserter = getProperty(field->props, PROP_INSERTER); if (getProperty(field->props, PROP_ERASER) != "") field->eraser = getProperty(field->props, PROP_ERASER); field->getterConversion = getProperty(field->props, PROP_GETTERCONVERSION, fieldClassInfo.getterConversion); field->clone = getProperty(field->props, PROP_CLONE, fieldClassInfo.clone); if (field->clone.empty()) { if (field->iscObject) field->clone = "->dup()"; else field->clone = str("new ") + field->dataType + "(*$)"; } } //TODO warn for non-applicable properties like @allowReplace for non-ownedpointer fields // variable name if (!classInfo.isClass) { field->var = field->name; } else { field->var = field->name + classInfo.fieldNameSuffix; if (field->var == field->getter) // isFoo <--> isFoo() conflict field->var += "_"; field->argName = field->name; } field->sizeVar = field->arraySize.empty() ? (field->name + "_arraysize") : field->arraySize; std::string sizetypeprop = getProperty(field->props, PROP_SIZETYPE); field->sizeType = !sizetypeprop.empty() ? sizetypeprop : "size_t"; // data type, argument type, conversion to/from string... std::string datatypeBase = getProperty(field->props, PROP_CPPTYPE, fieldClassInfo.dataTypeBase); std::string argtypeBase = getProperty(field->props, PROP_ARGTYPE, fieldClassInfo.argTypeBase); std::string returntypeBase = getProperty(field->props, PROP_RETURNTYPE, fieldClassInfo.returnTypeBase); if (datatypeBase.empty()) datatypeBase = field->type; if (argtypeBase.empty()) argtypeBase = datatypeBase; if (returntypeBase.empty()) returntypeBase = datatypeBase; // // Intended const usage for various isPointer/isConst/byValue combinations: // Foo*: setter: Foo* getter: const Foo* mgetter: Foo* // const Foo*: setter: const Foo* getter: const Foo* mgetter: - // Foo: setter: Foo& getter: const Foo& mgetter: Foo& // const Foo: setter: - getter: const Foo& mgetter: - // int (byvalue): setter: int getter: int mgetter: - // const int: setter: - getter: int mgetter: - // int*: same as Foo* // const int*: const Foo* // bool byRef = !field->isPointer && !field->byValue; field->dataType = decorateType(datatypeBase, field->isConst, field->isPointer, false); field->argType = decorateType(argtypeBase, field->isConst || byRef, field->isPointer, byRef); //TODO check "|| byRef" part -- removing it makes tests fail if (!field->isPointer && field->byValue) { field->returnType = decorateType(returntypeBase, false, false, false); field->argType = decorateType(argtypeBase, false, false, false); } else { field->returnType = decorateType(returntypeBase, true, field->isPointer, byRef); field->mutableReturnType = decorateType(returntypeBase, false, field->isPointer, byRef); } field->hasGetterForUpdate = !field->isConst && (field->isPointer ? true : !field->byValue); if (field->isEditable && field->fromString.empty() && classInfo.generateDescriptor && classInfo.generateSettersInDescriptor) errors->addError(field->astNode, "Field '%s' is editable, but @fromString is unspecified", field->name.c_str()); } MsgAnalyzer::FieldInfo *MsgAnalyzer::findField(ClassInfo& classInfo, const std::string& name) { for (FieldInfo& field : classInfo.fieldList) if (field.name == name) return &field; return nullptr; } MsgAnalyzer::FieldInfo *MsgAnalyzer::findSuperclassField(ClassInfo& classInfo, const std::string& fieldName) { ClassInfo *currentClass = &classInfo; while (currentClass->extendsQName != "") { currentClass = &typeTable->getClassInfo(currentClass->extendsQName); ensureAnalyzed(*currentClass); ensureFieldsAnalyzed(*currentClass); for (FieldInfo& f : currentClass->fieldList) if (f.name == fieldName && !f.type.empty()) return &f; } return nullptr; } void MsgAnalyzer::analyzeInheritedField(ClassInfo& classInfo, FieldInfo *field) { Assert(field->type.empty()); // i.e. it is an inherited field if (field->isAbstract) errors->addError(field->astNode, "An abstract field needs a type"); if (field->isArray) errors->addError(field->astNode, "Cannot set array field of super class"); if (field->value.empty()) errors->addError(field->astNode, "Missing field value assignment"); FieldInfo *fieldDefinition = findSuperclassField(classInfo, field->name); if (!fieldDefinition) errors->addError(field->astNode, "Unknown field '%s' (not found in any super class)", field->name.c_str()); else { // copy stuff field->setter = fieldDefinition->setter; //TODO more } } MsgAnalyzer::EnumInfo MsgAnalyzer::extractEnumDecl(EnumDeclElement *enumElem, const std::string& namespaceName) { EnumInfo enumInfo; enumInfo.astNode = enumElem; enumInfo.enumName = enumElem->getName(); enumInfo.enumQName = prefixWithNamespace(enumInfo.enumName, namespaceName); enumInfo.isDeclaration = true; return enumInfo; } MsgAnalyzer::EnumInfo MsgAnalyzer::extractEnumInfo(EnumElement *enumElem, const std::string& namespaceName) { EnumInfo enumInfo; enumInfo.astNode = enumElem; enumInfo.enumName = enumElem->getName(); enumInfo.enumQName = prefixWithNamespace(enumInfo.enumName, namespaceName); enumInfo.isDeclaration = false; // prepare enum items for (EnumFieldElement *fieldElem = check_and_cast_nullable(enumElem->getFirstChildWithTag(MSG_ENUM_FIELD)); fieldElem; fieldElem = fieldElem->getNextEnumFieldSibling()) { EnumItem item; item.astNode = fieldElem; item.name = fieldElem->getName(); item.value = fieldElem->getValue(); enumInfo.fieldList.push_back(item); } return enumInfo; } MsgAnalyzer::ClassInfo MsgAnalyzer::extractClassInfoFromEnum(EnumElement *enumElem, const std::string& namespaceName, bool isImported) { ClassInfo classInfo = extractClassInfo(enumElem, namespaceName, isImported); /* @primitive; @descriptor(false); @fromString((namespaceName::typeName)string2enum($, "namespaceName::typeName")); @toString(enum2string($, "namespaceName::typeName")); @defaultValue(static_cast(-1)); */ classInfo.dataTypeBase = classInfo.qname; classInfo.fromString = str("(") + classInfo.qname + ")string2enum($, \"" + classInfo.qname + "\")"; classInfo.toString = str("enum2string($, \"") + classInfo.qname + "\")"; classInfo.defaultValue = str("static_cast<") + classInfo.qname + ">(-1)"; // determine base class if (classInfo.extendsName != "") errors->addError(classInfo.astNode, "'%s': type '%s' cannot be used as base class of enum", classInfo.name.c_str(), classInfo.extendsName.c_str()); classInfo.extendsQName = ""; // determine class kind classInfo.isClass = false; classInfo.iscObject = false; classInfo.iscOwnedObject = false; classInfo.iscNamedObject = false; classInfo.isEnum = true; // isOpaque, byValue, data types, etc. classInfo.isOpaque = true; classInfo.byValue = true; classInfo.subclassable = false; classInfo.supportsPtr = false; classInfo.dataTypeBase = classInfo.qname; classInfo.argTypeBase = classInfo.qname; classInfo.returnTypeBase = classInfo.qname; classInfo.getterConversion = "$"; // // produce all sorts of derived names // classInfo.generateClass = false; classInfo.generateDescriptor = false; classInfo.generateSettersInDescriptor = false; classInfo.customize = false; classInfo.className = classInfo.name; classInfo.realClass = classInfo.name; classInfo.descriptorClass = makeIdentifier(classInfo.className) + "Descriptor"; classInfo.baseClass = classInfo.extendsQName; classInfo.omitGetVerb = false; classInfo.fieldNameSuffix = ""; classInfo.classInfoComplete = true; return classInfo; } std::string MsgAnalyzer::prefixWithNamespace(const std::string& name, const std::string& namespaceName) { return !namespaceName.empty() ? namespaceName + "::" + name : name; // prefer name from local namespace } std::string MsgAnalyzer::decorateType(const std::string& typeName, bool isConst, bool isPointer, bool isRef) { bool alreadyConst = opp_stringbeginswith(typeName.c_str(), "const "); // use with "const char *" return ((isConst && !alreadyConst) ? "const " : "") + typeName + (isPointer ? " *" : "") + (isRef ? "&" : ""); } bool MsgAnalyzer::getPropertyAsBool(const Properties& props, const char *name, bool defval) { const Property *p = props.get(name); if (p == nullptr) return defval; const auto& values = p->getValue(""); if (values.empty()) return true; else if (values.size() == 1) { if (values[0] == "false") return false; if (values[0] == "true") return true; //FIXME @descriptor(readonly) vs getPropertyAsBool("descriptor") // errors->addError(it->second.getASTNode(), "invalid value in boolean property '%s':'%s'", name, values[0].c_str()); return defval; } errors->addError(p->getASTNode(), "property '%s' is not simple", name); //TODO revise msg return defval; } std::string MsgAnalyzer::getProperty(const Properties& props, const char *name, const std::string& defval) { const Property *p = props.get(name); if (p == nullptr) return defval; const auto& values = p->getValue(""); if (values.empty()) return ""; else if (values.size() == 1) return values[0]; errors->addError(p->getASTNode(), "property '%s' is not simple", name); //TODO revise msg return ""; } std::string MsgAnalyzer::lookupExistingClassName(const std::string& name, const std::string& contextNamespace, ClassInfo *contextClass) { if (name.empty()) return ""; if (name.find("::") == 0) { // if $name start with "::", take it as an explicitly qualified name std::string qname = name.substr(2); if (typeTable->isClassDefined(qname)) return qname; } else { // relative name // look into the enclosing type and its super types if (contextClass != nullptr) { std::string qname = prefixWithNamespace(name, contextClass->qname); if (typeTable->isClassDefined(qname)) return qname; ClassInfo *currentClass = contextClass; while (currentClass->extendsQName != "") { currentClass = &typeTable->getClassInfo(currentClass->extendsQName); // go to super class std::string qname = prefixWithNamespace(name, currentClass->qname); if (typeTable->isClassDefined(qname)) return qname; ensureAnalyzed(*currentClass); } } // search from current namespace up to the top level std::string lookupNamespace = contextNamespace; while (true) { std::string qname = prefixWithNamespace(name, lookupNamespace); if (typeTable->isClassDefined(qname)) return qname; if (lookupNamespace.empty()) break; // drop last segment auto pos = lookupNamespace.rfind("::"); if (pos == std::string::npos) pos = 0; lookupNamespace = lookupNamespace.substr(0, pos); } // additionally, try in in the omnetpp namespace as well std::string qname = "omnetpp::" + name; if (typeTable->isClassDefined(qname)) return qname; } return ""; } void MsgAnalyzer::validateProperty(const Property& property, const char *usage) { const Property *propertyDecl = typeTable->getGlobalProperties().get(PROP_PROPERTY, property.getName()); if (propertyDecl == nullptr) { errors->addWarning(property.getASTNode(), "Unknown property @%s (missing declaration via @property?)", property.getName().c_str()); return; } const auto& allowedUsages = propertyDecl->getValue("usage"); if (!allowedUsages.empty() && !contains(allowedUsages, str(usage))) errors->addWarning(property.getASTNode(), "@%s cannot be used as %s property (see see corresponding @property declaration)", property.getName().c_str(), usage); } void MsgAnalyzer::validateFileProperty(const Property& property) { validateProperty(property, "file"); } } // namespace nedxml } // namespace omnetpp