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_ */ +  | 
