diff options
-rw-r--r-- | src/core/common/Logger.hpp | 10 | ||||
-rw-r--r-- | src/core/common/VariantReader.cpp | 17 | ||||
-rw-r--r-- | src/core/common/VariantReader.hpp | 20 | ||||
-rw-r--r-- | src/core/model/Document.cpp | 2 | ||||
-rw-r--r-- | src/core/model/Document.hpp | 5 | ||||
-rw-r--r-- | src/core/model/Domain.cpp | 4 | ||||
-rw-r--r-- | src/core/model/Domain.hpp | 4 | ||||
-rw-r--r-- | src/core/model/Node.cpp | 30 | ||||
-rw-r--r-- | src/core/model/Node.hpp | 76 | ||||
-rw-r--r-- | src/core/model/Typesystem.cpp | 47 | ||||
-rw-r--r-- | src/core/model/Typesystem.hpp | 81 | ||||
-rw-r--r-- | src/core/parser/ParserStack.cpp | 15 | ||||
-rw-r--r-- | src/core/parser/ParserStack.hpp | 41 | ||||
-rw-r--r-- | src/core/parser/Scope.cpp | 66 | ||||
-rw-r--r-- | src/core/parser/Scope.hpp | 66 | ||||
-rw-r--r-- | src/plugins/xml/XmlParser.cpp | 75 | ||||
-rw-r--r-- | test/core/common/VariantReaderTest.cpp | 36 | ||||
-rw-r--r-- | test/core/model/NodeTest.cpp | 2 | ||||
-rw-r--r-- | test/core/model/TypesystemTest.cpp | 45 | ||||
-rw-r--r-- | test/core/parser/StandaloneParserContext.hpp | 2 | ||||
-rw-r--r-- | test/plugins/xml/XmlParserTest.cpp | 9 |
21 files changed, 517 insertions, 136 deletions
diff --git a/src/core/common/Logger.hpp b/src/core/common/Logger.hpp index b365a39..767d8ab 100644 --- a/src/core/common/Logger.hpp +++ b/src/core/common/Logger.hpp @@ -243,6 +243,16 @@ public: } /** + * Logs the given loggable exception at the given location. + * + * @param ex is the exception that should be logged. + */ + void log(const LoggableException &ex, const SourceLocation &loc) + { + log(Severity::ERROR, ex.msg, loc.valid() ? loc : ex.getLocation()); + } + + /** * Logs the given message. The file name is set to the topmost file name on * the file name stack. * diff --git a/src/core/common/VariantReader.cpp b/src/core/common/VariantReader.cpp index faad40c..600fd9b 100644 --- a/src/core/common/VariantReader.cpp +++ b/src/core/common/VariantReader.cpp @@ -583,6 +583,7 @@ std::pair<bool, Variant> VariantReader::parseGenericToken( return std::make_pair(true, n.doubleValue()); } } + reader.resetPeek(); } // Try to parse an object @@ -618,8 +619,22 @@ std::pair<bool, Variant> VariantReader::parseGenericToken( v.setMagic(res.second.c_str()); return std::make_pair(res.first, v); } else { - return std::make_pair(res.first, Variant{res.second.c_str()}); + return std::make_pair(res.first, Variant::fromString(res.second)); + } +} + +std::pair<bool, Variant> VariantReader::parseGenericString( + const std::string &str, Logger &logger) +{ + CharReader reader{str}; + LoggerFork loggerFork = logger.fork(); + std::pair<bool, Variant> res = + parseGenericToken(reader, loggerFork, std::unordered_set<char>{}, true); + if (reader.atEnd()) { + loggerFork.commit(); + return res; } + return std::make_pair(true, Variant::fromString(str)); } } diff --git a/src/core/common/VariantReader.hpp b/src/core/common/VariantReader.hpp index abf529c..8aaffd8 100644 --- a/src/core/common/VariantReader.hpp +++ b/src/core/common/VariantReader.hpp @@ -229,6 +229,26 @@ public: CharReader &reader, Logger &logger, const std::unordered_set<char> &delims, bool extractUnescapedStrings = false); + + /** + * Tries to parse the most specific item from the given string. The + * resulting variant represents the value that has been read. If the end of + * the string was not reached while parsing an element, the result is + * returned as string. + * + * @param str is the string from which the value should be read. + * @param logger is the logger instance to which errors or warnings will be + * written. + * @return a pair indicating whether the operation was successful and the + * extracted variant value. Note that the variant value most times contains + * some meaningful data that can be worked with even if the operation was + * not successful (e.g. if a syntax error is encountered while reading an + * array, the successfully read elements will still be in the returned + * variant.) Information on why the operation has failed is passed to the + * logger. + */ + static std::pair<bool, Variant> parseGenericString( + const std::string &str, Logger &logger); }; } diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index bf8bfde..cf6ded3 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -333,7 +333,7 @@ bool AnnotationEntity::doValidate(Logger &logger) const /* Class Document */ -void Document::continueResolve(ResolutionState &state) +void Document::doResolve(ResolutionState &state) { continueResolveComposita(annotations, annotations.getIndex(), state); if (root != nullptr) { diff --git a/src/core/model/Document.hpp b/src/core/model/Document.hpp index 4b95079..b4ee429 100644 --- a/src/core/model/Document.hpp +++ b/src/core/model/Document.hpp @@ -501,6 +501,7 @@ class AnnotationEntity : public Node, public DocumentEntity { private: Owned<Anchor> start; Owned<Anchor> end; + protected: bool doValidate(Logger &logger) const override; @@ -556,7 +557,7 @@ private: NodeVector<AnnotationEntity> annotations; NodeVector<Domain> domains; - void continueResolve(ResolutionState &state) override; + void doResolve(ResolutionState &state) override; protected: bool doValidate(Logger &logger) const override; @@ -614,7 +615,7 @@ public: { domains.insert(domains.end(), d.begin(), d.end()); } - + /** * Returns true if and only if the given StructureNode is part of this * document, meaning that there is a path of parent references in the diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 17f3e02..9a0ed0d 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -70,7 +70,7 @@ FieldDescriptor::FieldDescriptor(Manager &mgr, Handle<Descriptor> parent, /* Class Descriptor */ -void Descriptor::continueResolve(ResolutionState &state) +void Descriptor::doResolve(ResolutionState &state) { if (attributesDescriptor != nullptr) { const NodeVector<Attribute> &attributes = @@ -254,7 +254,7 @@ AnnotationClass::AnnotationClass( /* Class Domain */ -void Domain::continueResolve(ResolutionState &state) +void Domain::doResolve(ResolutionState &state) { if (!continueResolveComposita(structuredClasses, structuredClasses.getIndex(), state) | diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 5cc7874..d1ba44f 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -390,7 +390,7 @@ private: std::vector<Rooted<Node>> &path) const; protected: - void continueResolve(ResolutionState &state) override; + void doResolve(ResolutionState &state) override; /** * Adds a FieldDescriptor and checks for name uniqueness. @@ -695,7 +695,7 @@ private: NodeVector<Typesystem> typesystems; protected: - void continueResolve(ResolutionState &state) override; + void doResolve(ResolutionState &state) override; void addStructuredClass(Handle<StructuredClass> s); void addAnnotationClass(Handle<AnnotationClass> a); diff --git a/src/core/model/Node.cpp b/src/core/model/Node.cpp index a935715..bd023e1 100644 --- a/src/core/model/Node.cpp +++ b/src/core/model/Node.cpp @@ -23,6 +23,7 @@ #include <core/common/Logger.hpp> #include <core/common/Rtti.hpp> #include <core/common/TypedRttiBuilder.hpp> +#include <core/common/Utils.hpp> #include "Node.hpp" @@ -272,14 +273,14 @@ bool Node::resolve(ResolutionState &state) } } else { size_t resCount = state.resultCount(); - continueResolve(state); + doResolve(state); return state.resultCount() > resCount; } } return false; } -void Node::continueResolve(ResolutionState &state) +void Node::doResolve(ResolutionState &state) { // Do nothing in the default implementation } @@ -353,8 +354,33 @@ std::vector<ResolutionResult> Node::resolve(const std::string &name, return resolve(std::vector<std::string>{name}, type); } +bool Node::checkDuplicate(Handle<Node> elem, + std::unordered_set<std::string> &names, + Logger &logger) const +{ + const std::string &name = elem->getName(); + if (!names.emplace(name).second) { + logger.error(std::string("Element with name \"") + name + + std::string("\" defined multiple times in parent ") + + type().name + std::string(" \"") + + Utils::join(path(), ".") + std::string("\"")); + return false; + } + return true; +} + bool Node::doValidate(Logger &logger) const { return true; } +bool Node::validateName(Logger &logger) const +{ + if (!Utils::isIdentifier(name)) { + logger.error(type().name + std::string(" name \"") + name + + std::string("\" is not a valid identifier")); + return false; + } + return true; +} + void Node::invalidate() { // Only perform the invalidation if necessary diff --git a/src/core/model/Node.hpp b/src/core/model/Node.hpp index 0523c69..0168a3e 100644 --- a/src/core/model/Node.hpp +++ b/src/core/model/Node.hpp @@ -31,6 +31,7 @@ #include <cstdint> #include <map> #include <set> +#include <unordered_set> #include <string> #include <vector> @@ -196,6 +197,19 @@ private: */ bool continueResolveIndex(const Index &index, ResolutionState &state); + /** + * Checks whether the name of the given node is already stored in the given + * set, if yes, logs a corresponding error message. + * + * @param node is the node of which the name should be checked. + * @param names is a set in which all encountered names are stored. + * @param logger is the logger instance to which error messages are written. + * @return true if the given node has a unique name, false otherwise. + */ + bool checkDuplicate(Handle<Node> node, + std::unordered_set<std::string> &names, + Logger &logger) const; + protected: /** * Function which should be overwritten by derived classes in order to @@ -207,7 +221,7 @@ protected: * * @param state is used internally to manage the resolution process. */ - virtual void continueResolve(ResolutionState &state); + virtual void doResolve(ResolutionState &state); /** * Tries to advance the resolution process with the compositum pointed at @@ -329,6 +343,56 @@ protected: */ virtual bool doValidate(Logger &logger) const; + /** + * Makes sure the name of this node is a valid identifier and loggs a + * corresponding error message. + * + * @param logger is the logger to which the error message is logged. + * @return true if the name is valid, false otherwise. + */ + bool validateName(Logger &logger) const; + + /** + * Helper function that can be used to forward the validation process to + * child nodes. + * + * @tparam T is the type of the list that should be handled. + * @param list is a list of arbitrary kind. The "validate" function is + * called for all elementsd of the list. + * @param logger is the logger to which any errors should be reported. + */ + template <class T> + bool continueValidation(const T &list, Logger &logger) const + { + bool res = true; + for (auto elem : list) { + res = elem->validate(logger) & res; + } + return res; + } + + /** + * Helper function that can be used to forward the validation process to + * child nodes while at the same time checking that the children have no + * duplicated names. + * + * @tparam T is the type of the list that should be handled. + * @param list is a list of arbitrary kind. The "validate" function is + * called for all elementsd of the list. + * @param logger is the logger to which any errors should be reported. + */ + template <class T> + bool continueValidationCheckDuplicates(const T &list, Logger &logger) const + { + bool res = true; + std::unordered_set<std::string> names; + for (auto elem : list) { + res = elem->validate(logger) & checkDuplicate(elem, names, logger) & + res; + } + return res; + } + public: /** * Initializes the node with empty name and parent. @@ -460,8 +524,9 @@ class NodeVector : public ManagedGenericList<T, std::vector<Handle<T>>, ListAccessor<Handle<T>>, Listener> { public: - using ManagedGenericList<T, std::vector<Handle<T>>, ListAccessor<Handle<T>>, - Listener>::ManagedGenericList; + using Base = ManagedGenericList<T, std::vector<Handle<T>>, ListAccessor<Handle<T>>, + Listener>; + using Base::Base; /** * Returns the reference to the internal index. @@ -490,9 +555,10 @@ class NodeMap : public ManagedGenericMap<K, T, std::map<K, Handle<T>>, MapAccessor<std::pair<K, Handle<T>>>, Listener> { public: - using ManagedGenericMap<K, T, std::map<K, Handle<T>>, + using Base = ManagedGenericMap<K, T, std::map<K, Handle<T>>, MapAccessor<std::pair<K, Handle<T>>>, - Listener>::ManagedGenericMap; + Listener>; + using Base::Base; /** * Returns the reference to the internal index. diff --git a/src/core/model/Typesystem.cpp b/src/core/model/Typesystem.cpp index 726de3e..a3d354c 100644 --- a/src/core/model/Typesystem.cpp +++ b/src/core/model/Typesystem.cpp @@ -151,12 +151,7 @@ EnumType::Ordinal EnumType::valueOf(const std::string &name) const bool Attribute::doValidate(Logger &logger) const { - if (!Utils::isIdentifier(getName())) { - logger.error("Attribute name \"" + getName() + - "\" is not a valid identifier."); - return false; - } - return true; + return validateName(logger); } /* Class StructType */ @@ -332,22 +327,8 @@ bool StructType::doBuild(Variant &data, Logger &logger) const bool StructType::doValidate(Logger &logger) const { - // Check whether all attributes are valid and unique - std::unordered_set<std::string> names; - bool res = true; - for (Handle<Attribute> a : attributes) { - res = a->validate(logger) && res; - const std::string &name = a->getName(); - if (!names.emplace(name).second) { - logger.error( - std::string("Attribute with name \"") + name + - std::string("\" defined multiple times in structure \"") + - Utils::join(path(), ".") + std::string("\"")); - res = false; - } - } - - return res; + return validateName(logger) & + continueValidationCheckDuplicates(attributes, logger); } Rooted<StructType> StructType::createValidated( @@ -473,6 +454,28 @@ bool ArrayType::doBuild(Variant &data, Logger &logger) const return res; } +/* Class Typesystem */ + +void Typesystem::doResolve(ResolutionState &state) +{ + continueResolveComposita(constants, constants.getIndex(), state); + continueResolveComposita(types, constants.getIndex(), state); +} + +bool Typesystem::doValidate(Logger &logger) const +{ + return validateName(logger) & + continueValidationCheckDuplicates(constants, logger) & + continueValidationCheckDuplicates(types, logger); +} + +Rooted<StructType> Typesystem::createStructType(const std::string &name) +{ + Rooted<StructType> structType{new StructType(getManager(), name, this)}; + addType(structType); + return structType; +} + /* Class SystemTypesystem */ SystemTypesystem::SystemTypesystem(Manager &mgr) diff --git a/src/core/model/Typesystem.hpp b/src/core/model/Typesystem.hpp index 7bc8950..64922f0 100644 --- a/src/core/model/Typesystem.hpp +++ b/src/core/model/Typesystem.hpp @@ -368,10 +368,7 @@ public: */ class Attribute : public Node { private: - /** - * Reference to the actual type of the attribute. - */ - const Owned<Type> type; + protected: /** @@ -384,14 +381,25 @@ protected: public: /** + * Reference to the actual type of the attribute. + */ + Owned<Type> type; + + /** + * Initial default value passed to the constructor of the Attribute class + * that has not been passed through the "build" method of the type. + */ + Variant rawDefaultValue; + + /** * Default value of the attribute. */ - const Variant defaultValue; + Variant defaultValue; /** * Flag indicating whether this attribute is actually optional or not. */ - const bool optional; + bool optional; /** * Constructor of the Attribute class. @@ -430,6 +438,50 @@ public: } /** + * Sets a new default value. This makes the Attribute optional. The given + * default value is passed through the "build" function of the current + * type. + * + * @param defaultValue is the new default value. + * @param logger is the logger instance to which errors that occur during + * reinterpretion of the default value. + */ + void setDefaultValue(const Variant &defaultValue, Logger &logger); + + /** + * Returns the default value of the attribute. + * + * @return the default value of the attribute. If no default value has been + * given a null variant is returned (the opposite does not hold). + */ + Variant getDefaultValue() const; + + /** + * Removes any default value from the attribute, making this attribute + * non-optional. + */ + void removeDefaultValue(); + + /** + * Returns true if the attribute is optional (a default value has been + * supplied by the user). + * + * @return true if the attribute is optional, false otherwise. + */ + bool isOptional() const {return optional; } + + /** + * Sets the type of the attribute to the specified value. This will + * reinterpret the default value that has been passed to the attribute (if + * available). + * + * @param type is the new type that should be used for the attribute. + * @param logger is the logger instance to which errors that occur during + * reinterpretion of the default value. + */ + void setType(Handle<Type> type, Logger &logger); + + /** * Returns a reference to the type descriptor holding the type of the * attribute. * @@ -859,6 +911,12 @@ private: */ NodeVector<Constant> constants; +protected: + + void doResolve(ResolutionState &state) override; + + bool doValidate(Logger &logger) const override; + public: /** * Constructor of the Typesystem class. @@ -867,11 +925,20 @@ public: * @param name is the name of the typesystem. */ Typesystem(Manager &mgr, std::string name) - : Node(mgr, name), types(this), constants(this) + : Node(mgr, std::move(name)), types(this), constants(this) { } /** + * Creates a new StructType instance with the given name. Adds the new + * StructType as member to the typesystem. + * + * @param name is the name of the structure that should be created. + * @return the new StructType instance. + */ + Rooted<StructType> createStructType(const std::string &name); + + /** * Adds the given type to the to the type list. * * @param type is the Type that should be stored in this Typesystem diff --git a/src/core/parser/ParserStack.cpp b/src/core/parser/ParserStack.cpp index caf2116..9cf782f 100644 --- a/src/core/parser/ParserStack.cpp +++ b/src/core/parser/ParserStack.cpp @@ -63,11 +63,11 @@ void Handler::child(std::shared_ptr<Handler> handler) HandlerInstance HandlerDescriptor::create(const ParserContext &ctx, std::string name, State parentState, - bool isChild, - Variant::mapType &args) const + bool isChild, Variant::mapType &args, + const SourceLocation &location) const { Handler *h; - HandlerData data{ctx, name, targetState, parentState, isChild}; + HandlerData data{ctx, name, targetState, parentState, isChild, location}; if (ctor) { h = ctor(data); } else { @@ -115,7 +115,8 @@ std::set<std::string> ParserStack::expectedCommands(State state) return res; } -void ParserStack::start(std::string name, Variant::mapType &args) +void ParserStack::start(std::string name, Variant::mapType &args, + const SourceLocation &location) { // Fetch the current handler and the current state const HandlerInstance *h = stack.empty() ? nullptr : &stack.top(); @@ -143,11 +144,11 @@ void ParserStack::start(std::string name, Variant::mapType &args) } // Instantiate the handler and call its start function - stack.emplace( - descr->create(ctx, name, curState, isChild, args)); + stack.emplace(descr->create(ctx, name, curState, isChild, args, location)); } -void ParserStack::start(std::string name, const Variant::mapType &args) +void ParserStack::start(std::string name, const Variant::mapType &args, + const SourceLocation &location) { Variant::mapType argsCopy(args); start(name, argsCopy); diff --git a/src/core/parser/ParserStack.hpp b/src/core/parser/ParserStack.hpp index 43d6529..aa196e7 100644 --- a/src/core/parser/ParserStack.hpp +++ b/src/core/parser/ParserStack.hpp @@ -86,6 +86,11 @@ struct HandlerData { const bool isChild; /** + * Current source code location. + */ + const SourceLocation location; + + /** * Constructor of the HandlerData class. * * @param ctx is the parser context the handler should be executed in. @@ -94,14 +99,16 @@ struct HandlerData { * @param parentState is the state of the parent command. * @param isChild specifies whether this handler was called not for the * command that was specified in the state machine but a child command. + * @param location is the location at which the handler is created. */ HandlerData(const ParserContext &ctx, std::string name, State state, - State parentState, bool isChild) + State parentState, bool isChild, const SourceLocation location) : ctx(ctx), name(std::move(name)), state(state), parentState(parentState), - isChild(isChild){}; + isChild(isChild), + location(location){}; }; /** @@ -123,28 +130,29 @@ public: * @param data is a structure containing all data being passed to the * handler. */ - Handler(const HandlerData &handlerData) : handlerData(handlerData) {}; + Handler(const HandlerData &handlerData) : handlerData(handlerData){}; /** * Virtual destructor. */ virtual ~Handler(){}; - - const std::string& name() {return handlerData.name;} + const std::string &name() { return handlerData.name; } - Scope &scope() {return handlerData.ctx.scope;} + Scope &scope() { return handlerData.ctx.scope; } - Registry ®istry() {return handlerData.ctx.registry;} + Registry ®istry() { return handlerData.ctx.registry; } Manager &manager() { return handlerData.ctx.manager; } Logger &logger() { return handlerData.ctx.logger; } - State state() {return handlerData.state; } + State state() { return handlerData.state; } State parentState() { return handlerData.parentState; } + SourceLocation location() { return handlerData.location; } + bool isChild() { return handlerData.isChild; } /** @@ -279,7 +287,8 @@ struct HandlerDescriptor { */ HandlerInstance create(const ParserContext &ctx, std::string name, State parentState, bool isChild, - Variant::mapType &args) const; + Variant::mapType &args, + const SourceLocation &location) const; }; /** @@ -294,9 +303,9 @@ private: ParserContext &ctx; /** - * User specified data that will be passed to all handlers. + * Current location in the source code. */ - void *userData; + SourceLocation location; /** * Map containing all registered command names and the corresponding @@ -369,16 +378,22 @@ public: * * @param name is the name of the command. * @param args is a map from strings to variants (argument name and value). + * @param location is the location in the source file at which the command + * starts. */ - void start(std::string name, Variant::mapType &args); + void start(std::string name, Variant::mapType &args, + const SourceLocation &location = SourceLocation{}); /** * Function that should be called whenever a new command starts. * * @param name is the name of the command. * @param args is a map from strings to variants (argument name and value). + * @param location is the location in the source file at which the command + * starts. */ - void start(std::string name, const Variant::mapType &args); + void start(std::string name, const Variant::mapType &args, + const SourceLocation &location = SourceLocation{}); /** * Function called whenever a command ends. diff --git a/src/core/parser/Scope.cpp b/src/core/parser/Scope.cpp index d76af9c..6e7dceb 100644 --- a/src/core/parser/Scope.cpp +++ b/src/core/parser/Scope.cpp @@ -59,11 +59,11 @@ Rooted<Node> ScopeBase::resolve(const std::vector<std::string> &path, // Log an error if the object is not unique if (res.size() > 1) { - logger.error(std::string("The reference ") + - Utils::join(path, ".") + (" is ambigous!")); + logger.error(std::string("The reference \"") + + Utils::join(path, ".") + ("\" is ambigous!")); logger.note("Referenced objects are:"); for (const ResolutionResult &r : res) { - logger.note(std::string("\t") + Utils::join(r.path(), ".")); + logger.note(Utils::join(r.path(), ".")); } } return res[0].node; @@ -73,10 +73,16 @@ Rooted<Node> ScopeBase::resolve(const std::vector<std::string> &path, /* Class DeferredResolution */ -DeferredResolution::DeferredResolution( - const NodeVector<Node> &nodes, const std::vector<std::string> &path, - const RttiType &type, std::function<void(Handle<Node>)> resultCallback) - : scope(nodes), resultCallback(resultCallback), path(path), type(type) +DeferredResolution::DeferredResolution(const NodeVector<Node> &nodes, + const std::vector<std::string> &path, + const RttiType &type, + ResolutionResultCallback resultCallback, + const SourceLocation &location) + : scope(nodes), + resultCallback(resultCallback), + path(path), + type(type), + location(location) { } @@ -84,7 +90,12 @@ bool DeferredResolution::resolve(Logger &logger) { Rooted<Node> res = scope.resolve(path, type, logger); if (res != nullptr) { - resultCallback(res); + try { + resultCallback(res, logger); + } + catch (LoggableException ex) { + logger.log(ex); + } return true; } return false; @@ -106,30 +117,32 @@ Rooted<Node> Scope::getRoot() const { return nodes.front(); } Rooted<Node> Scope::getLeaf() { return nodes.back(); } bool Scope::resolve(const std::vector<std::string> &path, const RttiType &type, - Logger &logger, - std::function<Rooted<Node>()> imposterCallback, - std::function<void(Handle<Node>)> resultCallback) + Logger &logger, ResolutionImposterCallback imposterCallback, + ResolutionResultCallback resultCallback, + const SourceLocation &location) { - Rooted<Node> res = ScopeBase::resolve(path, type, logger); - if (res != nullptr) { - resultCallback(res); - return true; + if (!resolve(path, type, logger, resultCallback, location)) { + resultCallback(imposterCallback(), logger); + return false; } - resultCallback(imposterCallback()); - deferred.emplace_back(nodes, path, type, resultCallback); - return false; + return true; } bool Scope::resolve(const std::vector<std::string> &path, const RttiType &type, - Logger &logger, - std::function<void(Handle<Node>)> successCallback) + Logger &logger, ResolutionResultCallback resultCallback, + const SourceLocation &location) { Rooted<Node> res = ScopeBase::resolve(path, type, logger); if (res != nullptr) { - successCallback(res); + try { + resultCallback(res, logger); + } + catch (LoggableException ex) { + logger.log(ex, location); + } return true; } - deferred.emplace_back(nodes, path, type, successCallback); + deferred.emplace_back(nodes, path, type, resultCallback, location); return false; } @@ -157,14 +170,13 @@ bool Scope::performDeferredResolution(Logger &logger) // Output an error message if there are still deferred elements left that // could not be resolved - // TODO: Log this at the position at which the resolution was originally - // triggered if (!deferred.empty()) { for (const auto &failed : deferred) { logger.error( - std::string("Could not resolve \"") + - Utils::join(failed.path, ".") + - std::string("\" of internal type " + failed.type.name)); + std::string("Could not resolve a reference to \"") + + Utils::join(failed.path, ".") + + std::string("\" of type " + failed.type.name), + failed.location); } } diff --git a/src/core/parser/Scope.hpp b/src/core/parser/Scope.hpp index 2713c41..c99aa65 100644 --- a/src/core/parser/Scope.hpp +++ b/src/core/parser/Scope.hpp @@ -43,6 +43,18 @@ namespace parser { class Scope; /** + * Callback function type used for creating a dummy object while no correct + * object is available for resolution. + */ +using ResolutionImposterCallback = std::function<Rooted<Node>()>; + +/** + * Callback function type called whenever the result of a resolution is + * available. + */ +using ResolutionResultCallback = std::function<void(Handle<Node>, Logger &logger)>; + +/** * The GuardedScope class takes care of pushing a Node instance into the * name resolution stack of a Scope instance and poping this node once the * ScopedScope instance is deletes. This way you cannot forget to pop a Node @@ -148,7 +160,7 @@ private: /** * Callback function to be called when an element is successfully resolved. */ - std::function<void(Handle<Node>)> resultCallback; + ResolutionResultCallback resultCallback; public: /** @@ -162,6 +174,11 @@ public: const RttiType &type; /** + * Position at which the resolution was triggered. + */ + const SourceLocation location; + + /** * Constructor of the DeferredResolutionScope class. Copies the given * arguments. * @@ -172,11 +189,13 @@ public: * @param type is the RttiType of the element that should be queried. * @param resultCallback is the callback function that should be called if * the desired element has indeed been found. + * @param location is the location at which the resolution was triggered. */ DeferredResolution(const NodeVector<Node> &nodes, const std::vector<std::string> &path, const RttiType &type, - std::function<void(Handle<Node>)> resultCallback); + ResolutionResultCallback resultCallback, + const SourceLocation &location = SourceLocation{}); /** * Performs the actual deferred resolution and calls the resultCallback @@ -267,34 +286,39 @@ public: * resolved object directly when this function is called. If the resolution * was not successful the first time, it may be called another time later * in the context of the "performDeferredResolution" function. + * @param location is the location in the current source file in which the + * resolution was triggered. * @return true if the resolution was immediately successful. This does not * mean, that the resolved object does not exist, as it may be resolved * later. */ bool resolve(const std::vector<std::string> &path, const RttiType &type, - Logger &logger, std::function<Rooted<Node>()> imposterCallback, - std::function<void(Handle<Node>)> resultCallback); + Logger &logger, ResolutionImposterCallback imposterCallback, + ResolutionResultCallback resultCallback, + const SourceLocation &location = SourceLocation{}); /** * Tries to resolve a node for the given type and path for all nodes * currently on the stack, starting with the topmost node on the stack. - * The "successCallback" is called when the resolution was successful, which + * The "resultCallback" is called when the resolution was successful, which * may be at a later point in time. * * @param path is the path for which a node should be resolved. * @param type is the type of the node that should be resolved. * @param logger is the logger instance into which resolution problems * should be logged. - * @param successCallback is the callback function to which the result of + * @param resultCallback is the callback function to which the result of * the resolution process is passed. This function is called once the * resolution was successful. + * @param location is the location in the current source file in which the + * resolution was triggered. * @return true if the resolution was immediately successful. This does not * mean, that the resolved object does not exist, as it may be resolved * later. */ bool resolve(const std::vector<std::string> &path, const RttiType &type, - Logger &logger, - std::function<void(Handle<Node>)> successCallback); + Logger &logger, ResolutionResultCallback resultCallback, + const SourceLocation &location = SourceLocation{}); /** * Tries to resolve a node for the given type and path for all nodes @@ -319,6 +343,8 @@ public: * resolved object directly when this function is called. If the resolution * was not successful the first time, it may be called another time later * in the context of the "performDeferredResolution" function. + * @param location is the location in the current source file in which the + * resolution was triggered. * @return true if the resolution was immediately successful. This does not * mean, that the resolved object does not exist, as it may be resolved * later. @@ -326,41 +352,45 @@ public: template <class T> bool resolve(const std::vector<std::string> &path, Logger &logger, std::function<Rooted<T>()> imposterCallback, - std::function<void(Handle<T>)> successCallback) + std::function<void(Handle<T>, Logger&)> resultCallback, + const SourceLocation &location = SourceLocation{}) { return resolve( path, typeOf<T>(), logger, [imposterCallback]() -> Rooted<Node> { return imposterCallback(); }, - [successCallback](Handle<Node> node) { - successCallback(node.cast<T>()); - }); + [resultCallback](Handle<Node> node, Logger &logger) { + resultCallback(node.cast<T>(), logger); + }, location); } /** * Tries to resolve a node for the given type and path for all nodes * currently on the stack, starting with the topmost node on the stack. - * The "successCallback" is called when the resolution was successful, which + * The "resultCallback" is called when the resolution was successful, which * may be at a later point in time. * * @tparam is the type of the node that should be resolved. * @param path is the path for which a node should be resolved. * @param logger is the logger instance into which resolution problems * should be logged. - * @param successCallback is the callback function to which the result of + * @param resultCallback is the callback function to which the result of * the resolution process is passed. This function is called once the * resolution was successful. + * @param location is the location in the current source file in which the + * resolution was triggered. * @return true if the resolution was immediately successful. This does not * mean, that the resolved object does not exist, as it may be resolved * later. */ template <class T> bool resolve(const std::vector<std::string> &path, Logger &logger, - std::function<void(Handle<T>)> resultCallback) + std::function<void(Handle<T>, Logger&)> resultCallback, + const SourceLocation &location = SourceLocation{}) { return resolve(path, typeOf<T>(), logger, - [resultCallback](Handle<Node> node) { - resultCallback(node.cast<T>()); - }); + [resultCallback](Handle<Node> node, Logger &logger) { + resultCallback(node.cast<T>(), logger); + }, location); } /** diff --git a/src/plugins/xml/XmlParser.cpp b/src/plugins/xml/XmlParser.cpp index ced61ee..cd220a9 100644 --- a/src/plugins/xml/XmlParser.cpp +++ b/src/plugins/xml/XmlParser.cpp @@ -23,6 +23,7 @@ #include <core/common/CharReader.hpp> #include <core/common/Utils.hpp> +#include <core/common/VariantReader.hpp> #include <core/parser/ParserStack.hpp> #include <core/model/Typesystem.hpp> @@ -32,6 +33,8 @@ namespace ousia { namespace parser { namespace xml { +using namespace ousia::model; + /* Document structure */ static const State STATE_DOCUMENT = 0; static const State STATE_HEAD = 1; @@ -63,6 +66,8 @@ public: void end() override { scope().performDeferredResolution(logger()); + // TODO: Automatically call validate in "pop"? + scope().getLeaf()->validate(logger()); scope().pop(); } @@ -76,22 +81,36 @@ class StructHandler : public Handler { public: using Handler::Handler; - std::string name; - std::string parent; - - NodeVector<model::Attribute> attributes; - void start(Variant::mapType &args) override { - this->name = args["name"].asString(); - this->parent = args["parent"].asString(); - } + // Fetch the arguments used for creating this type + const std::string &name = args["name"].asString(); + const std::string &parent = args["parent"].asString(); + + // Fetch the current typesystem and create the struct node + Rooted<Typesystem> typesystem = scope().getLeaf().cast<Typesystem>(); + Rooted<StructType> structType = typesystem->createStructType(name); + + // Try to resolve the parent type and set it as parent structure + if (!parent.empty()) { + scope().resolve<StructType>(Utils::split(parent, '.'), logger(), + [structType](Handle<StructType> parent, + Logger &logger) mutable { + structType->setParentStructure( + parent, logger); + }, + location()); + } - void end() override { - + // Descend into the struct type + scope().push(structType); } - void child(std::shared_ptr<Handler> handler) {} + void end() override + { + // Descend from the struct type + scope().pop(); + } static Handler *create(const HandlerData &handlerData) { @@ -103,15 +122,24 @@ class StructFieldHandler : public Handler { public: using Handler::Handler; - Rooted<model::Attribute> attribute; - void start(Variant::mapType &args) override { - /* this->name = args["name"].asString(); - this->type = args["parent"].asString();*/ + // Read the argument values + /* const std::string &name = args["name"].asString(); + const std::string &type = args["parent"].asString(); + const Variant &defaultValue = args["default"]; + const bool optional = !(defaultValue.isObject() && + defaultValue.asObject() == nullptr);*/ + + // Try to resolve the } void end() override {} + + static Handler *create(const HandlerData &handlerData) + { + return new StructFieldHandler{handlerData}; + } }; static const std::multimap<std::string, HandlerDescriptor> XML_HANDLERS{ @@ -142,7 +170,7 @@ static const std::multimap<std::string, HandlerDescriptor> XML_HANDLERS{ {Argument::String("name"), Argument::String("parent", "")}}}, {"field", {{{STATE_STRUCT}}, - nullptr, + StructFieldHandler::create, STATE_FIELD, false, {Argument::String("name"), Argument::String("type"), @@ -198,24 +226,25 @@ public: /* Adapter Expat -> ParserStack */ -static void syncLoggerPosition(XML_Parser p) +static SourceLocation syncLoggerPosition(XML_Parser p) { // Fetch the current location in the XML file int line = XML_GetCurrentLineNumber(p); int column = XML_GetCurrentColumnNumber(p); size_t offs = XML_GetCurrentByteIndex(p); + SourceLocation loc{line, column, offs}; // Update the default location of the current logger instance ParserStack *stack = static_cast<ParserStack *>(XML_GetUserData(p)); - stack->getContext().logger.setDefaultLocation( - SourceLocation{line, column, offs}); + stack->getContext().logger.setDefaultLocation(loc); + return loc; } static void xmlStartElementHandler(void *p, const XML_Char *name, const XML_Char **attrs) { XML_Parser parser = static_cast<XML_Parser>(p); - syncLoggerPosition(parser); + SourceLocation loc = syncLoggerPosition(parser); ParserStack *stack = static_cast<ParserStack *>(XML_GetUserData(parser)); @@ -223,9 +252,11 @@ static void xmlStartElementHandler(void *p, const XML_Char *name, const XML_Char **attr = attrs; while (*attr) { const std::string key{*(attr++)}; - args.emplace(std::make_pair(key, Variant{*(attr++)})); + std::pair<bool, Variant> value = VariantReader::parseGenericString( + *(attr++), stack->getContext().logger); + args.emplace(std::make_pair(key, value.second)); } - stack->start(std::string(name), args); + stack->start(std::string(name), args, loc); } static void xmlEndElementHandler(void *p, const XML_Char *name) diff --git a/test/core/common/VariantReaderTest.cpp b/test/core/common/VariantReaderTest.cpp index 3ab38b9..9a9b694 100644 --- a/test/core/common/VariantReaderTest.cpp +++ b/test/core/common/VariantReaderTest.cpp @@ -762,6 +762,42 @@ TEST(VariantReader, parseGeneric) } } +TEST(VariantReader, parseGenericString) +{ + // Simple case, unescaped string + { + auto res = VariantReader::parseGenericString("foo", logger); + ASSERT_TRUE(res.first); + ASSERT_TRUE(res.second.isMagic()); + ASSERT_EQ("foo", res.second.asMagic()); + } + + // Simple case, unescaped string with space + { + auto res = VariantReader::parseGenericString("foo bar", logger); + ASSERT_TRUE(res.first); + ASSERT_FALSE(res.second.isMagic()); + ASSERT_TRUE(res.second.isString()); + ASSERT_EQ("foo bar", res.second.asString()); + } + + // Parse double + { + auto res = VariantReader::parseGenericString("12.3", logger); + ASSERT_TRUE(res.first); + ASSERT_TRUE(res.second.isDouble()); + ASSERT_EQ(12.3, res.second.asDouble()); + } + + // Parse string + { + auto res = VariantReader::parseGenericString("6 times 7 is 42", logger); + ASSERT_TRUE(res.first); + ASSERT_TRUE(res.second.isString()); + ASSERT_EQ("6 times 7 is 42", res.second.asString()); + } +} + TEST(VariantReader, parseGenericComplex) { CharReader reader("10 true [1, 2] [] [foo=bar,h]; []"); diff --git a/test/core/model/NodeTest.cpp b/test/core/model/NodeTest.cpp index 973ce22..8bbee05 100644 --- a/test/core/model/NodeTest.cpp +++ b/test/core/model/NodeTest.cpp @@ -26,7 +26,7 @@ namespace ousia { class TestNode : public Node { protected: - void continueResolve(ResolutionState &state) override + void doResolve(ResolutionState &state) override { continueResolveComposita(composita, composita.getIndex(), state); continueResolveReferences(references, state); diff --git a/test/core/model/TypesystemTest.cpp b/test/core/model/TypesystemTest.cpp index 5871092..5a2efdd 100644 --- a/test/core/model/TypesystemTest.cpp +++ b/test/core/model/TypesystemTest.cpp @@ -611,6 +611,51 @@ TEST(StructType, createValidated) } } +TEST(StructType, setParentStructure) +{ + Manager mgr; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<StructType> sa{new StructType(mgr, "a", sys)}; + Rooted<StructType> sb{new StructType(mgr, "b", sys)}; + Rooted<StructType> sc{new StructType(mgr, "c", sys)}; + + sa->addAttribute(new Attribute{mgr, "a", sys->getBoolType()}, logger); + sb->addAttribute(new Attribute{mgr, "b", sys->getStringType()}, logger); + sc->addAttribute(new Attribute{mgr, "a", sys->getIntType()}, logger); + sc->addAttribute(new Attribute{mgr, "b", sys->getIntType()}, logger); + + ASSERT_TRUE(sa->validate(logger)); + ASSERT_TRUE(sb->validate(logger)); + ASSERT_TRUE(sc->validate(logger)); + + logger.reset(); + sb->setParentStructure(sa, logger); + ASSERT_EQ(2U, sb->getAttributes().size()); + ASSERT_EQ("a", sb->getAttributes()[0]->getName()); + ASSERT_EQ("b", sb->getAttributes()[1]->getName()); + ASSERT_EQ(sys->getBoolType(), sb->getAttributes()[0]->getType()); + ASSERT_EQ(sys->getStringType(), sb->getAttributes()[1]->getType()); + ASSERT_FALSE(logger.hasError()); + + logger.reset(); + sc->setParentStructure(sb, logger); + ASSERT_EQ(4U, sc->getAttributes().size()); + ASSERT_EQ("a", sc->getAttributes()[0]->getName()); + ASSERT_EQ("b", sc->getAttributes()[1]->getName()); + ASSERT_EQ(sys->getBoolType(), sc->getAttributes()[0]->getType()); + ASSERT_EQ(sys->getStringType(), sc->getAttributes()[1]->getType()); + ASSERT_TRUE(logger.hasError()); + + logger.reset(); + sc->setParentStructure(nullptr, logger); + ASSERT_EQ(2U, sc->getAttributes().size()); + ASSERT_EQ("a", sc->getAttributes()[0]->getName()); + ASSERT_EQ("b", sc->getAttributes()[1]->getName()); + ASSERT_EQ(sys->getIntType(), sc->getAttributes()[0]->getType()); + ASSERT_EQ(sys->getIntType(), sc->getAttributes()[1]->getType()); + ASSERT_FALSE(logger.hasError()); +} + TEST(StructType, cast) { Manager mgr; diff --git a/test/core/parser/StandaloneParserContext.hpp b/test/core/parser/StandaloneParserContext.hpp index 64a245f..78d148d 100644 --- a/test/core/parser/StandaloneParserContext.hpp +++ b/test/core/parser/StandaloneParserContext.hpp @@ -26,10 +26,10 @@ namespace parser { struct StandaloneParserContext : public ParserContext { private: + Manager manager; Logger logger; Scope scope; Registry registry; - Manager manager; public: StandaloneParserContext() diff --git a/test/plugins/xml/XmlParserTest.cpp b/test/plugins/xml/XmlParserTest.cpp index ce53eb3..2046940 100644 --- a/test/plugins/xml/XmlParserTest.cpp +++ b/test/plugins/xml/XmlParserTest.cpp @@ -53,14 +53,17 @@ const char *TEST_DATA = " <head>\n" " <typesystem name=\"color\">\n" " <types>\n" - " <struct name=\"blub\">\n" - " <field name=\"a\" type=\"int\"/>\n" - " </struct>\n" " <struct name=\"color\" parent=\"blub\">\n" " <field name=\"r\" type=\"int\"/>\n" " <field name=\"g\" type=\"int\"/>\n" " <field name=\"b\" type=\"int\"/>\n" " </struct>\n" + " <struct name=\"blub\">\n" + " <field name=\"a\" type=\"int\"/>\n" + " </struct>\n" + " <struct name=\"blub\">\n" + " <field name=\"a\" type=\"int\"/>\n" + " </struct>\n" " </types>\n" " </typesystem>\n" " </head>\n" |