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 /src | |
| parent | 6c1288bd3746329c3721c6aca1fb0420061831c3 (diff) | |
Allowing deferred resolution of Nodes
Diffstat (limited to 'src')
| -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 | 
3 files changed, 373 insertions, 65 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)  | 
