diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/Exceptions.cpp | 7 | ||||
-rw-r--r-- | src/core/Logger.cpp | 2 | ||||
-rw-r--r-- | src/core/Logger.hpp | 90 | ||||
-rw-r--r-- | src/core/Registry.cpp | 47 | ||||
-rw-r--r-- | src/core/Registry.hpp | 51 | ||||
-rw-r--r-- | src/core/Utils.cpp | 22 | ||||
-rw-r--r-- | src/core/Utils.hpp | 52 | ||||
-rw-r--r-- | src/core/parser/Parser.cpp (renamed from src/core/Parser.cpp) | 9 | ||||
-rw-r--r-- | src/core/parser/Parser.hpp (renamed from src/core/Parser.hpp) | 85 | ||||
-rw-r--r-- | src/core/parser/ParserStack.cpp | 150 | ||||
-rw-r--r-- | src/core/parser/ParserStack.hpp | 341 | ||||
-rw-r--r-- | src/core/parser/Scope.cpp | 26 | ||||
-rw-r--r-- | src/core/parser/Scope.hpp | 172 | ||||
-rw-r--r-- | src/core/parser/XmlParser.cpp | 134 | ||||
-rw-r--r-- | src/core/parser/XmlParser.hpp | 63 | ||||
-rw-r--r-- | src/core/variant/Variant.cpp | 155 | ||||
-rw-r--r-- | src/core/variant/Variant.hpp | 693 |
17 files changed, 1859 insertions, 240 deletions
diff --git a/src/core/Exceptions.cpp b/src/core/Exceptions.cpp index 92d9293..735dac6 100644 --- a/src/core/Exceptions.cpp +++ b/src/core/Exceptions.cpp @@ -29,16 +29,17 @@ std::string LoggableException::formatMessage(const std::string &msg, int column, bool fatal) { std::stringstream ss; + ss << "error "; if (!file.empty()) { ss << "while processing \"" << file << "\" "; } if (line >= 0) { - ss << "at line: " << line << " "; + ss << "at line " << line << ", "; if (column >= 0) { - ss << "col: " << column << " "; + ss << "column " << column << " "; } } - ss << "message: " << msg; + ss << "with message: " << msg; return ss.str(); } } diff --git a/src/core/Logger.cpp b/src/core/Logger.cpp index 1a3b6c6..17f55a6 100644 --- a/src/core/Logger.cpp +++ b/src/core/Logger.cpp @@ -149,7 +149,7 @@ void TerminalLogger::process(const Message &msg) os << t.color(Terminal::RED, true) << "error: "; break; case Severity::FATAL_ERROR: - os << t.color(Terminal::RED, true) << "error: "; + os << t.color(Terminal::RED, true) << "fatal: "; break; } os << t.reset(); diff --git a/src/core/Logger.hpp b/src/core/Logger.hpp index 260d010..a30374c 100644 --- a/src/core/Logger.hpp +++ b/src/core/Logger.hpp @@ -256,6 +256,22 @@ public: * the file name stack. * * @param msg is the actual log message. + * @param file is the name of the file the message refers to. May be empty. + * @param line is the line in the above file at which the error occured. + * Ignored if negative. + * @param column is the column in the above file at which the error occured. + * Ignored if negative. + */ + void debug(const std::string &msg, const std::string &file, int line = -1, int column = -1) + { + log(Severity::DEBUG, msg, file, line, column); + } + + /** + * Logs a debug message. The file name is set to the topmost file name on + * the file name stack. + * + * @param msg is the actual log message. * @param line is the line in the above file at which the error occured. * Ignored if negative. * @param column is the column in the above file at which the error occured. @@ -263,7 +279,23 @@ public: */ void debug(const std::string &msg, int line = -1, int column = -1) { - log(Severity::DEBUG, msg, line, column); + debug(msg, currentFilename(), line, column); + } + + /** + * Logs a note. The file name is set to the topmost file name on + * the file name stack. + * + * @param msg is the actual log message. + * @param file is the name of the file the message refers to. May be empty. + * @param line is the line in the above file at which the error occured. + * Ignored if negative. + * @param column is the column in the above file at which the error occured. + * Ignored if negative. + */ + void note(const std::string &msg, const std::string &file, int line = -1, int column = -1) + { + log(Severity::NOTE, msg, file, line, column); } /** @@ -278,7 +310,23 @@ public: */ void note(const std::string &msg, int line = -1, int column = -1) { - log(Severity::NOTE, msg, line, column); + note(msg, currentFilename(), line, column); + } + + /** + * Logs a warning. The file name is set to the topmost file name on + * the file name stack. + * + * @param msg is the actual log message. + * @param file is the name of the file the message refers to. May be empty. + * @param line is the line in the above file at which the error occured. + * Ignored if negative. + * @param column is the column in the above file at which the error occured. + * Ignored if negative. + */ + void warning(const std::string &msg, const std::string &file, int line = -1, int column = -1) + { + log(Severity::WARNING, msg, file, line, column); } /** @@ -293,7 +341,23 @@ public: */ void warning(const std::string &msg, int line = -1, int column = -1) { - log(Severity::WARNING, msg, line, column); + warning(msg, currentFilename(), line, column); + } + + /** + * Logs an error message. The file name is set to the topmost file name on + * the file name stack. + * + * @param msg is the actual log message. + * @param file is the name of the file the message refers to. May be empty. + * @param line is the line in the above file at which the error occured. + * Ignored if negative. + * @param column is the column in the above file at which the error occured. + * Ignored if negative. + */ + void error(const std::string &msg, const std::string &file, int line = -1, int column = -1) + { + log(Severity::ERROR, msg, file, line, column); } /** @@ -308,7 +372,23 @@ public: */ void error(const std::string &msg, int line = -1, int column = -1) { - log(Severity::ERROR, msg, line, column); + error(msg, currentFilename(), line, column); + } + + /** + * Logs a fatal error. The file name is set to the topmost file name on + * the file name stack. + * + * @param msg is the actual log message. + * @param file is the name of the file the message refers to. May be empty. + * @param line is the line in the above file at which the error occured. + * Ignored if negative. + * @param column is the column in the above file at which the error occured. + * Ignored if negative. + */ + void fatalError(const std::string &msg, const std::string &file, int line = -1, int column = -1) + { + log(Severity::FATAL_ERROR, msg, file, line, column); } /** @@ -323,7 +403,7 @@ public: */ void fatalError(const std::string &msg, int line = -1, int column = -1) { - log(Severity::FATAL_ERROR, msg, line, column); + fatalError(msg, currentFilename(), line, column); } /** diff --git a/src/core/Registry.cpp b/src/core/Registry.cpp new file mode 100644 index 0000000..1961b35 --- /dev/null +++ b/src/core/Registry.cpp @@ -0,0 +1,47 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <core/Logger.hpp> + +#include <core/parser/Parser.hpp> + +namespace ousia { + +using namespace parser; + +/* Class Registry */ + +void Registry::registerParser(parser::Parser *parser) +{ + parsers.push_back(parser); + for (const auto &mime : parser.mimetypes()) { + parserMimetypes.insert(std::make_pair(mime, parser)); + } +} + +Parser* Registry::getParserForMimetype(const std::string &mimetype) +{ + const auto it = parserMimetypes.find(mimetype); + if (it != parserMimetypes.end()) { + return it->second; + } + return nullptr; +} + +} + diff --git a/src/core/Registry.hpp b/src/core/Registry.hpp new file mode 100644 index 0000000..235e427 --- /dev/null +++ b/src/core/Registry.hpp @@ -0,0 +1,51 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _OUSIA_REGISTRY_HPP_ +#define _OUSIA_REGISTRY_HPP_ + +#include <map> +#include <vector> + +namespace ousia { + +// TODO: Add support for ScriptEngine type + +class Logger; + +namespace parser { +class Parser; +} + +class Registry { +private: + Logger &logger; + std::vector<parser::Parser*> parsers; + std::map<std::string, parser::Parser*> parserMimetypes; + +public: + Registry(Logger &logger) : logger(logger) {} + + void registerParser(parser::Parser *parser); + + parser::Parser *getParserForMimetype(std::string mimetype); +}; +} + +#endif /* _OUSIA_REGISTRY_HPP_ */ + diff --git a/src/core/Utils.cpp b/src/core/Utils.cpp index 184fdd0..c460ed4 100644 --- a/src/core/Utils.cpp +++ b/src/core/Utils.cpp @@ -16,10 +16,31 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <algorithm> +#include <limits> + #include "Utils.hpp" namespace ousia { +std::string Utils::trim(const std::string &s) +{ + size_t firstNonWhitespace = std::numeric_limits<size_t>::max(); + size_t lastNonWhitespace = 0; + for (size_t i = 0; i < s.size(); i++) { + if (!isWhitespace(s[i])) { + firstNonWhitespace = std::min(i, firstNonWhitespace); + lastNonWhitespace = std::max(i, lastNonWhitespace); + } + } + + if (firstNonWhitespace < lastNonWhitespace) { + return s.substr(firstNonWhitespace, + lastNonWhitespace - firstNonWhitespace + 1); + } + return std::string{}; +} + bool Utils::isIdentifier(const std::string &name) { bool first = true; @@ -34,6 +55,5 @@ bool Utils::isIdentifier(const std::string &name) } return true; } - } diff --git a/src/core/Utils.hpp b/src/core/Utils.hpp index 2fcd794..14bd7b4 100644 --- a/src/core/Utils.hpp +++ b/src/core/Utils.hpp @@ -16,18 +16,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ - #ifndef _OUSIA_UTILS_H_ #define _OUSIA_UTILS_H_ +#include <sstream> #include <string> namespace ousia { class Utils { - public: - /** * Returns true if the given character is in [A-Za-z] */ @@ -39,10 +37,7 @@ public: /** * Returns true if the given character is in [0-9] */ - static bool isNumeric(const char c) - { - return (c >= '0') && (c <= '9'); - } + static bool isNumeric(const char c) { return (c >= '0') && (c <= '9'); } /** * Returns true if the given character is in [A-Za-z0-9] @@ -57,8 +52,49 @@ public: */ static bool isIdentifier(const std::string &name); -}; + /** + * Returns true if the given character is a whitespace character. + */ + static bool isWhitespace(const char c) + { + return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'); + } + /** + * Removes whitespace at the beginning and the end of the given string. + */ + static std::string trim(const std::string &s); + + /** + * Turns the elements of a collection into a string separated by the + * given delimiter. + * + * @param es is an iterable container of elements that can be appended to an + * output stream (the << operator must be implemented). + * @param delim is the delimiter that should be used to separate the items. + * @param start is a character sequence that should be prepended to the + * result. + * @param end is a character sequence that should be appended to the result. + */ + template <class T> + static std::string join(T es, const std::string &delim, + const std::string &start = "", + const std::string &end = "") + { + std::stringstream res; + bool first = true; + res << start; + for (const auto &e : es) { + if (!first) { + res << delim; + } + res << e; + first = false; + } + res << end; + return res.str(); + } +}; } #endif /* _OUSIA_UTILS_H_ */ diff --git a/src/core/Parser.cpp b/src/core/parser/Parser.cpp index bc98ac0..23fd9b7 100644 --- a/src/core/Parser.cpp +++ b/src/core/parser/Parser.cpp @@ -21,12 +21,13 @@ #include "Parser.hpp" namespace ousia { +namespace parser { -Rooted<Node> Parser::parse(const std::string &str, Handle<Node> context, Logger &logger) +Rooted<Node> Parser::parse(const std::string &str, ParserContext &ctx) { - std::istringstream is(str); - return parse(is, context, logger); + std::istringstream is{str}; + return parse(is, ctx); +} } - } diff --git a/src/core/Parser.hpp b/src/core/parser/Parser.hpp index 74a1988..fa5dd49 100644 --- a/src/core/Parser.hpp +++ b/src/core/parser/Parser.hpp @@ -19,7 +19,7 @@ /** * @file Parser.hpp * - * Contains the abstract "Parser" class. Parsers are objects capable of reading + * Contains the abstract Parser class. Parsers are objects capable of reading * a certain file format and transforming it into a node. * * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) @@ -32,11 +32,15 @@ #include <set> #include <string> -#include "Exceptions.hpp" -#include "Node.hpp" -#include "Logger.hpp" +#include <core/Exceptions.hpp> +#include <core/Node.hpp> +#include <core/Logger.hpp> +#include <core/Registry.hpp> + +#include "Scope.hpp" namespace ousia { +namespace parser { // TODO: Implement a proper Mimetype class @@ -49,15 +53,61 @@ public: }; /** + * Struct containing the objects that are passed to a parser instance. + */ +struct ParserContext { + /** + * Reference to the Scope instance that should be used within the parser. + */ + Scope &scope; + + /** + * Reference to the Registry instance that should be used within the parser. + */ + Registry ®istry; + + /** + * Reference to the Logger the parser should log any messages to. + */ + Logger &logger; + + /** + * Constructor of the ParserContext class. + * + * @param scope is a reference to the Scope instance that should be used to + * lookup names. + * @param registry is a reference at the Registry class, which allows to + * obtain references at parsers for other formats or script engine + * implementations. + * @param logger is a reference to the Logger instance that should be used + * to log error messages and warnings that occur while parsing the document. + */ + ParserContext(Scope &scope, Registry ®istry, Logger &logger) + : scope(scope), registry(registry), logger(logger){}; +}; + +struct StandaloneParserContext : public ParserContext { +private: + Logger logger; + Scope scope; + Registry registry; + +public: + StandaloneParserContext() + : ParserContext(scope, registry, logger), + scope(nullptr), + registry(logger){}; +}; + +/** * Abstract parser class. This class builds the basic interface that should be * used by any parser which reads data from an input stream and transforms it * into an Ousía node graph. */ class Parser { public: - - Parser() {}; - Parser(const Parser&) = delete; + Parser(){}; + Parser(const Parser &) = delete; /** * Returns a set containing all mime types supported by the parser. The mime @@ -78,18 +128,14 @@ public: * derived classes. * * @param is is a reference to the input stream that should be parsed. - * @param context defines the context in which the input stream should be - * parsed. The context represents the scope from which element names should - * be looked up. - * @param logger is a reference to the Logger instance that should be used - * to log error messages and warnings that occur while parsing the document. + * @param ctx is a reference to the context that should be used while + * parsing the document. * @return a reference to the node representing the subgraph that has been * created. The resulting node may point at not yet resolved entities, the * calling code will try to resolve these. If no valid node can be produced, * a corresponding LoggableException must be thrown by the parser. */ - virtual Rooted<Node> parse(std::istream &is, Handle<Node> context, - Logger &logger) = 0; + virtual Rooted<Node> parse(std::istream &is, ParserContext &ctx) = 0; /** * Parses the given string and returns a corresponding node for @@ -97,20 +143,17 @@ public: * derived classes. * * @param str is the string that should be parsed. - * @param context defines the context in which the input stream should be - * parsed. The context represents the scope from which element names should - * be looked up. - * @param logger is a reference to the Logger instance that should be used - * to log error messages and warnings that occur while parsing the document. + * @param ctx is a reference to the context that should be used while + * parsing the document. * @return a reference to the node representing the subgraph that has been * created. The resulting node may point at not yet resolved entities, the * calling code will try to resolve these. If no valid node can be produced, * a corresponding ParserException must be thrown by the parser. */ - Rooted<Node> parse(const std::string &str, Handle<Node> context, - Logger &logger); + Rooted<Node> parse(const std::string &str, ParserContext &ctx); }; } +} #endif /* _OUSIA_PARSER_HPP_ */ diff --git a/src/core/parser/ParserStack.cpp b/src/core/parser/ParserStack.cpp new file mode 100644 index 0000000..dca7f35 --- /dev/null +++ b/src/core/parser/ParserStack.cpp @@ -0,0 +1,150 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sstream> + +#include "ParserStack.hpp" + +#include <core/Utils.hpp> +#include <core/Exceptions.hpp> + +namespace ousia { +namespace parser { + +/* Class Handler */ + +void Handler::data(const std::string &data, int field) +{ + for (auto &c : data) { + if (!Utils::isWhitespace(c)) { + throw LoggableException{"No data allowed here."}; + } + } +} + +/* Class HandlerDescriptor */ + +HandlerInstance HandlerDescriptor::create(const ParserContext &ctx, + std::string name, State parentState, + bool isChild, + const Variant &args) const +{ + Handler *h = ctor(ctx, name, targetState, parentState, isChild); + h->start(args); + return HandlerInstance(h, this); +} + +/* Class ParserStack */ + +/** + * Returns an Exception that should be thrown when a currently invalid command + * is thrown. + */ +static LoggableException invalidCommand(const std::string &name, + const std::set<std::string> &expected) +{ + if (expected.empty()) { + return LoggableException{ + std::string{"No nested elements allowed, but got \""} + name + + std::string{"\""}}; + } else { + return LoggableException{ + std::string{"Expected "} + + (expected.size() == 1 ? std::string{"\""} + : std::string{"one of \""}) + + Utils::join(expected, "\", \"") + std::string{"\", but got \""} + name + + std::string{"\""}}; + } +} + +std::set<std::string> ParserStack::expectedCommands(State state) +{ + std::set<std::string> res; + for (const auto &v : handlers) { + if (v.second.parentStates.count(state)) { + res.insert(v.first); + } + } + return res; +} + +void ParserStack::start(std::string name, const Variant &args) +{ + // Fetch the current handler and the current state + const HandlerInstance *h = stack.empty() ? nullptr : &stack.top(); + const State curState = currentState(); + bool isChild = false; + + // Fetch the correct Handler descriptor for this + const HandlerDescriptor *descr = nullptr; + auto range = handlers.equal_range(name); + for (auto it = range.first; it != range.second; it++) { + const std::set<State> &parentStates = it->second.parentStates; + if (parentStates.count(curState) || parentStates.count(STATE_ALL)) { + descr = &(it->second); + break; + } + } + if (!descr && currentArbitraryChildren()) { + isChild = true; + descr = h->descr; + } + + // No descriptor found, throw an exception. + if (!descr) { + throw invalidCommand(name, expectedCommands(curState)); + } + + // Instantiate the handler and call its start function + stack.emplace(descr->create(ctx, name, curState, isChild, args)); +} + +void ParserStack::end() +{ + // Check whether the current command could be ended + if (stack.empty()) { + throw LoggableException{"No command to end."}; + } + + // Remove the current HandlerInstance from the stack + HandlerInstance inst{stack.top()}; + stack.pop(); + + // Call the end function of the last Handler + inst.handler->end(); + + // Call the "child" function of the parent Handler in the stack + // (if one exists). + if (!stack.empty()) { + stack.top().handler->child(inst.handler); + } +} + +void ParserStack::data(const std::string &data, int field) +{ + // Check whether there is any command the data can be sent to + if (stack.empty()) { + throw LoggableException{"No command to receive data."}; + } + + // Pass the data to the current Handler instance + stack.top().handler->data(data, field); +} +} +} + diff --git a/src/core/parser/ParserStack.hpp b/src/core/parser/ParserStack.hpp new file mode 100644 index 0000000..c5ed4e4 --- /dev/null +++ b/src/core/parser/ParserStack.hpp @@ -0,0 +1,341 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +/** + * @file ParserStack.hpp + * + * Helper classes for document or description parsers. Contains the ParserStack + * class, which is an pushdown automaton responsible for accepting commands in + * the correct order and calling specified handlers. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_PARSER_STACK_HPP_ +#define _OUSIA_PARSER_STACK_HPP_ + +#include <cstdint> + +#include <map> +#include <memory> +#include <set> +#include <stack> +#include <vector> + +#include <core/variant/Variant.hpp> + +#include "Parser.hpp" + +namespace ousia { +namespace parser { + +/** + * The State type alias is used to + */ +using State = int16_t; + +static const State STATE_ALL = -2; +static const State STATE_NONE = -1; + +/** + * The handler class provides a context for handling an XML tag. It has to be + * overridden and registered in the StateStack class to form handlers for + * concrete XML tags. + */ +class Handler { +private: + Rooted<Node> node; + +protected: + void setNode(Handle<Node> node) { this->node = node; } + +public: + /** + * Reference to the ParserContext instance that should be used to resolve + * references to nodes in the Graph. + */ + const ParserContext &ctx; + + /** + * Contains the name of the tag that is being handled. + */ + const std::string name; + + /** + * Contains the current state of the state machine. + */ + const State state; + + /** + * Contains the state of the state machine when the parent node was handled. + */ + const State parentState; + + /** + * Set to true if the tag that is being handled is not the tag that was + * specified in the state machine but a child tag of that tag. + */ + const bool isChild; + + /** + * Constructor of the Handler class. + * + * @param ctx is the parser context the handler should be executed in. + * @param name is the name of the string. + * @param state is the state this handler was called for. + * @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. + */ + Handler(const ParserContext &ctx, std::string name, State state, + State parentState, bool isChild) + : ctx(ctx), + name(std::move(name)), + state(state), + parentState(parentState), + isChild(isChild){}; + + /** + * Virtual destructor. + */ + virtual ~Handler(){}; + + /** + * Returns the node instance that was created by the handler. + * + * @return the Node instance created by the handler. May be nullptr if no + * Node was created. + */ + Rooted<Node> getNode() { return node; } + + /** + * Called when the command that was specified in the constructor is + * instanciated. + * + * @param args is a map from strings to variants (argument name and value). + */ + virtual void start(const Variant &args) = 0; + + /** + * Called whenever the command for which this handler + */ + virtual void end() = 0; + + /** + * Called whenever raw data (int the form of a string) is available for the + * Handler instance. In the default handler an exception is raised if the + * received data contains non-whitespace characters. + * + * @param data is a pointer at the character data that is available for the + * Handler instance. + * @param field is the field number (the interpretation of this value + * depends on the format that is being parsed). + */ + virtual void data(const std::string &data, int field); + + /** + * Called whenever a direct child element was created and has ended. + * + * @param handler is a reference at the child Handler instance. + */ + virtual void child(std::shared_ptr<Handler> handler){}; +}; + +/** + * HandlerConstructor is a function pointer type used to create concrete + * instances of the Handler class. + */ +using HandlerConstructor = Handler *(*)(const ParserContext &ctx, + std::string name, State state, + State parentState, bool isChild); + +struct HandlerDescriptor; + +/** + * Used internlly by StateStack to store Handler instances and parameters + * from HandlerDescriptor that are not stored in the Handler instance + * itself. Instances of the HandlerInstance class can be created using the + * HandlerDescriptor "create" method. + */ +struct HandlerInstance { + /** + * Pointer at the actual handler instance. + */ + std::shared_ptr<Handler> handler; + + const HandlerDescriptor *descr; + + HandlerInstance(Handler *handler, const HandlerDescriptor *descr) + : handler(handler), descr(descr) + { + } +}; + +/** + * Used internally by StateStack to store the pushdown automaton + * description. + */ +struct HandlerDescriptor { + /** + * The valid parent states. + */ + const std::set<State> parentStates; + + /** + * Pointer at a function which creates a new concrete Handler instance. + */ + const HandlerConstructor ctor; + + /** + * The target state for the registered handler. + */ + const State targetState; + + /** + * Set to true if this handler instance allows arbitrary children as + * tags. + */ + const bool arbitraryChildren; + + HandlerDescriptor(std::set<State> parentStates, HandlerConstructor ctor, + State targetState, bool arbitraryChildren = false) + : parentStates(std::move(parentStates)), + ctor(ctor), + targetState(targetState), + arbitraryChildren(arbitraryChildren) + { + } + + /** + * Creates an instance of the concrete Handler class represented by the + * HandlerDescriptor and calls its start function. + */ + HandlerInstance create(const ParserContext &ctx, std::string name, + State parentState, bool isChild, + const Variant &args) const; +}; + +/** + * The ParserStack class is a pushdown automaton responsible for turning a + * command stream into a tree of Node instances. + */ +class ParserStack { +private: + /** + * Reference at the parser context. + */ + const ParserContext &ctx; + + /** + * User specified data that will be passed to all handlers. + */ + void *userData; + + /** + * Map containing all registered command names and the corresponding + * handler + * descriptor. + */ + const std::multimap<std::string, HandlerDescriptor> &handlers; + + /** + * Internal stack used for managing the currently active Handler instances. + */ + std::stack<HandlerInstance> stack; + + /** + * Used internally to get all expected command names for the given state + * (does not work if the current Handler instance allows arbitrary + * children). This function is used to build error messages. + * + * @param state is the state for which all expected command names should be + * returned. + */ + std::set<std::string> expectedCommands(State state); + +public: + /** + * Creates a new instance of the ParserStack class. + * + * @param handlers is a map containing the command names and the + * corresponding HandlerDescriptor instances. + */ + ParserStack(const ParserContext &ctx, + const std::multimap<std::string, HandlerDescriptor> &handlers) + : ctx(ctx), handlers(handlers){}; + + /** + * Returns the state the ParserStack instance currently is in. + * + * @return the state of the currently active Handler instance or STATE_NONE + * if no handler is on the stack. + */ + State currentState() + { + return stack.empty() ? STATE_NONE : stack.top().handler->state; + } + + /** + * Returns the command name that is currently being handled. + * + * @return the name of the command currently being handled by the active + * Handler instance or an empty string if no handler is currently active. + */ + std::string currentName() + { + return stack.empty() ? std::string{} : stack.top().handler->name; + } + + /** + * Returns whether the current command handler allows arbitrary children. + * + * @return true if the handler allows arbitrary children, false otherwise. + */ + bool currentArbitraryChildren() + { + return stack.empty() ? false : stack.top().descr->arbitraryChildren; + } + + /** + * 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). + */ + void start(std::string name, const Variant &args); + + /** + * Function called whenever a command ends. + */ + void end(); + + /** + * Function that should be called whenever data is available for the + * command. + * + * @param data is the data that should be passed to the handler. + * @param field is the field number (the interpretation of this value + * depends on the format that is being parsed). + */ + void data(const std::string &data, int field = 0); +}; +} +} + +#endif /* _OUSIA_PARSER_STACK_HPP_ */ + diff --git a/src/core/parser/Scope.cpp b/src/core/parser/Scope.cpp new file mode 100644 index 0000000..a60ade0 --- /dev/null +++ b/src/core/parser/Scope.cpp @@ -0,0 +1,26 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include "Scope.hpp" + +namespace ousia { +namespace parser { + + +} +} diff --git a/src/core/parser/Scope.hpp b/src/core/parser/Scope.hpp new file mode 100644 index 0000000..9c5504f --- /dev/null +++ b/src/core/parser/Scope.hpp @@ -0,0 +1,172 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _OUSIA_PARSER_SCOPE_H_ +#define _OUSIA_PARSER_SCOPE_H_ + +#include <deque> + +#include <core/Node.hpp> + +/** + * @file Scope.hpp + * + * Contains the Scope class used for resolving references based on the current + * parser state. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +namespace ousia { +namespace parser { + +class Scope; + +/** + * The ScopedScope 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 { +private: + /** + * Reference at the backing scope instance. + */ + Scope *scope; + +public: + /** + * Creates a new ScopedScope instance. + * + * @param scope is the backing Scope instance. + * @param node is the Node instance that should be poped onto the stack of + * the Scope instance. + */ + ScopedScope(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; + + /** + * Move constructor of the ScopedScope class. + */ + ScopedScope(ScopedScope &&); + + /** + * Provides access at the underlying Scope instance. + */ + Scope *operator->() { return scope; } + + /** + * Provides access at the underlying Scope instance. + */ + Scope &operator*() { return *scope; } +}; + +/** + * 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 { +private: + std::deque<Rooted<Node>> nodes; + +public: + /** + * Constructor of the Scope class. + * + * @param rootNode is the top-most Node from which elements can be looked + * up. + */ + Scope(Handle<Node> rootNode) { nodes.push_back(rootNode); } + + /** + * Returns a reference at the Manager instance all nodes belong to. + */ + Manager &getManager() { return getRoot()->getManager(); } + + /** + * 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); } + + /** + * Removes the last pushed node from the scope. + */ + void pop() { nodes.pop_back(); } + + /** + * 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}; } + + /** + * Returns the top-most Node instance in the Scope hirarchy. + * + * @return a reference at the root node. + */ + Rooted<Node> getRoot() { return nodes.front(); } + + /** + * Returns the bottom-most Node instance in the Scope hirarchy, e.g. the + * node that was pushed last onto the stack. + * + * @return a reference at the leaf node. + */ + Rooted<Node> getLeaf() { return nodes.back(); } +}; + +/* Class ScopedScope -- inline declaration of some methods */ + +inline ScopedScope::ScopedScope(Scope *scope, Handle<Node> node) : scope(scope) +{ + scope->push(node); +} + +inline ScopedScope::~ScopedScope() +{ + if (scope) { + scope->pop(); + } +} + +inline ScopedScope::ScopedScope(ScopedScope &&s) +{ + scope = s.scope; + s.scope = nullptr; +} +} +} + +#endif /* _OUSIA_PARSER_SCOPE_H_ */ + diff --git a/src/core/parser/XmlParser.cpp b/src/core/parser/XmlParser.cpp deleted file mode 100644 index f9bb43e..0000000 --- a/src/core/parser/XmlParser.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - 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 <http://www.gnu.org/licenses/>. -*/ - -#include <expat.h> - -#include "XmlParser.hpp" - -namespace ousia { - -/** - * The XmlParserData struct holds all information relevant to the expat callback - * functions. - */ -struct XmlParserData { - Rooted<Node> context; - Logger &logger; - - XmlParserData(Handle<Node> context, Logger &logger) - : context(context), logger(logger) - { - } -}; - -/** - * Wrapper class around the XML_Parser pointer which safely frees it whenever - * the scope is left (e.g. because an exception was thrown). - */ -class ScopedExpatXmlParser { -private: - /** - * Internal pointer to the XML_Parser instance. - */ - XML_Parser parser; - -public: - /** - * Constructor of the ScopedExpatXmlParser class. Calls XML_ParserCreateNS - * from the expat library. Throws a parser exception if the XML parser - * cannot be initialized. - * - * @param encoding is the protocol-defined encoding passed to expat (or - * nullptr if expat should determine the encoding by itself). - * @param namespaceSeparator is the separator used to separate the namespace - * components in the node name given by expat. - */ - ScopedExpatXmlParser(const XML_Char *encoding, XML_Char namespaceSeparator) - : parser(nullptr) - { - parser = XML_ParserCreateNS("UTF-8", ':'); - if (!parser) { - throw ParserException{ - "Internal error: Could not create expat XML parser!"}; - } - } - - /** - * Destuctor of the ScopedExpatXmlParser, frees the XML parser instance. - */ - ~ScopedExpatXmlParser() - { - if (parser) { - XML_ParserFree(parser); - parser = nullptr; - } - } - - /** - * Returns the XML_Parser pointer. - */ - XML_Parser operator&() { return parser; } -}; - -std::set<std::string> XmlParser::mimetypes() -{ - return std::set<std::string>{{"text/vnd.ousia.oxm", "text/vnd.ousia.oxd"}}; -} - -Rooted<Node> XmlParser::parse(std::istream &is, Handle<Node> context, - Logger &logger) -{ - // Create the parser object - ScopedExpatXmlParser p{"UTF-8", ':'}; - - // Set the callback functions, provide a pointer to a XmlParserData instance - // as user data. - XmlParserData ctx{context, logger}; - - // Feed data into expat while there is data to process - const std::streamsize BUFFER_SIZE = 4096; // TODO: Move to own header? - while (true) { - // Fetch a buffer from expat for the input data - char *buf = static_cast<char *>(XML_GetBuffer(&p, BUFFER_SIZE)); - if (!buf) { - throw ParserException{"Internal error: XML parser out of memory!"}; - } - - // Read the input data from the stream - const std::streamsize bytesRead = is.read(buf, BUFFER_SIZE).gcount(); - - // Parse the data and handle any XML error - if (!XML_ParseBuffer(&p, bytesRead, bytesRead == 0)) { - const int line = XML_GetCurrentLineNumber(&p); - const int column = XML_GetCurrentColumnNumber(&p); - const XML_Error code = XML_GetErrorCode(&p); - const std::string msg = std::string{XML_ErrorString(code)}; - logger.error("XML: " + msg, line, column); - break; - } - - // Abort once there are no more bytes in the stream - if (bytesRead == 0) { - break; - } - } - - return nullptr; -} -} - diff --git a/src/core/parser/XmlParser.hpp b/src/core/parser/XmlParser.hpp deleted file mode 100644 index f6fb060..0000000 --- a/src/core/parser/XmlParser.hpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - 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 <http://www.gnu.org/licenses/>. -*/ - -/** - * @file XmlParser.hpp - * - * Contains the parser responsible for reading Ousía XML Documents (extension - * oxd) and Ousía XML Modules (extension oxm). - * - * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) - */ - -#ifndef _OUSIA_XML_PARSER_HPP_ -#define _OUSIA_XML_PARSER_HPP_ - -#include <core/Parser.hpp> - -namespace ousia { - -/** - * The XmlParser class implements parsing the various types of Ousía XML - * documents using the expat stream XML parser. - */ -class XmlParser : public Parser { -public: - /** - * Returns the mimetype supported by the XmlParser which is - * "text/vnd.ousia.oxm" and "text/vnd.ousia.oxd". - * - * @return a list containing the mimetype supported by Ousía. - */ - std::set<std::string> mimetypes() override; - - /** - * Parses the given input stream as XML file and returns the parsed - * top-level node. Throws - * - * @param is is the input stream that will be parsed. - */ - Rooted<Node> parse(std::istream &is, Handle<Node> context, - Logger &logger) override; - - using Parser::parse; -}; -} - -#endif /* _OUSIA_XML_PARSER_HPP_ */ - diff --git a/src/core/variant/Variant.cpp b/src/core/variant/Variant.cpp new file mode 100644 index 0000000..d33cd4f --- /dev/null +++ b/src/core/variant/Variant.cpp @@ -0,0 +1,155 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#include <sstream> + +#include <core/Utils.hpp> + +#include "Variant.hpp" + +namespace ousia { + +/* Class Variant::TypeException */ + +Variant::TypeException::TypeException(Type actualType, Type requestedType) + : OusiaException(std::string("Variant: Requested \"") + + Variant::getTypeName(requestedType) + + std::string("\" but is \"") + + Variant::getTypeName(actualType) + std::string("\"")), + actualType(actualType), + requestedType(requestedType) +{ +} + +/* Class Variant */ + +const char *Variant::getTypeName(Type type) +{ + switch (type) { + case Type::NULLPTR: + return "null"; + case Type::BOOL: + return "boolean"; + case Type::INT: + return "integer"; + case Type::DOUBLE: + return "double"; + case Type::STRING: + return "string"; + case Type::ARRAY: + return "array"; + case Type::MAP: + return "map"; + } + return "unknown"; +} + +Variant::boolType Variant::toBool() const +{ + switch (getType()) { + case Type::NULLPTR: + return false; + case Type::BOOL: + return asBool(); + case Type::INT: + return asInt() != 0; + case Type::DOUBLE: + return asDouble() != 0.0; + case Type::STRING: + return true; + case Type::ARRAY: + return true; + case Type::MAP: + return true; + } + return false; +} + +Variant::intType Variant::toInt() const +{ + switch (getType()) { + case Type::NULLPTR: + return 0; + case Type::BOOL: + return asBool() ? 1 : 0; + case Type::INT: + return asInt(); + case Type::DOUBLE: + return asDouble(); + case Type::STRING: + return 0; // TODO: Parse string as int + case Type::ARRAY: { + const arrayType &a = asArray(); + return (a.size() == 1) ? a[0].toInt() : 0; + } + case Type::MAP: + return 0; + } + return false; +} + +Variant::doubleType Variant::toDouble() const +{ + switch (getType()) { + case Type::NULLPTR: + return 0.0; + case Type::BOOL: + return asBool() ? 1.0 : 0.0; + case Type::INT: + return asInt(); + case Type::DOUBLE: + return asDouble(); + case Type::STRING: + return 0.0; // TODO: Parse string as double + case Type::ARRAY: { + const arrayType &a = asArray(); + return (a.size() == 1) ? a[0].toDouble() : 0; + } + case Type::MAP: + return 0; + } + return false; +} + +Variant::stringType Variant::toString(bool escape) const +{ + switch (getType()) { + case Type::NULLPTR: + return "null"; + case Type::BOOL: + return asBool() ? "true" : "false"; + case Type::INT: + return std::to_string(asInt()); + case Type::DOUBLE: + return std::to_string(asDouble()); + case Type::STRING: { + // TODO: Use proper serialization function + std::stringstream ss; + ss << "\"" << asString() << "\""; + return ss.str(); + } + case Type::ARRAY: + return Utils::join(asArray(), ", ", "[", "]"); + case Type::MAP: + return Utils::join(asMap(), ", ", "{", "}"); + } + return ""; +} + +} + diff --git a/src/core/variant/Variant.hpp b/src/core/variant/Variant.hpp new file mode 100644 index 0000000..d65e14a --- /dev/null +++ b/src/core/variant/Variant.hpp @@ -0,0 +1,693 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +/** + * @file Variant.hpp + * + * The Variant class is used to efficiently represent a variables of varying + * type. Variant instances are used to represent data given by the end user and + * to exchange information between the host application and the script clients. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_VARIANT_HPP_ +#define _OUSIA_VARIANT_HPP_ + +#include <cstdint> +#include <map> +#include <string> +#include <vector> +#include <ostream> + +// TODO: Use +// http://nikic.github.io/2012/02/02/Pointer-magic-for-efficient-dynamic-value-representations.html +// later (will allow to use 8 bytes for a variant) + +#include <core/Exceptions.hpp> + +namespace ousia { + +/** + * Instances of the Variant class represent any kind of data that is exchanged + * between the host application and the script engine. Variants are immutable. + */ +class Variant { +public: + /** + * Enum containing the possible types a variant may have. + */ + enum class Type : int16_t { + NULLPTR, + BOOL, + INT, + DOUBLE, + STRING, + ARRAY, + MAP + }; + + /** + * Exception thrown whenever a variant is accessed via a getter function + * that + * is not supported for the current variant type. + */ + class TypeException : public OusiaException { + private: + /** + * Internally used string holding the exception message. + */ + const std::string msg; + + public: + /** + * Contains the actual type of the variant. + */ + const Type actualType; + + /** + * Contains the requested type of the variant. + */ + const Type requestedType; + + /** + * Constructor of the TypeException. + * + * @param actualType describes the actual type of the variant. + * @param requestedType describes the type in which the variant was + * requested. + */ + TypeException(Type actualType, Type requestedType); + }; + + using boolType = bool; + using intType = int32_t; + using doubleType = double; + using stringType = std::string; + using arrayType = std::vector<Variant>; + using mapType = std::map<std::string, Variant>; + +private: + /** + * Used to store the actual type of the variant. + */ + Type type = Type::NULLPTR; + + /** + * Anonymous union containing the possible value of the variant. + */ + union { + /** + * The boolean value. Only valid if type is Type::BOOL. + */ + boolType boolVal; + /** + * The integer value. Only valid if type is Type::INT. + */ + intType intVal; + /** + * The number value. Only valid if type is Type::DOUBLE. + */ + doubleType doubleVal; + /** + * Pointer to the more complex data structures on the free store. Only + * valid if type is one of Type::STRING, Type::ARRAY, + * Type::MAP. + */ + void *ptrVal; + }; + + /** + * Internally used to convert the current pointer value to a reference of + * the specified type. + */ + template <typename T> + T &asObj(Type requestedType) const + { + const Type actualType = getType(); + if (actualType == requestedType) { + return *(static_cast<T *>(ptrVal)); + } + throw TypeException{actualType, requestedType}; + } + + /** + * Used internally to assign the value of another Variant instance to this + * instance. + * + * @param v is the Variant instance that should be copied to this instance. + */ + void copy(const Variant &v) + { + destroy(); + type = v.type; + switch (type) { + case Type::NULLPTR: + break; + case Type::BOOL: + boolVal = v.boolVal; + break; + case Type::INT: + intVal = v.intVal; + break; + case Type::DOUBLE: + doubleVal = v.doubleVal; + break; + case Type::STRING: + ptrVal = new stringType(v.asString()); + break; + case Type::ARRAY: + ptrVal = new arrayType(v.asArray()); + break; + case Type::MAP: + ptrVal = new mapType(v.asMap()); + break; + } + } + + /** + * Used internally to move the value of another Variant instance to this + * instance. + * + * @param v is the Variant instance that should be copied to this instance. + */ + void move(Variant &&v) + { + destroy(); + type = v.type; + switch (type) { + case Type::NULLPTR: + break; + case Type::BOOL: + boolVal = v.boolVal; + break; + case Type::INT: + intVal = v.intVal; + break; + case Type::DOUBLE: + doubleVal = v.doubleVal; + break; + case Type::STRING: + case Type::ARRAY: + case Type::MAP: + ptrVal = v.ptrVal; + v.ptrVal = nullptr; + break; + } + v.type = Type::NULLPTR; + } + + /** + * Used internally to destroy any value that was allocated on the heap. + */ + void destroy() + { + if (ptrVal) { + switch (type) { + case Type::STRING: + delete static_cast<stringType *>(ptrVal); + break; + case Type::ARRAY: + delete static_cast<arrayType *>(ptrVal); + break; + case Type::MAP: + delete static_cast<mapType *>(ptrVal); + break; + default: + break; + } + } + } + +public: + /** + * Copy constructor of the Variant class. + * + * @param v is the Variant instance that should be cloned. + */ + Variant(const Variant &v) : ptrVal(nullptr) { copy(v); } + + /** + * Move constructor of the Variant class. + * + * @param v is the reference to the Variant instance that should be moved, + * this instance is invalidated afterwards. + */ + Variant(Variant &&v) : ptrVal(nullptr) { move(std::move(v)); } + + /** + * Default constructor. Type is set to Type:null. + */ + Variant() : ptrVal(nullptr) { setNull(); } + + /** + * Default destructor, frees any memory that was allocated on the heap. + */ + ~Variant() { destroy(); } + + /** + * Constructor for null values. Initializes the variant as null value. + */ + Variant(std::nullptr_t) : ptrVal(nullptr) { setNull(); } + + /** + * Constructor for boolean values. + * + * @param b boolean value. + */ + Variant(boolType b) : ptrVal(nullptr) { setBool(b); } + + /** + * Constructor for integer values. + * + * @param i integer value. + */ + Variant(intType i) : ptrVal(nullptr) { setInt(i); } + + /** + * Constructor for double values. + * + * @param d double value. + */ + Variant(doubleType d) : ptrVal(nullptr) { setDouble(d); } + + /** + * Constructor for string values. The given string is copied and managed by + * the new Variant instance. + * + * @param s is a reference to a C-Style string used as string value. + */ + Variant(const char *s) : ptrVal(nullptr) { setString(s); } + + /** + * Constructor for array values. The given array is copied and managed by + * the new Variant instance. + * + * @param a is a reference to the array + */ + Variant(arrayType a) : ptrVal(nullptr) { setArray(std::move(a)); } + + /** + * Constructor for map values. The given map is copied and managed by the + * new Variant instance. + * + * @param m is a reference to the map. + */ + Variant(mapType m) : ptrVal(nullptr) { setMap(std::move(m)); } + + /** + * Copy assignment operator. + */ + Variant &operator=(const Variant &v) + { + copy(v); + return *this; + } + + /** + * Move assignment operator. + */ + Variant &operator=(Variant &&v) + { + move(std::move(v)); + return *this; + } + + /** + * Assign nullptr_t operator (allows to write Variant v = nullptr). + * + * @param p is an instance of std::nullptr_t. + */ + Variant &operator=(std::nullptr_t) + { + setNull(); + return *this; + } + + /** + * Assign a boolean value. + * + * @param b is the boolean value to which the variant should be set. + */ + Variant &operator=(boolType b) + { + setBool(b); + return *this; + } + + /** + * Assign an integer value. + * + * @param i is the integer value to which the variant should be set. + */ + Variant &operator=(intType i) + { + setInt(i); + return *this; + } + + /** + * Assign a double value. + * + * @param d is the double value to which the variant should be set. + */ + Variant &operator=(doubleType d) + { + setDouble(d); + return *this; + } + + /** + * Assign a zero terminated const char array. + * + * @param s is the zero terminated const char array to which the variant + * should be set. + */ + Variant &operator=(const char *s) + { + setString(s); + return *this; + } + + /** + * Checks whether this Variant instance represents the nullptr. + * + * @return true if the Variant instance represents the nullptr, false + * otherwise. + */ + bool isNull() const { return type == Type::NULLPTR; } + + /** + * Checks whether this Variant instance is a boolean. + * + * @return true if the Variant instance is a boolean, false otherwise. + */ + bool isBool() const { return type == Type::BOOL; } + + /** + * Checks whether this Variant instance is an integer. + * + * @return true if the Variant instance is an integer, false otherwise. + */ + bool isInt() const { return type == Type::INT; } + + /** + * Checks whether this Variant instance is a double. + * + * @return true if the Variant instance is a double, false otherwise. + */ + bool isDouble() const { return type == Type::DOUBLE; } + + /** + * Checks whether this Variant instance is a string. + * + * @return true if the Variant instance is a string, false otherwise. + */ + bool isString() const { return type == Type::STRING; } + + /** + * Checks whether this Variant instance is an array. + * + * @return true if the Variant instance is an array, false otherwise. + */ + bool isArray() const { return type == Type::ARRAY; } + + /** + * Checks whether this Variant instance is a map. + * + * @return true if the Variant instance is a map, false otherwise. + */ + bool isMap() const { return type == Type::MAP; } + + /** + * Returns the Variant boolean value. Performs no type conversion. Throws an + * exception if the underlying type is not a boolean. + * + * @return the boolean value. + */ + boolType asBool() const + { + if (isBool()) { + return boolVal; + } + throw TypeException{getType(), Type::BOOL}; + } + + /** + * Returns the Variant integer value. Performs no type conversion. Throws an + * exception if the underlying type is not an integer. + * + * @return the integer value. + */ + intType asInt() const + { + if (isInt()) { + return intVal; + } + throw TypeException{getType(), Type::INT}; + } + + /** + * Returns the Variant double value. Performs no type conversion. Throws an + * exception if the underlying type is not a double. + * + * @return the double value. + */ + doubleType asDouble() const + { + if (isDouble()) { + return doubleVal; + } + throw TypeException{getType(), Type::DOUBLE}; + } + + /** + * Returns a const reference to the string value. Performs no type + * conversion. Throws an exception if the underlying type is not a string. + * + * @return the string value as const reference. + */ + const stringType &asString() const + { + return asObj<stringType>(Type::STRING); + } + + /** + * Returns a const reference to the string value. Performs no type + * conversion. Throws an exception if the underlying type is not a string. + * + * @return the string value as reference. + */ + stringType &asString() { return asObj<stringType>(Type::STRING); } + + /** + * Returns a const reference to the array value. Performs no type + * conversion. Throws an exception if the underlying type is not an array. + * + * @return the array value as const reference. + */ + const arrayType &asArray() const { return asObj<arrayType>(Type::ARRAY); } + + /** + * Returns a const reference to the array value. Performs no type + * conversion. Throws an exception if the underlying type is not an array. + * + * @return the array value as reference. + */ + arrayType &asArray() { return asObj<arrayType>(Type::ARRAY); } + + /** + * Returns a const reference to the map value. Performs no type + * conversion. Throws an exception if the underlying type is not a map. + * + * @return the map value as const reference. + */ + const mapType &asMap() const { return asObj<mapType>(Type::MAP); } + + /** + * Returns a reference to the map value. Performs no type conversion. + * Throws an exception if the underlying type is not a map. + * + * @return the map value as reference. + */ + mapType &asMap() { return asObj<mapType>(Type::MAP); } + + /** + * Returns the value of the Variant as boolean, performs type conversion. + * + * @return the Variant value converted to a boolean value. + */ + boolType toBool() const; + + /** + * Returns the value of the Variant as integer, performs type conversion. + * + * @return the Variant value converted to an integer value. + */ + intType toInt() const; + + /** + * Returns the value of the Variant as double, performs type conversion. + * + * @return the Variant value converted to a double value. + */ + doubleType toDouble() const; + + /** + * Returns the value of the Variant as string, performs type conversion. + * + * @return the value of the variant as string. + * @param escape if set to true, adds double quotes to strings and escapes + * them properly (resulting in a more or less JSONesque output). + */ + stringType toString(bool escape = false) const; + + /** + * Sets the variant to null. + */ + void setNull() + { + destroy(); + type = Type::NULLPTR; + ptrVal = nullptr; + } + + /** + * Sets the variant to the given boolean value. + * + * @param b is the new boolean value. + */ + void setBool(boolType b) + { + destroy(); + type = Type::BOOL; + boolVal = b; + } + + /** + * Sets the variant to the given integer value. + * + * @param i is the new integer value. + */ + void setInt(intType i) + { + destroy(); + type = Type::INT; + intVal = i; + } + + /** + * Sets the variant to the given double value. + * + * @param d is the new double value. + */ + void setDouble(doubleType d) + { + destroy(); + type = Type::DOUBLE; + doubleVal = d; + } + + /** + * Sets the variant to the given string value. + * + * @param d is the new string value. + */ + void setString(const char *s) + { + if (isString()) { + asString().assign(s); + } else { + destroy(); + type = Type::STRING; + ptrVal = new stringType(s); + } + } + + /** + * Sets the variant to the given array value. + * + * @param a is the new array value. + */ + void setArray(arrayType a) + { + if (isArray()) { + asArray().swap(a); + } else { + destroy(); + type = Type::ARRAY; + ptrVal = new arrayType(std::move(a)); + } + } + + /** + * Sets the variant to the given map value. + * + * @param a is the new map value. + */ + void setMap(mapType m) + { + if (isMap()) { + asMap().swap(m); + } else { + destroy(); + type = Type::MAP; + ptrVal = new mapType(std::move(m)); + } + } + + /** + * Returns the current type of the Variant. + * + * @return the current type of the Variant. + */ + Type getType() const { return type; } + + /** + * Returns the name of the given variant type as C-style string. + */ + static const char *getTypeName(Type type); + + /** + * Returns the name of the type of this variant instance. + */ + const char *getTypeName() { return Variant::getTypeName(getType()); } + + /** + * Prints the Variant to the output stream. + */ + friend std::ostream &operator<<(std::ostream &os, const Variant &v) + { + return os << v.toString(true); + } + + /** + * Prints a key value pair to the output stream. + */ + friend std::ostream &operator<<(std::ostream &os, + const mapType::value_type &v) + { + // TODO: Use proper serialization function + return os << "\"" << v.first << "\": " << v.second.toString(true); + } +}; + +} + +#endif /* _OUSIA_VARIANT_HPP_ */ + |