diff options
author | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2015-01-16 17:02:04 +0100 |
---|---|---|
committer | Andreas Stöckel <andreas@somweyr.de> | 2015-01-16 17:02:04 +0100 |
commit | 378ff2235fdf32983ebf2186a9127e51cbe8a0ab (patch) | |
tree | 12223877b7631dc2adc9d9d69cd7e31da079e27e | |
parent | 6c1288bd3746329c3721c6aca1fb0420061831c3 (diff) |
Allowing deferred resolution of Nodes
-rw-r--r-- | src/core/parser/Scope.cpp | 129 | ||||
-rw-r--r-- | src/core/parser/Scope.hpp | 290 | ||||
-rw-r--r-- | src/plugins/xml/XmlParser.cpp | 19 | ||||
-rw-r--r-- | test/core/parser/StandaloneParserContext.hpp | 2 | ||||
-rw-r--r-- | test/plugins/css/CSSParserTest.cpp | 2 | ||||
-rw-r--r-- | test/plugins/xml/XmlParserTest.cpp | 8 |
6 files changed, 381 insertions, 69 deletions
diff --git a/src/core/parser/Scope.cpp b/src/core/parser/Scope.cpp index c73b908..d76af9c 100644 --- a/src/core/parser/Scope.cpp +++ b/src/core/parser/Scope.cpp @@ -23,8 +23,30 @@ namespace ousia { namespace parser { -Rooted<Node> Scope::resolve(const std::vector<std::string> &path, - const RttiType &type, Logger &logger) +/* Class GuardedScope */ + +GuardedScope::GuardedScope(Scope *scope, Handle<Node> node) : scope(scope) +{ + scope->push(node); +} + +GuardedScope::~GuardedScope() +{ + if (scope) { + scope->pop(); + } +} + +GuardedScope::GuardedScope(GuardedScope &&s) +{ + scope = s.scope; + s.scope = nullptr; +} + +/* Class ScopeBase */ + +Rooted<Node> ScopeBase::resolve(const std::vector<std::string> &path, + const RttiType &type, Logger &logger) { // Go up the stack and try to resolve the for (auto it = nodes.rbegin(); it != nodes.rend(); it++) { @@ -48,5 +70,108 @@ Rooted<Node> Scope::resolve(const std::vector<std::string> &path, } return nullptr; } + +/* 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) +{ +} + +bool DeferredResolution::resolve(Logger &logger) +{ + Rooted<Node> res = scope.resolve(path, type, logger); + if (res != nullptr) { + resultCallback(res); + return true; + } + return false; +} + +/* Class Scope */ + +void Scope::push(Handle<Node> node) { nodes.push_back(node); } + +void Scope::pop() { nodes.pop_back(); } + +GuardedScope Scope::descend(Handle<Node> node) +{ + return GuardedScope{this, node}; +} + +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) +{ + Rooted<Node> res = ScopeBase::resolve(path, type, logger); + if (res != nullptr) { + resultCallback(res); + return true; + } + resultCallback(imposterCallback()); + deferred.emplace_back(nodes, path, type, resultCallback); + return false; +} + +bool Scope::resolve(const std::vector<std::string> &path, const RttiType &type, + Logger &logger, + std::function<void(Handle<Node>)> successCallback) +{ + Rooted<Node> res = ScopeBase::resolve(path, type, logger); + if (res != nullptr) { + successCallback(res); + return true; + } + deferred.emplace_back(nodes, path, type, successCallback); + return false; +} + +bool Scope::performDeferredResolution(Logger &logger) +{ + // Repeat the resolution process as long as something has changed in the + // last iteration (resolving a node may cause other nodes to be resolvable). + while (true) { + // Iterate over all deferred resolution processes, + bool hasChange = false; + for (auto it = deferred.begin(); it != deferred.end();) { + if (it->resolve(logger)) { + it = deferred.erase(it); + hasChange = true; + } else { + it++; + } + } + + // Abort if nothing has changed in the last iteration + if (!hasChange) { + break; + } + } + + // 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)); + } + } + + // We were successful if there are no more deferred resolutions + return deferred.empty(); +} + +void Scope::purgeDeferredResolutions() { deferred.clear(); } } } diff --git a/src/core/parser/Scope.hpp b/src/core/parser/Scope.hpp index 01a8ea7..2713c41 100644 --- a/src/core/parser/Scope.hpp +++ b/src/core/parser/Scope.hpp @@ -19,9 +19,12 @@ #ifndef _OUSIA_PARSER_SCOPE_H_ #define _OUSIA_PARSER_SCOPE_H_ +#include <functional> +#include <list> #include <vector> #include <core/common/Logger.hpp> +#include <core/common/Rtti.hpp> #include <core/model/Node.hpp> /** @@ -36,15 +39,16 @@ namespace ousia { namespace parser { +// Forward declaration class Scope; /** - * The ScopedScope class takes care of pushing a Node instance into the + * 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 * from a Scope instance as this operation is performed automatically. */ -class ScopedScope { +class GuardedScope { private: /** * Reference at the backing scope instance. @@ -59,23 +63,21 @@ public: * @param node is the Node instance that should be pushed onto the stack of * the Scope instance. */ - ScopedScope(Scope *scope, Handle<Node> node); + GuardedScope(Scope *scope, Handle<Node> node); /** * Pops the Node given in the constructor form the stack of the Scope * instance. */ - ~ScopedScope(); - - /** - * Copying a ScopedScope is invalid. - */ - ScopedScope(const ScopedScope &) = delete; + ~GuardedScope(); /** * Move constructor of the ScopedScope class. */ - ScopedScope(ScopedScope &&); + GuardedScope(GuardedScope &&); + + // No copy construction + GuardedScope(const GuardedScope &) = delete; /** * Provides access at the underlying Scope instance. @@ -89,49 +91,150 @@ public: }; /** + * Base class for the + */ +class ScopeBase { +protected: + /** + * List containing all nodes currently on the scope, with the newest nodes + * being pushed to the back of the list. + */ + NodeVector<Node> nodes; + +public: + /** + * Default constructor, creates an empty Scope instance. + */ + ScopeBase() {} + + /** + * Creates a new instance of the ScopeBase class, copying the the given + * nodes as initial start value of the node stack. This could for example + * be initialized with the path of a node. + * + * @param nodes is a node vector containing the current node stack. + */ + ScopeBase(const NodeVector<Node> &nodes) : nodes(nodes) {} + + /** + * Tries to resolve a node for the given type and path for all nodes that + * are currently in the stack, starting with the topmost node on the stack. + * + * @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. + * @return a reference at a resolved node or nullptr if no node could be + * found. + */ + Rooted<Node> resolve(const std::vector<std::string> &path, + const RttiType &type, Logger &logger); +}; + +/** + * Class used for representing a deferred resolution. A deferred resolution is + * triggered whenever an object cannot be resolved, but there may be a chance + * that it can be resolved in the future. This happens e.g. if a document is + * just being parsed and the object that is being refered to has not been + * reached yet. + */ +class DeferredResolution { +private: + /** + * Copy of the scope at the time when the resolution was first triggered. + */ + ScopeBase scope; + + /** + * Callback function to be called when an element is successfully resolved. + */ + std::function<void(Handle<Node>)> resultCallback; + +public: + /** + * Path queried for the resolution. + */ + std::vector<std::string> path; + + /** + * Reference at the type of the object that should be resolved. + */ + const RttiType &type; + + /** + * Constructor of the DeferredResolutionScope class. Copies the given + * arguments. + * + * @param nodes is a reference at the current internal node stack of the + * Scope class. + * @param path is the path that was queried when the resolution failed the + * first time. + * @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. + */ + DeferredResolution(const NodeVector<Node> &nodes, + const std::vector<std::string> &path, + const RttiType &type, + std::function<void(Handle<Node>)> resultCallback); + + /** + * Performs the actual deferred resolution and calls the resultCallback + * callback function in case the resolution is sucessful. In this case + * returns true, false otherwise. + * + * @param logger is the logger instance to which error messages should be + * logged. + * @return true if the resolution was successful, false otherwise. + */ + bool resolve(Logger &logger); +}; + +/** * Provides an interface for document parsers to resolve references based on the * current position in the created document tree. The Scope class itself is * represented as a chain of Scope objects where each element has a reference to * a Node object attached to it. The descend method can be used to add a new * scope element to the chain. */ -class Scope { +class Scope : public ScopeBase { private: - std::vector<Rooted<Node>> nodes; + /** + * List containing all deferred resolution descriptors. + */ + std::list<DeferredResolution> deferred; public: /** - * Constructor of the Scope class. - * - * @param rootNode is the top-most Node from which elements can be looked - * up. + * Default constructor of the Scope class, creates an empty Scope with no + * element on the internal stack. */ - Scope(Handle<Node> rootNode) { nodes.push_back(rootNode); } + Scope() {} /** * Pushes a new node onto the scope. * * @param node is the node that should be used for local lookup. */ - void push(Handle<Node> node) { nodes.push_back(node); } + void push(Handle<Node> node); /** * Removes the last pushed node from the scope. */ - void pop() { nodes.pop_back(); } + void pop(); /** * Returns a ScopedScope instance, which automatically pushes the given node * into the Scope stack and pops it once the ScopedScope is destroyed. */ - ScopedScope descend(Handle<Node> node) { return ScopedScope{this, node}; } + GuardedScope descend(Handle<Node> node); /** * Returns the top-most Node instance in the Scope hirarchy. * * @return a reference at the root node. */ - Rooted<Node> getRoot() { return nodes.front(); } + Rooted<Node> getRoot() const; /** * Returns the bottom-most Node instance in the Scope hirarchy, e.g. the @@ -139,42 +242,141 @@ public: * * @return a reference at the leaf node. */ - Rooted<Node> getLeaf() { return nodes.back(); } + Rooted<Node> getLeaf(); /** - * Tries to resolve a node for the given type and path for all nodes that - * are currently in the stack, starting with the topmost node on the stack. + * 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. + * Calls the "imposterCallback" function for obtaining a temporary result if + * a node cannot be resolved right now. The "resultCallback" is at most + * called twice: Once when this method is called (probably with the + * temporary) and another time if the resolution turned out to be successful + * 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. - * @return a reference at a resolved node or nullptr if no node could be - * found. + * @param imposterCallback is the callback function that is called if + * the node cannot be resolved at this moment. It gives the caller the + * possibility to create an imposter (a temporary object) that may be used + * later in the resolution process. + * @param resultCallback is the callback function to which the result of + * the resolution process is passed. This function is called at least once + * either with the imposter (if the resolution was not successful) or the + * 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. + * @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. */ - Rooted<Node> resolve(const std::vector<std::string> &path, - const RttiType &type, Logger &logger); -}; + bool resolve(const std::vector<std::string> &path, const RttiType &type, + Logger &logger, std::function<Rooted<Node>()> imposterCallback, + std::function<void(Handle<Node>)> resultCallback); -/* Class ScopedScope -- inline declaration of some methods */ + /** + * 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 + * 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 + * the resolution process is passed. This function is called once the + * resolution was successful. + * @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); -inline ScopedScope::ScopedScope(Scope *scope, Handle<Node> node) : scope(scope) -{ - scope->push(node); -} + /** + * 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. + * Calls the "imposterCallback" function for obtaining a temporary result if + * a node cannot be resolved right now. The "resultCallback" is at most + * called twice: Once when this method is called (probably with the + * temporary) and another time if the resolution turned out to because + * successful 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 imposterCallback is the callback function that is called if + * the node cannot be resolved at this moment. It gives the caller the + * possibility to create an imposter (a temporary object) that may be used + * later in the resolution process. + * @param resultCallback is the callback function to which the result of + * the resolution process is passed. This function is called at least once + * either with the imposter (if the resolution was not successful) or the + * 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. + * @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<Rooted<T>()> imposterCallback, + std::function<void(Handle<T>)> successCallback) + { + return resolve( + path, typeOf<T>(), logger, + [imposterCallback]() -> Rooted<Node> { return imposterCallback(); }, + [successCallback](Handle<Node> node) { + successCallback(node.cast<T>()); + }); + } -inline ScopedScope::~ScopedScope() -{ - if (scope) { - scope->pop(); + /** + * 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 + * 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 + * the resolution process is passed. This function is called once the + * resolution was successful. + * @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) + { + return resolve(path, typeOf<T>(), logger, + [resultCallback](Handle<Node> node) { + resultCallback(node.cast<T>()); + }); } -} -inline ScopedScope::ScopedScope(ScopedScope &&s) -{ - scope = s.scope; - s.scope = nullptr; -} + /** + * Tries to resolve all currently deferred resolution steps. + * + * @param logger is the logger instance into which errors should be logged. + */ + bool performDeferredResolution(Logger &logger); + + /** + * Clears the list of currently deferred resolutions. This function may be + * used to gracefully continue parsing, even after the resolution has + * failed. + */ + void purgeDeferredResolutions(); +}; } } diff --git a/src/plugins/xml/XmlParser.cpp b/src/plugins/xml/XmlParser.cpp index 87a2016..9d1aba4 100644 --- a/src/plugins/xml/XmlParser.cpp +++ b/src/plugins/xml/XmlParser.cpp @@ -85,29 +85,10 @@ public: void end() override { - // Try to resolve the specified parent structure - Rooted<model::StructType> parentStructure; - if (!parent.empty()) { - // TODO: What about (temporarily) unresolved nodes - // Idea: Provide constructor for empty node, store unresolved nodes - // in the scope, resolve later - parentStructure = - scope() - .resolve(Utils::split(parent, '.'), - (const RttiType &)RttiTypes::StructType, logger()) - .cast<model::StructType>(); - } - - Rooted<model::Typesystem> typesystem = - scope().getLeaf().cast<model::Typesystem>(); } void child(std::shared_ptr<Handler> handler) { -/* std::shared_ptr<StructFieldHandler> structFieldHandler = - dynamic_cast<StructFieldHandler>(handler);*/ - - // Try to resolve } static Handler *create(const HandlerData &handlerData) diff --git a/test/core/parser/StandaloneParserContext.hpp b/test/core/parser/StandaloneParserContext.hpp index 0beacaf..64a245f 100644 --- a/test/core/parser/StandaloneParserContext.hpp +++ b/test/core/parser/StandaloneParserContext.hpp @@ -34,12 +34,10 @@ private: public: StandaloneParserContext() : ParserContext(scope, registry, logger, manager), - scope(nullptr), registry(logger){}; StandaloneParserContext(Logger &externalLogger) : ParserContext(scope, registry, externalLogger, manager), - scope(nullptr), registry(externalLogger){}; }; diff --git a/test/plugins/css/CSSParserTest.cpp b/test/plugins/css/CSSParserTest.cpp index ca21e79..54c359b 100644 --- a/test/plugins/css/CSSParserTest.cpp +++ b/test/plugins/css/CSSParserTest.cpp @@ -269,7 +269,7 @@ void assertException(std::string css) { ScopedLogger sl(logger, "test.css", SourceLocation{}, CharReader::contextCallback, &reader); - Scope scope(nullptr); + Scope scope; Registry registry(logger); Manager manager; ParserContext ctx{scope, registry, logger, manager}; diff --git a/test/plugins/xml/XmlParserTest.cpp b/test/plugins/xml/XmlParserTest.cpp index fd13cb9..ce53eb3 100644 --- a/test/plugins/xml/XmlParserTest.cpp +++ b/test/plugins/xml/XmlParserTest.cpp @@ -53,7 +53,13 @@ const char *TEST_DATA = " <head>\n" " <typesystem name=\"color\">\n" " <types>\n" - " <struct name=\"color\">\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" " </types>\n" " </typesystem>\n" |