diff options
author | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2014-12-28 00:54:05 +0100 |
---|---|---|
committer | Andreas Stöckel <andreas@somweyr.de> | 2014-12-28 00:54:05 +0100 |
commit | 3f22fdbf6aa5d4543c122a91cf244046697a1ec9 (patch) | |
tree | a4ea51b9147d7773a5fa1d5fb1bbac3f644082ec /src/core/model | |
parent | 079b45a6745dc296a60622d5a4897ccdfcf1fa0f (diff) |
Finished StructType implementation, started to write unit tests
Diffstat (limited to 'src/core/model')
-rw-r--r-- | src/core/model/Typesystem.cpp | 251 | ||||
-rw-r--r-- | src/core/model/Typesystem.hpp | 291 |
2 files changed, 441 insertions, 101 deletions
diff --git a/src/core/model/Typesystem.cpp b/src/core/model/Typesystem.cpp index c2ab363..4a2f4eb 100644 --- a/src/core/model/Typesystem.cpp +++ b/src/core/model/Typesystem.cpp @@ -26,44 +26,44 @@ namespace model { /* Class Type */ -bool Type::build(Variant &var, Logger &logger) const +bool Type::build(Variant &data, Logger &logger) const { try { - return doBuild(var, logger); + return doBuild(data, logger); } catch (LoggableException ex) { logger.log(ex); - var = create(); + data = create(); return false; } } /* Class StringType */ -bool StringType::doBuild(Variant &var, Logger &logger) const +bool StringType::doBuild(Variant &data, Logger &logger) const { // Cannot convert non-primitive values to strings - if (!var.isPrimitive()) { + if (!data.isPrimitive()) { throw LoggableException{"Expected a string or primitive input."}; } // Perform an implicit type conversion - if (!var.isString() || var.isMagic()) { + if (!data.isString() || data.isMagic()) { // Convert the variant value to a string and set it - var = var.toString().c_str(); + data = data.toString().c_str(); // Log conversions as these may be potentially unwanted logger.note(std::string("Implicit conversion from ") + - var.getTypeName() + " to string."); + data.getTypeName() + " to string."); } return true; } /* Class IntType */ -bool IntType::doBuild(Variant &var, Logger &logger) const +bool IntType::doBuild(Variant &data, Logger &logger) const { - if (!var.isInt()) { + if (!data.isInt()) { throw LoggableException{"Expected an integer value."}; } return true; @@ -71,20 +71,20 @@ bool IntType::doBuild(Variant &var, Logger &logger) const /* Class DoubleType */ -bool DoubleType::doBuild(Variant &var, Logger &logger) const +bool DoubleType::doBuild(Variant &data, Logger &logger) const { - if (!var.isInt() && !var.isDouble()) { + if (!data.isInt() && !data.isDouble()) { throw LoggableException{"Expected a double value."}; } - var = Variant{var.toDouble()}; + data = Variant{data.toDouble()}; return true; } /* Class BoolType */ -bool BoolType::doBuild(Variant &var, Logger &logger) const +bool BoolType::doBuild(Variant &data, Logger &logger) const { - if (!var.isBool()) { + if (!data.isBool()) { throw LoggableException("Expected boolean value!"); } return true; @@ -92,11 +92,11 @@ bool BoolType::doBuild(Variant &var, Logger &logger) const /* Class EnumType */ -bool EnumType::doBuild(Variant &var, Logger &logger) const +bool EnumType::doBuild(Variant &data, Logger &logger) const { // If the variant is an int, check whether the value is in range - if (var.isInt()) { - int i = var.asInt(); + if (data.isInt()) { + int i = data.asInt(); if (i < 0 || i >= (int)values.size()) { throw LoggableException("Value is out of range."); } @@ -105,9 +105,9 @@ bool EnumType::doBuild(Variant &var, Logger &logger) const // If the given variant is a magic value it may be an enumeration constant. // Set the variant to the numeric value - if (var.isMagic()) { + if (data.isMagic()) { // Fetch the given constant name and look it up in the value map - const std::string &name = var.asMagic(); + const std::string &name = data.asMagic(); auto it = values.find(name); // Throw an execption if the given string value is not found @@ -115,16 +115,15 @@ bool EnumType::doBuild(Variant &var, Logger &logger) const throw LoggableException(std::string("Unknown enum constant: \"") + name + std::string("\"")); } - var = it->second; + data = it->second; return true; } throw LoggableException{"Expected integer or identifier"}; } -Rooted<EnumType> EnumType::createValidated(Manager &mgr, std::string name, - Handle<Typesystem> system, - const std::vector<std::string> &values, - Logger &logger) +Rooted<EnumType> EnumType::createValidated( + Manager &mgr, std::string name, Handle<Typesystem> system, + const std::vector<std::string> &values, Logger &logger) { // Map used to store the unique values of the enum std::map<std::string, Ordinal> unique_values; @@ -170,15 +169,211 @@ EnumType::Ordinal EnumType::valueOf(const std::string &name) const throw LoggableException(std::string("Unknown enum constant: ") + name); } +/* Class StructType */ + +bool StructType::resolveIndexKey(const std::string &key, size_t &idx) const +{ + try { + idx = stoul(key.substr(1)); + return true; + } + catch (std::exception ex) { + return false; + } +} + +bool StructType::resolveIdentifierKey(const std::string &key, size_t &idx) const +{ + auto it = attributeNames.find(key); + if (it == attributeNames.end()) { + return false; + } + idx = it->second; + return true; +} + +bool StructType::resolveKey(const std::string &key, size_t &idx) const +{ + bool res; + if (!key.empty() && key[0] == '#') { + res = resolveIndexKey(key, idx); + } else { + res = resolveIdentifierKey(key, idx); + } + return res && (idx < attributes.size()); +} + +bool StructType::insertDefaults(Variant &data, const std::vector<bool> &set, + Logger &logger) const +{ + bool ok = true; + Variant::arrayType &arr = data.asArray(); + for (size_t a = 0; a < arr.size(); a++) { + if (!set[a]) { + if (attributes[a]->optional) { + arr[a] = attributes[a]->defaultValue; + } else { + ok = false; + arr[a] = attributes[a]->getType()->create(); + logger.error(std::string("Expected attribute ") + + attributes[a]->getName() + + std::string(", but no value given.")); + } + } + } + return ok; +} + +bool StructType::buildFromArray(Variant &data, Logger &logger, bool trim) const +{ + bool ok = true; + Variant::arrayType &arr = data.asArray(); + std::vector<bool> set; + + // Fetch the size of the input array n and the number of attributes N + const size_t n = arr.size(); + const size_t N = attributes.size(); + arr.resize(N); + set.resize(N); + + // Make sure the array has the correct size + if (n > N && !trim) { + ok = false; + logger.error(std::string("Expected at most ") + std::to_string(N) + + std::string(" attributes, but got ") + std::to_string(n)); + } + + // Make sure the given attributes have to correct type + for (size_t a = 0; a < n; a++) { + set[a] = attributes[a]->getType()->build(arr[a], logger); + ok = ok && set[a]; + } + + return insertDefaults(data, set, logger) && ok; +} + +bool StructType::buildFromMap(Variant &data, Logger &logger, bool trim) const +{ + bool ok = true; + const Variant::mapType &map = data.asMap(); + Variant::arrayType arr; + std::vector<bool> set; + + // Fetch the size of the input map n and the number of attributes N + const size_t N = attributes.size(); + arr.resize(N); + set.resize(N); + + // Iterate over the map entries + for (auto &m : map) { + // Fetch key and value + const std::string &key = m.first; + const Variant &value = m.second; + + // Lookup the key index + size_t idx = 0; + if (resolveKey(key, idx)) { + // Warn about overriding the same key + if (set[idx]) { + logger.warning( + std::string("Attribute \"") + key + + std::string("\" set multiple times, overriding!")); + } + + // Convert the value to the type of the attribute + arr[idx] = value; + set[idx] = attributes[idx]->getType()->build(arr[idx], logger); + } else if (!trim) { + ok = false; + logger.error(std::string("Invalid attribute key \"") + key + + std::string("\"")); + } + } + + // Copy the built array to the result and insert missing default values + data = arr; + return insertDefaults(data, set, logger) && ok; +} + +bool StructType::buildFromArrayOrMap(Variant &data, Logger &logger, + bool trim) const +{ + if (data.isArray()) { + return buildFromArray(data, logger, trim); + } + if (data.isMap()) { + return buildFromMap(data, logger, trim); + } + throw LoggableException( + "Expected array or map for building a struct type!"); +} + +bool StructType::doBuild(Variant &data, Logger &logger) const +{ + return buildFromArrayOrMap(data, logger, false); +} + +Rooted<StructType> StructType::createValidated( + Manager &mgr, std::string name, Handle<Typesystem> system, + Handle<StructType> parent, NodeVector<Attribute> attributes, Logger &logger) +{ + // Check the attributes for validity and uniqueness + std::map<std::string, size_t> attributeNames; + for (size_t idx = 0; idx < attributes.size(); idx++) { + // Check for valid attribute names + const std::string &attrName = attributes[idx]->getName(); + if (!Utils::isIdentifier(name)) { + logger.error(std::string("Invalid attribute name \"") + name + + std::string("\"")); + } + + // Check for uniqueness + auto res = attributeNames.emplace(attrName, idx); + if (!res.second) { + logger.error(std::string("Attribute with name \"") + name + + std::string("\" defined multiple times")); + } + } + + // Call the private constructor + return new StructType(mgr, name, system, parent, attributes, attributeNames); +} + +Variant StructType::create() const +{ + Variant::arrayType arr; + arr.resize(attributes.size()); + for (size_t idx = 0; idx < attributes.size(); idx++) { + arr[idx] = attributes[idx]->getType()->create(); + } + return arr; +} + +bool StructType::derivedFrom(Handle<StructType> other) const +{ + if (other == this) { + return true; + } + if (parent != nullptr) { + return parent->derivedFrom(other); + } + return false; +} + +Variant StructType::cast(Variant &data, Logger &logger) const +{ + return buildFromArrayOrMap(data, logger, true); +} + /* Class ArrayType */ -bool ArrayType::doBuild(Variant &var, Logger &logger) const +bool ArrayType::doBuild(Variant &data, Logger &logger) const { - if (!var.isArray()) { + if (!data.isArray()) { throw LoggableException("Expected array!"); } bool res = true; - for (auto &v : var.asArray()) { + for (auto &v : data.asArray()) { if (!innerType->build(v, logger)) { res = false; } diff --git a/src/core/model/Typesystem.hpp b/src/core/model/Typesystem.hpp index 8562546..7eedc67 100644 --- a/src/core/model/Typesystem.hpp +++ b/src/core/model/Typesystem.hpp @@ -76,13 +76,13 @@ protected: * an LoggableException in case the given data cannot be converted to * the internal representation given by the type descriptor. * - * @param var is a variant containing the data that should be checked and + * @param data is a variant containing the data that should be checked and * -- if possible and necessary -- converted to a variant adhering to the * internal representation used by the Type class. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - virtual bool doBuild(Variant &var, Logger &logger) const = 0; + virtual bool doBuild(Variant &data, Logger &logger) const = 0; public: /** @@ -102,13 +102,13 @@ public: * Validates and completes the given variant which was read from a * user-supplied source. * - * @param var is a variant containing the data that should be checked and + * @param data is a variant containing the data that should be checked and * -- if possible and necessary -- converted to a variant adhering to the * internal representation used by the Type class. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool build(Variant &var, Logger &logger) const; + bool build(Variant &data, Logger &logger) const; /** * Returns the underlying Typesystem instance. @@ -132,12 +132,12 @@ protected: * If possible, converts the given variant to a string. Only works, if the * variant contains primitive objects (integers, strings, booleans, etc.). * - * @param var is a variant containing the data that should be checked and + * @param data is a variant containing the data that should be checked and * converted to a string. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; + bool doBuild(Variant &data, Logger &logger) const override; public: /** @@ -171,11 +171,11 @@ protected: * Expects the given variant to be an integer. Does not perform any type * conversion. * - * @param var is a variant containing the data that should be checked. + * @param data is a variant containing the data that should be checked. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; + bool doBuild(Variant &data, Logger &logger) const override; public: /** @@ -209,11 +209,11 @@ protected: * Expects the given variant to be a double or an integer. Converts integers * to doubles. * - * @param var is a variant containing the data that should be checked. + * @param data is a variant containing the data that should be checked. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; + bool doBuild(Variant &data, Logger &logger) const override; public: /** @@ -247,11 +247,11 @@ protected: * Expects the given variant to be a boolean. Performs no implicit type * conversion. * - * @param var is a variant containing the data that should be checked. + * @param data is a variant containing the data that should be checked. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; + bool doBuild(Variant &data, Logger &logger) const override; public: /** @@ -289,27 +289,33 @@ private: */ const std::map<std::string, Ordinal> values; + /** + * Private constructor of the EnumType class used to create a new EnumType + * instance from a previously created name to value map. The parameters are + * not checked for validity. + * + * @param mgr is the underlying Manager instance. + * @param name is the name of the EnumType instance. Should be a valid + * identifier. + * @param values is a vector containing the enumeration type constants. + */ + EnumType(Manager &mgr, std::string name, Handle<Typesystem> system, + std::map<std::string, Ordinal> values) + : Type(mgr, std::move(name), system, false), values(std::move(values)) + { + } + protected: /** * Converts the given variant to the corresponding enum type representation. * The variant may either be a magic string containing the name of an * enumeration type or an integer. * - * @param var is a variant containing the data that should be checked. + * @param data is a variant containing the data that should be checked. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; - - /** - * Protected constructor of the EnumType class used to create a new EnumType - * instance from a previously created name to value map. - */ - EnumType(Manager &mgr, std::string name, Handle<Typesystem> system, - std::map<std::string, Ordinal> values) - : Type(mgr, std::move(name), system, false), values(std::move(values)) - { - } + bool doBuild(Variant &data, Logger &logger) const override; public: /** @@ -318,6 +324,7 @@ public: * @param mgr is the underlying Manager instance. * @param name is the name of the EnumType instance. Should be a valid * identifier. + * @param system is a reference to the parent Typesystem instance. * @param values is a vector containing the enumeration type constants. * The constants are checked for validity (must be a valid identifier) and * uniqueness (each value must exist exactly once). @@ -357,7 +364,7 @@ private: /** * Reference to the actual type of the attribute. */ - Owned<Type> type; + const Owned<Type> type; public: /** @@ -377,12 +384,13 @@ public: * @param type holds a reference to the type descriptor holding the type * of the attribute. * @param name is the name of the Attribute. Should be a valid identifier. - * @param defaultValue is the default value of the attribute + * @param defaultValue is the default value of the attribute and must have + * been passed through the build of the specified type. * @param optional should be set to true if the if the default value should * be used. */ Attribute(Manager &mgr, std::string name, Handle<Type> type, - Variant defaultValue, bool optional) + Variant defaultValue, bool optional = true) : Node(mgr, std::move(name)), type(acquire(type)), defaultValue(defaultValue), @@ -418,60 +426,197 @@ public: * The StructType class represents a user defined structure. */ class StructType : public Type { -protected: +private: /** - * TODO: DOC + * Reference to the parent structure type (or nullptr if the struct type is + * not derived from any other struct type). */ - bool doBuild(Variant &var, Logger &logger) const override - { - // If we already have an array, we just check that. - if (var.isArray()) { - auto arr = var.asArray(); - for (size_t a = 0; a < attributes.size(); a++) { - if (!attributes[a]->getType()->build(arr[a], logger)) { - return false; - } - } - return true; - } - // Otherwise we expect a map. - if (!var.isMap()) { - throw LoggableException("Expected map!"); - } - auto &map = var.asMap(); - // We transform the map into an array with the correct values at the - // correct places. - Variant::arrayType vec; - for (auto &a : attributes) { - auto it = map.find(a->getName()); - // we use the default if nothing is set. - if (it == map.end() || !a->getType()->build(it->second, logger)) { - logger.note(std::string("Using default value for ") + - a->getName()); - vec.push_back(a->defaultValue); - } else { - vec.push_back(it->second); - } - } - var = Variant(vec); - return true; - } + const Owned<StructType> parent; -public: + /** + * Vector containing references to all attribute descriptors. + */ const NodeVector<Attribute> attributes; + /** + * Map storing the attribute names. + */ + const std::map<std::string, size_t> attributeNames; + + /** + * Resolves an attribute key string of the form "#idx" to the corresponding + * attribute index. + * + * @param key is the key to be parsed. + * @param val is the variable in which the result should be stored. + * @return true if the operation was successful, false otherwise. + */ + bool resolveIndexKey(const std::string &key, size_t &idx) const; + + /** + * Resolves an attribute key strin of the form "key" to the corresponding + * attribute index. + * + * @param key is the key to be parsed. + * @param val is the variable in which the result should be stored. + * @return true if the operation was successful, false otherwise. + */ + bool resolveIdentifierKey(const std::string &key, size_t &idx) const; + + /** + * Resolves the given attribute key to the corresponding array index. + * + * @param key is the key to be parsed. + * @param val is the variable in which the result should be stored. + * @return true if the operation was successful, false otherwise. + */ + bool resolveKey(const std::string &key, size_t &idx) const; + + /** + * Inserts default values into unset attribute slots. Loggs errors if a + * attribute that was not explicitly set has no default value associated to + * it. + * + * @param data is a variant with array type that should be updated. + * @param set indicating which array slots that have been set explicitly. + * @param logger used to which error messages and warnings are logged. + * @return true if the operation is successful, false otherwise. + */ + bool insertDefaults(Variant &data, const std::vector<bool> &set, + Logger &logger) const; + + /** + * Checks an array for validity and if possible updates its content to match + * the types of the structure type. + * + * @param data is the variant to be checked. + * @param logger used to which error messages and warnings are logged. + * @param trim if true, longer arrays are accepted and trimmed to the number + * of attributes (as needed when casting from a derived type). + * @return true if the operation is successful, false otherwise. + */ + bool buildFromArray(Variant &data, Logger &logger, bool trim) const; + + /** + * Checks a map and its entries for validity and if possible updates its + * content to match the types of the structure type. + * + * @param data is the variant to be checked. + * @param logger used to which error messages and warnings are logged. + * @param trim if true, unspecified indices are ignored. This may be needed + * when casting from a derived type. + * @return true if the operation is successful, false otherwise. + */ + bool buildFromMap(Variant &data, Logger &logger, bool trim) const; + + /** + * Checks a map or an array for validity and if possible updates its content + * to match the types of the structure type. + * + * @param data is the variant to be checked. + * @param logger used to which error messages and warnings are logged. + * @param trim if true, unspecified indices are ignored. This may be needed + * when casting from a derived type. + * @return true if the operation is successful, false otherwise. + */ + bool buildFromArrayOrMap(Variant &data, Logger &logger, bool trim) const; + + /** + * Private constructor of the StructType class, creates a new instance + * without performing any validity checks. + * + * @param mgr is the underlying Manager instance. + * @param name is the name of the EnumType instance. Should be a valid + * identifier. + * @param system is a reference to the parent Typesystem instance. + * @param parent is a reference to the StructType this type is derived from, + * may be nullptr. + * @param attributes is a vector containing the struct type attributes. + */ StructType(Manager &mgr, std::string name, Handle<Typesystem> system, - ManagedVector<Attribute> attributes) + Handle<StructType> parent, NodeVector<Attribute> attributes, + std::map<std::string, size_t> attributeNames) : Type(mgr, std::move(name), system, false), - attributes(this, std::move(attributes)) + parent(acquire(parent)), + attributes(this, std::move(attributes)), + attributeNames(std::move(attributeNames)) { } - // TODO - // static StructType createValidated( - // Manager &mgr, std::string name, Handle<Typesystem> system, - // ManagedVector<Attribute> attributes); - Variant create() const override { return Variant{Variant::arrayType{}}; } +protected: + /** + * Converts the given variant to the representation of the structure type. + * The variant may either be an array containing the values of the + * attributes in the correct order or a map containing the names of the + * attributes or their position in the for of a hash symbol "#" folowed by + * the index of the attribute. The resulting variant is an array containg + * the value of each attribute, extended by the default values + * in the correct order. + * + * @param data is a variant containing the data that should be checked. + * @param logger is the Logger instance into which errors should be written. + * @return true if the conversion was successful, false otherwise. + */ + bool doBuild(Variant &data, Logger &logger) const override; + +public: + /** + * Creates a new instance of the StructType class and checks the given + * parameters for validity. + * + * @param mgr is the underlying Manager instance. + * @param name is the name of the EnumType instance. Should be a valid + * identifier. + * @param system is a reference to the parent Typesystem instance. + * @param parent is a reference to the StructType this type is derived from, + * may be nullptr. + * @param attributes is a vector containing the struct type attributes. + * The attributes are checked for validity (their names must be a valid + * identifiers) and uniqueness (each value must exist exactly once). + * @param logger is the Logger instance into which errors should be written. + */ + static Rooted<StructType> createValidated(Manager &mgr, std::string name, + Handle<Typesystem> system, + Handle<StructType> parent, + NodeVector<Attribute> attributes, + Logger &logger); + + /** + * Creates a Variant containing a valid representation of a data instance of + * this StructType. + * + * @return a valid, empty data instance of this type. + */ + Variant create() const override; + + /** + * Function to return true if the other type either equals this type or this + * type is derived from the other type. + * + * @param other is the other struct type that should be checked. + * @return true if the other type instance points at the same instance as + * this type or this type is derived from the other type. + */ + bool derivedFrom(Handle<StructType> other) const; + + /** + * Casts the given type instance of a derived type to a type instance valid + * for this type. This operation is only valid if this type instance is a + * parent of the type instance that generated the data. + * + * @param data is the data that should be cast to this type. The data must + * have been built by a derived type of this type instance. + * @param logger is the Logger instance to which errors should be logged. + */ + Variant cast(Variant &data, Logger &logger) const; + + /** + * Returns a handle pointing at the parent type. + * + * @return a rooted handle pointing at the parent type or nullptr, if this + * struct type has no parent. + */ + Rooted<StructType> getParent() const { return parent; } }; /** @@ -492,12 +637,12 @@ protected: * Makes sure the given variant is an array and its elements match the inner * type of the Arraqy. * - * @param var is a variant containing the array data that should be checked + * @param data is a variant containing the array data that should be checked * and passed to the inner type validation function. * @param logger is the Logger instance into which errors should be written. * @return true if the conversion was successful, false otherwise. */ - bool doBuild(Variant &var, Logger &logger) const override; + bool doBuild(Variant &data, Logger &logger) const override; public: /** |