From db51a874964b038c69f1336a8a659ae40471e26b Mon Sep 17 00:00:00 2001 From: Andreas Stöckel Date: Sun, 18 Jan 2015 13:46:06 +0100 Subject: Implemented storing locations in the ParserStack, improved parsing typesystems --- src/core/model/Typesystem.cpp | 47 +++++++++++----------- src/core/model/Typesystem.hpp | 81 ++++++++++++++++++++++++++++++++++---- src/core/parser/ParserStack.cpp | 15 +++---- src/core/parser/ParserStack.hpp | 41 +++++++++++++------ src/core/parser/Scope.cpp | 66 ++++++++++++++++++------------- src/core/parser/Scope.hpp | 66 ++++++++++++++++++++++--------- src/plugins/xml/XmlParser.cpp | 75 ++++++++++++++++++++++++----------- test/plugins/xml/XmlParserTest.cpp | 9 +++-- 8 files changed, 281 insertions(+), 119 deletions(-) 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 names; - bool res = true; - for (Handle 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::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 Typesystem::createStructType(const std::string &name) +{ + Rooted 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; + protected: /** @@ -383,15 +380,26 @@ protected: bool doValidate(Logger &logger) const override; public: + /** + * Reference to the actual type of the attribute. + */ + Owned 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. @@ -429,6 +437,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, Logger &logger); + /** * Returns a reference to the type descriptor holding the type of the * attribute. @@ -859,6 +911,12 @@ private: */ NodeVector constants; +protected: + + void doResolve(ResolutionState &state) override; + + bool doValidate(Logger &logger) const override; + public: /** * Constructor of the Typesystem class. @@ -867,10 +925,19 @@ 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 createStructType(const std::string &name); + /** * Adds the given type to the to the type list. * 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) 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 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 @@ -85,6 +85,11 @@ struct HandlerData { */ const bool isChild; + /** + * Current source code location. + */ + const SourceLocation location; + /** * Constructor of the HandlerData class. * @@ -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 ScopeBase::resolve(const std::vector &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 ScopeBase::resolve(const std::vector &path, /* Class DeferredResolution */ -DeferredResolution::DeferredResolution( - const NodeVector &nodes, const std::vector &path, - const RttiType &type, std::function)> resultCallback) - : scope(nodes), resultCallback(resultCallback), path(path), type(type) +DeferredResolution::DeferredResolution(const NodeVector &nodes, + const std::vector &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 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 Scope::getRoot() const { return nodes.front(); } Rooted Scope::getLeaf() { return nodes.back(); } bool Scope::resolve(const std::vector &path, const RttiType &type, - Logger &logger, - std::function()> imposterCallback, - std::function)> resultCallback) + Logger &logger, ResolutionImposterCallback imposterCallback, + ResolutionResultCallback resultCallback, + const SourceLocation &location) { - Rooted 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 &path, const RttiType &type, - Logger &logger, - std::function)> successCallback) + Logger &logger, ResolutionResultCallback resultCallback, + const SourceLocation &location) { Rooted 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 @@ -42,6 +42,18 @@ namespace parser { // Forward declaration class Scope; +/** + * 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. + */ +using ResolutionResultCallback = std::function, 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 @@ -148,7 +160,7 @@ private: /** * Callback function to be called when an element is successfully resolved. */ - std::function)> resultCallback; + ResolutionResultCallback resultCallback; public: /** @@ -161,6 +173,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 &nodes, const std::vector &path, const RttiType &type, - std::function)> 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 &path, const RttiType &type, - Logger &logger, std::function()> imposterCallback, - std::function)> 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 &path, const RttiType &type, - Logger &logger, - std::function)> 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 bool resolve(const std::vector &path, Logger &logger, std::function()> imposterCallback, - std::function)> successCallback) + std::function, Logger&)> resultCallback, + const SourceLocation &location = SourceLocation{}) { return resolve( path, typeOf(), logger, [imposterCallback]() -> Rooted { return imposterCallback(); }, - [successCallback](Handle node) { - successCallback(node.cast()); - }); + [resultCallback](Handle node, Logger &logger) { + resultCallback(node.cast(), 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 bool resolve(const std::vector &path, Logger &logger, - std::function)> resultCallback) + std::function, Logger&)> resultCallback, + const SourceLocation &location = SourceLocation{}) { return resolve(path, typeOf(), logger, - [resultCallback](Handle node) { - resultCallback(node.cast()); - }); + [resultCallback](Handle node, Logger &logger) { + resultCallback(node.cast(), 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 #include +#include #include #include @@ -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 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 = scope().getLeaf().cast(); + Rooted structType = typesystem->createStructType(name); + + // Try to resolve the parent type and set it as parent structure + if (!parent.empty()) { + scope().resolve(Utils::split(parent, '.'), logger(), + [structType](Handle 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) {} + 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 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 XML_HANDLERS{ @@ -142,7 +170,7 @@ static const std::multimap 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(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(p); - syncLoggerPosition(parser); + SourceLocation loc = syncLoggerPosition(parser); ParserStack *stack = static_cast(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 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/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 = " \n" " \n" " \n" - " \n" - " \n" - " \n" " \n" " \n" " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" " \n" " \n" " \n" -- cgit v1.2.3