/*
Ousía
Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef _OUSIA_PARSER_SCOPE_HPP_
#define _OUSIA_PARSER_SCOPE_HPP_
#include
#include
#include
#include
#include
#include
#include
#include
/**
* @file ParserScope.hpp
*
* Contains the ParserScope class used for resolving references based on the
* current parser state.
*
* @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de)
*/
namespace ousia {
// Forward declaration
class CharReader;
class Logger;
class ParserScope;
class Type;
class Variant;
/**
* Callback function type used for creating a dummy object while no correct
* object is available for resolution.
*/
using ResolutionImposterCallback = std::function()>;
/**
* Callback function type called whenever the result of a resolution is
* available.
*
* @param resolved is the new, resolved node.
* @param owner is the node that was passed as "owner".
* @param logger is the logger to which errors should be logged.
*/
using ResolutionResultCallback = std::function<
void(Handle resolved, Handle owner, Logger &logger)>;
/**
* Base class for the ParserScope, does not contain the mechanisms for deferred
* lookup, only maintains the stack of nodes.
*/
class ParserScopeBase {
protected:
/**
* List containing all nodes currently on the scope, with the newest nodes
* being pushed to the back of the list.
*/
NodeVector nodes;
public:
/**
* Default constructor, creates an empty ParserScope instance.
*/
ParserScopeBase();
/**
* Creates a new instance of the ParserScopeBase 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.
*/
ParserScopeBase(const NodeVector &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 type 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.
* @return a reference at a resolved node or nullptr if no node could be
* found.
*/
Rooted resolve(const Rtti *type, const std::vector &path,
Logger &logger);
/**
* Returns true if the stack is empty.
*
* @return true if there is no element on the stack.
*/
bool isEmpty() const;
/**
* Returns a reference at the internal node stack.
*
* @return a const reference at the internal node stack.
*/
const NodeVector &getStack() const;
/**
* Returns a list containing the Rtti type of each Node that is currently
* in the stack.
*
* @return the type signature of the current node stack.
*/
std::vector getStackTypeSignature() const;
/**
* Returns the top-most Node instance in the ParserScopeBase hirarchy.
*
* @return a reference at the root node.
*/
Rooted getRoot() const;
/**
* Returns the bottom-most Node instance in the ParserScopeBase hirarchy,
* e.g. the node that was pushed last onto the stack.
*
* @return a reference at the leaf node.
*/
Rooted getLeaf() const;
/**
* Ascends in the stack starting with the leaf node, returns the first node
* that matches the type given in the RttiSet or nullptr if none matches.
*
* @param types is a set of Rtti types for which should be searched in the
* stack.
* @param maxDepth is the maximum number of stack entries the selection
* function may ascend. A negative value indicates no limitation.
* @return the matching node or nullptr if the node was not found.
*/
Rooted select(RttiSet types, int maxDepth = -1);
/**
* Ascends in the stack starting with the leaf node, returns the first node
* that matches the given type or nullptr if none matches.
*
* @tparam T is the type that should be searched in the stack.
* @param maxDepth is the maximum number of stack entries the selection
* function may ascend. A negative value indicates no limitation.
* @return the matching node or nullptr if the node was not found.
*/
template
Rooted select(int maxDepth = -1)
{
return select(RttiSet{typeOf()}, maxDepth).cast();
}
/**
* Ascends in the stack starting with the leaf node, returns the first node
* that matches the type given in the RttiSet. Throws an exception if no
* node matches.
*
* @param types is a set of Rtti types for which should be searched in the
* stack.
* @param maxDepth is the maximum number of stack entries the selection
* function may ascend. A negative value indicates no limitation.
* @return the matching node.
*/
Rooted selectOrThrow(RttiSet types, int maxDepth = -1);
/**
* Ascends in the stack starting with the leaf node, returns the first node
* that matches the given type. Throws an exception if no node matches.
*
* @tparam T is the type that should be searched in the stack.
* @param maxDepth is the maximum number of stack entries the selection
* function may ascend. A negative value indicates no limitation.
* @return the matching node.
*/
template
Rooted selectOrThrow(int maxDepth = -1)
{
return selectOrThrow(RttiSet{typeOf()}, maxDepth).cast();
}
};
/**
* 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.
*/
ParserScopeBase scope;
/**
* Callback function to be called when an element is successfully resolved.
*/
ResolutionResultCallback resultCallback;
public:
/**
* Path queried for the resolution.
*/
std::vector path;
/**
* Reference at the type of the object that should be resolved.
*/
const Rtti *type;
/**
* Node for which the resolution is taking place.
*/
Rooted owner;
/**
* Constructor of the DeferredResolutionScope class. Copies the given
* arguments.
*
* @param nodes is a reference at the current internal node stack of the
* ParserScope class.
* @param path is the path that was queried when the resolution failed the
* first time.
* @param type is the Rtti 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 owner is the node for which the resolution takes place.
*/
DeferredResolution(const NodeVector &nodes,
const std::vector &path, const Rtti *type,
ResolutionResultCallback resultCallback,
Handle owner);
/**
* 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 ignore is a set of nodes that should be ignored if returned as
* resolution result as they are
* @param logger is the logger instance to which error messages should be
* logged.
* @return true if the resolution was successful, false otherwise.
*/
bool resolve(const std::unordered_multiset &ignore,
Logger &logger);
/**
* Inform the callee about the failure by calling the callback function with
* "nullptr" as resolved element.
*
* @param logger is the logger instance to which error messages should be
* logged.
*/
void fail(Logger &logger);
};
/**
* Enum containing all possible parser flags that can be used by parsers to
* signal states that cannot be (explicitly or implicitly) stored in the node
* graph itself.
*/
enum class ParserFlag {
/**
* Set to the boolean value "true" if the head section of a file has passed.
* This happens once the first non-import tag is reached.
*/
POST_HEAD,
/**
* Set to the boolean value "true" if explicit fields may no longer be
* defined inside a structure element.
*/
POST_EXPLICIT_FIELDS,
/**
* Set to true if all user defined tokens have been registered.
*/
POST_USER_DEFINED_TOKEN_REGISTRATION
};
/**
* Provides an interface for document parsers to resolve references based on the
* current position in the created document tree. The ParserScope class itself
* is represented as a chain of ParserScope objects where each element has a
* reference to a Node object attached to it.
*/
class ParserScope : public ParserScopeBase {
public:
/**
* Struct describing a set parser flag.
*/
struct ParserFlagDescriptor {
/**
* Stack depth at which the flag has been set.
*/
size_t depth;
/**
* Flag that has been set.
*/
ParserFlag flag;
/**
* Value of that flag.
*/
bool value;
/**
* Default constructor.
*/
ParserFlagDescriptor() {}
/**
* Constructor of the parser flag descriptor class.
*
* @param depth is the depth at which the flag was set.
* @param flag is the flag that has been set.
* @param value is the value that has been set for that flag.
*/
ParserFlagDescriptor(size_t depth, ParserFlag flag, bool value)
: depth(depth), flag(flag), value(value)
{
}
};
private:
/**
* List containing all deferred resolution descriptors.
*/
std::list deferred;
/**
* Multiset storing the Nodes that are currently awaiting resolution. This
* list has the purpose of forcing nodes to be resolved in the correct order
* -- first nodes need to be returned as resolution result, that do
* themselves not depend on other resolutions. However, if no further
* resolutions are possible, this rule is ignored and all resolutions are
* performed.
*/
std::unordered_multiset awaitingResolution;
/**
* Vector containing all set flags. The vector contains triples of the
* depth at which the flag was set, the flag itself and the value.
*/
std::vector flags;
/**
* Depth of the "nodes" list when the ParserScope was created.
*/
size_t topLevelDepth;
/**
* List of a all nodes that have been pushed onto the scope at the top level
* depth.
*/
NodeVector topLevelNodes;
/**
* Private constructor used to create a ParserScope fork.
*/
ParserScope(const NodeVector &nodes,
const std::vector &flags);
public:
/**
* Default constructor of the ParserScope class, creates an empty
* ParserScope with no element on the internal stack.
*/
ParserScope();
/**
* Makes sure all elements on the scope have been unwound. Loggs an error
* message if this is not the case and returns false.
*
* @param logger is the Logger instance to which information in case of
* failure should be written.
* @return true if the stack is unwound, false otherwise.
*/
bool checkUnwound(Logger &logger) const;
/**
* Returns a new ParserScope instance with a copy of the current node stack
* but empty deferred resolutions list and empty topLevelNodes.
*
* @return a forked ParserScope instance, which starts with a copy of the
* node stack.
*/
ParserScope fork();
/**
* Joins a previously forked ParserScope instance with this ParserScope.
* Copies all pending deferred resolutions from this ParserScope instance.
* Joining only works if the node stack of the given ParserScope has the
* same depth as the node stack of this ParserScope instance (has been
* unwound). This is assured by calling the "checkUnwound" function of
* the fork.
*
* @param fork is the ParserScope fork that should be joined with this
* ParserScope instance.
* @param logger is the Logger instance to which information in case of
* failure should be written.
* @return true if the operation was successful, false otherwise.
*/
bool join(const ParserScope &fork, Logger &logger);
/**
* Pushes a new node onto the scope.
*
* @param node is the node that should be used for local lookup.
*/
void push(Handle node);
/**
* Removes the last pushed node from the scope. If the node that is popped
* from the internal stack is a RootNode, pending resolutions are performed
* and the RootNode is validated.
*
* @param logger is the Logger instance to which error messages should be
* logged.
*/
void pop(Logger &logger);
/**
* Returns the top-level nodes. These are the nodes that are pushed onto the
* scope instance while the node stack has the depth it had during the
* creation of this ParserScope instance.
*
* @return a node vector containing the top-level nodes.
*/
NodeVector getTopLevelNodes() const;
/**
* Sets a parser flag for the current stack depth.
*
* @param flag is the flag that should be set.
* @param value is the value to which the flag should be set.
*/
void setFlag(ParserFlag flag, bool value);
/**
* Gets the parser flag for the current stack depth, ascends the stack until
* a set for this flag is found. Returns false if the flag is not set.
*
* @param flag is the flag for which the value should be returned.
* @return the value that was previously set by setParserFlag or false if no
* value has been set.
*/
bool getFlag(ParserFlag flag);
/**
* 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 type is the type of the node that should be resolved.
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @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.
*/
bool resolve(const Rtti *type, const std::vector &path,
Handle owner, Logger &logger,
ResolutionImposterCallback imposterCallback,
ResolutionResultCallback resultCallback);
/**
* 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 "resultCallback" is called when the resolution was successful, which
* may be at a later point in time.
*
* @param type is the type of the node that should be resolved.
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 Rtti *type, const std::vector &path,
Handle owner, Logger &logger,
ResolutionResultCallback resultCallback);
/**
* 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 T is the type of the node that should be resolved.
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @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
bool resolve(const std::vector &path, Handle owner,
Logger &logger, ResolutionImposterCallback imposterCallback,
ResolutionResultCallback resultCallback)
{
return resolve(typeOf(), path, owner, logger, imposterCallback,
resultCallback);
}
/**
* 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 "resultCallback" is called when the resolution was successful, which
* may be at a later point in time.
*
* @tparam T is the type of the node that should be resolved.
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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
bool resolve(const std::vector &path, Handle owner,
Logger &logger, ResolutionResultCallback resultCallback)
{
return resolve(typeOf(), path, owner, logger, resultCallback);
}
/**
* 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 T is the type of the node that should be resolved.
* @param name is the path for which a node should be resolved. The name is
* split at '.' to form a path.
* @param owner is the node for which the resolution takes place.
* @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
bool resolve(const std::string &name, Handle owner, Logger &logger,
ResolutionImposterCallback imposterCallback,
ResolutionResultCallback resultCallback)
{
return resolve(Utils::split(name, '.'), owner, logger,
imposterCallback, resultCallback);
}
/**
* 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 "resultCallback" is called when the resolution was successful, which
* may be at a later point in time.
*
* @tparam T is the type of the node that should be resolved.
* @param name is the path for which a node should be resolved. The name is
* split at '.' to form a path.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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
bool resolve(const std::string &name, Handle owner, Logger &logger,
ResolutionResultCallback resultCallback)
{
return resolve(Utils::split(name, '.'), owner, logger,
resultCallback);
}
/**
* 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.
*
* @tparam T 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.
* @return a reference at a resolved node or nullptr if no node could be
* found.
*/
template
Rooted resolve(const std::vector &path, Logger &logger)
{
// TODO: Rooted res = resolve(typeOf(), path,
// logger).cast();
// does not work. Why? Bother stackoverflow with this.
Rooted res = ParserScopeBase::resolve(typeOf(), path, logger);
return res.cast();
}
/**
* Resolves a typesystem type. Makes sure an array type is returned if an
* array type is requested.
*
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveType(const std::vector &path, Handle owner,
Logger &logger, ResolutionResultCallback resultCallback);
/**
* Resolves a typesystem type. Makes sure an array type is returned if an
* array type is requested.
*
* @param name is the path for which a node should be resolved. The name is
* split at '.' to form a path.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveType(const std::string &name, Handle owner,
Logger &logger, ResolutionResultCallback resultCallback);
/**
* Build and resolves a (possibly) magic value with the given typesystem
* type. This function does not perform any deferred lookups.
*
* @param data is a reference at a variant that may contain magic values
* (even in inner structures). The data will be passed to the "build"
* function of the given type.
* @param type is the Typesystem type the data should be interpreted with.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @return true if the value was successfully built.
*/
bool resolveValue(Variant &data, Handle type, Logger &logger);
/**
* Resolves a type and makes sure the corresponding value is of the correct
* type.
*
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @param value is a reference at the Variant that represents the value for
* which the type should be looked up. The value must be valid as long as
* the owner node is valid (so it should be a part of the owner).
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveTypeWithValue(const std::vector &path,
Handle owner, Variant &value,
Logger &logger,
ResolutionResultCallback resultCallback);
/**
* Resolves a type and makes sure the corresponding value is of the correct
* type.
*
* @param name is the path for which a node should be resolved. The name is
* split at '.' to form a path.
* @param owner is the node for which the resolution takes place.
* @param value is a reference at the Variant that represents the value for
* which the type should be looked up. The value must be valid as long as
* the owner node is valid (so it should be a part of the owner).
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveTypeWithValue(const std::string &name, Handle owner,
Variant &value, Logger &logger,
ResolutionResultCallback resultCallback);
/**
* Resolves a FieldDescriptor. Makes sure that the default field can be
* handled.
*
* @param path is the path for which a node should be resolved.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveFieldDescriptor(const std::vector &path,
Handle owner, Logger &logger,
ResolutionResultCallback resultCallback);
/**
* Resolves a FieldDescriptor. Makes sure that the default field can be
* handled.
*
* @param name is the path for which a node should be resolved. The name is
* split at '.' to form a path.
* @param owner is the node for which the resolution takes place.
* @param logger is the logger instance into which resolution problems
* should be logged.
* @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.
* @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 resolveFieldDescriptor(const std::string &name, Handle owner,
Logger &logger,
ResolutionResultCallback resultCallback);
/**
* Tries to resolve all currently deferred resolution steps. The list of
* pending deferred resolutions is cleared after this function has run.
*
* @param logger is the logger instance into which errors should be logged.
* @param postpone if set to true, postpones issuing any error messages and
* waits for node resolution.
*/
bool performDeferredResolution(Logger &logger, bool postpone = false);
};
}
#endif /* _OUSIA_PARSER_SCOPE_HPP_ */