diff options
-rw-r--r-- | CMakeLists.txt | 19 | ||||
-rw-r--r-- | src/core/script/ScriptEngine.cpp | 65 | ||||
-rw-r--r-- | src/core/script/ScriptEngine.hpp | 233 | ||||
-rw-r--r-- | src/core/script/Variant.cpp | 11 | ||||
-rw-r--r-- | src/core/script/Variant.hpp | 199 | ||||
-rw-r--r-- | src/core/utils/Utils.cpp | 39 | ||||
-rw-r--r-- | src/core/utils/Utils.hpp | 65 | ||||
-rw-r--r-- | test/core/script/Variant.cpp | 15 | ||||
-rw-r--r-- | test/core/utils/Utils.cpp | 35 |
9 files changed, 565 insertions, 116 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb3ac9..94bbcbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,20 +51,27 @@ INCLUDE_DIRECTORIES( src/ ) +# ousia_utils library +ADD_LIBRARY(ousia_utils + src/core/utils/Utils.cpp +) + # ousia_script library (containing the code of t ADD_LIBRARY(ousia_script src/core/script/Variant.cpp + src/core/script/ScriptEngine.cpp + src/core/script/ +) + +# Link the ousia executable against ousia_core +TARGET_LINK_LIBRARIES(ousia_script + ousia_utils ) # Definition of the main program #ADD_EXECUTABLE(ousia #) -# Link the ousia executable against ousia_core -#TARGET_LINK_LIBRARIES(ousia -# ousia_script -#) - # If testing is enabled, build the unit tests IF(test) # Include the gtest include files and the src directory @@ -76,11 +83,13 @@ IF(test) # Add all unit test files ADD_EXECUTABLE(ousia_test test/core/model/RangeSet + test/core/utils/Utils test/core/script/Variant ) TARGET_LINK_LIBRARIES(ousia_test ${GTEST_LIBRARIES} + ousia_utils ousia_script ) diff --git a/src/core/script/ScriptEngine.cpp b/src/core/script/ScriptEngine.cpp new file mode 100644 index 0000000..f34ccea --- /dev/null +++ b/src/core/script/ScriptEngine.cpp @@ -0,0 +1,65 @@ +/* + 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 "ScriptEngine.hpp" + +namespace ousia { +namespace script { + +ScriptEngineException::ScriptEngineException(int line, int col, + const std::string &msg) : + line(line), col(col), + msg(std::to_string(line) + ":" + std::to_string(col) + " " + msg) {} + +ScriptEngineException::ScriptEngineException(const std::string &msg) : + line(-1), col(-1), msg(msg) {} + +const char* ScriptEngineException::what() const noexcept +{ + return msg.c_str(); +} + +void ScriptEngineFactory::registerScriptEngine(const std::string &name, + ScriptEngine *engine) +{ + registry[name] = engine; +} + +bool ScriptEngineFactory::unregisterScriptEngine(const std::string &name) +{ + return registry.erase(name) > 0; +} + +/* Class ScriptEngineFactory */ + +ScriptEngineScope* ScriptEngineFactory::createScope( + const std::string &name) const +{ + auto it = registry.find(name); + if (it != registry.end()) { + return it->second->createScope(); + } + return nullptr; +} + + +} +} + diff --git a/src/core/script/ScriptEngine.hpp b/src/core/script/ScriptEngine.hpp new file mode 100644 index 0000000..3a53791 --- /dev/null +++ b/src/core/script/ScriptEngine.hpp @@ -0,0 +1,233 @@ +/* + 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_SCRIPT_ENGINE_HPP_ +#define _OUSIA_SCRIPT_ENGINE_HPP_ + +#include <map> +#include <string> +#include <exception> + +#include <core/utils/Utils.hpp> + +#include "Variant.hpp" + +namespace ousia { +namespace script { + +/** + * Class used for signaling errors while executing code or registering variables + * in the script engine. + */ +class ScriptEngineException : public std::exception { + +public: + /** + * Line and column at which the exception occured. Set to -1 if the error + * does not correspond to a line or column. + */ + const int line, col; + + /** + * Error message. + */ + const std::string msg; + + /** + * ScriptEngineException constructor. + * + * @param line in the script code at which the exception occured. + * @param col in the script code at which the exception occured. + * @param msg is the message containing the reason for the exception. + */ + ScriptEngineException(int line, int col, const std::string &msg); + + /** + * ScriptEngineException constructor. + * + * @param msg is the message containing the reason for the exception. + */ + ScriptEngineException(const std::string &msg); + + /** + * Returns the error message. + */ + virtual const char* what() const noexcept; + +}; + +/** + * The ScriptEngineScope class represents an execution scope -- an execution + * scope is the base class + */ +class ScriptEngineScope { + +private: + + /** + * Helper used to check the given identifiers for their validity. + * + * @param name is the name of the identifier that should be checked. + * @throws ScriptEngineException if the given identifier is invalid. + */ + static void checkIdentifier(const std::string &name) + { + if (!Utils::isIdentifier(name)) { + throw ScriptEngineException{"Invalid identifier \"" + name + "\""}; + } + } + +protected: + + /** + * Implementation of the @see run function. + */ + virtual Variant doRun(const std::string &code) = 0; + + /** + * Implementation of the @see setVariable function. + */ + virtual void doSetVariable(const std::string &name, const Variant &val, + bool constant) = 0; + + /** + * Implementation of the @see getVariable function. + */ + virtual Variant doGetVariable(const std::string &name) = 0; + +public: + + /** + * Virtual destructor. Must be overwritten by implementing classes. + */ + virtual ~ScriptEngineScope() {}; + + /** + * Runs the given code in the excution context. + * + * @param code is a string containing the code the script engine should run. + * @return a variant containg the result of the executed code. + * @throws ScriptEngineException if an error occured during code execution. + */ + Variant run(const std::string &code) + { + return doRun(code); + } + + /** + * Sets the value of a variable in the scope with the given name. + * + * @param name is the name of the variable in the scope. Must be a + * well-formed identifier. + * @param val is the value of the variable. + * @param constant if true, the value of the variable cannot be changed by + * the script code. + * @throws ScriptEngineException if name is not a well-formed identifier. + */ + void setVariable(const std::string &name, const Variant &val, + bool constant = false) + { + checkIdentifier(name); + doSetVariable(name, val, constant); + } + + /** + * Reads the value of the variable with the given name. + * + * @param name is the name of the variable. The name must be well-formed. + * @return the value of the variable, or a NULL variant if the variable does + * not exist. + * @throws ScriptEngineException if name is not a well-formed identifier. + */ + Variant getVariable(const std::string &name) + { + checkIdentifier(name); + return doGetVariable(name); + } + +}; + +/** + * The abstract ScriptEngine class is used to provide an interface for script + * engine implementations. A script engine implementation has to provide a + * function which creates an execution scope. + */ +class ScriptEngine { + +public: + /** + * Requests an execution scope from the script engine implementation. The + * calling code is responsible for disposing the returned pointer. + */ + virtual ScriptEngineScope* createScope() const = 0; + +}; + +/** + * The ScriptEngineFactory class is a central registry for ScriptEngine + * instances and factory of ScriptEngineScope instances for a certain scripting + * language. + */ +class ScriptEngineFactory { + +private: + /** + * Internal map between the script language name and the actual script + * engine instance. + */ + std::map<std::string, ScriptEngine*> registry; + +public: + + /** + * Registers a ScriptEngine instance for a new scripting language. + * + * @param name is the name of the scripting language as MIME, e.g. + * "text/javascript" + * @param engine is the backend that should be registered. + */ + void registerScriptEngine(const std::string &name, ScriptEngine *engine); + + /** + * Removes a script engine from the registry. + * + * @param name is the name of the script engine that + */ + bool unregisterScriptEngine(const std::string &name); + + /** + * Creates an execution scope for the scripting language with the given + * name. + * + * @param name is the name of the scripting language for which the scope + * is being created. + * @return a pointer to the new execution scope or null if a script engine + * with the given name does not exist. The caller of this function is + * responsible + */ + ScriptEngineScope* createScope(const std::string &name) const; + +}; + +} +} + + +#endif /* _OUSIA_SCRIPT_ENGINE_HPP_ */ + + diff --git a/src/core/script/Variant.cpp b/src/core/script/Variant.cpp index 623b396..a379735 100644 --- a/src/core/script/Variant.cpp +++ b/src/core/script/Variant.cpp @@ -24,9 +24,12 @@ namespace script { std::ostream& operator<< (std::ostream& os, const Variant &v) { switch (v.type) { - case VariantType::none: + case VariantType::null: os << "null"; break; + case VariantType::boolean: + os << (v.booleanValue ? "true" : "false"); + break; case VariantType::integer: os << v.integerValue; break; @@ -34,12 +37,12 @@ std::ostream& operator<< (std::ostream& os, const Variant &v) os << v.numberValue; break; case VariantType::string: - os << "\"" << v.stringValue << "\""; + os << "\"" << v.getStringValue() << "\""; break; case VariantType::array: { bool first = true; os << "["; - for (auto &v2 : v.arrayValue) { + for (auto &v2 : v.getArrayValue()) { if (!first) { os << ", "; } @@ -52,7 +55,7 @@ std::ostream& operator<< (std::ostream& os, const Variant &v) case VariantType::map: { bool first = true; os << "{"; - for (auto &v2 : v.mapValue) { + for (auto &v2 : v.getMapValue()) { if (!first) { os << ", "; } diff --git a/src/core/script/Variant.hpp b/src/core/script/Variant.hpp index e427af5..faa5bad 100644 --- a/src/core/script/Variant.hpp +++ b/src/core/script/Variant.hpp @@ -19,6 +19,8 @@ #ifndef _OUSIA_VARIANT_HPP_ #define _OUSIA_VARIANT_HPP_ +#include <iostream> + #include <cstdint> #include <ostream> #include <string> @@ -28,8 +30,6 @@ namespace ousia { namespace script { -// TODO: Make Variant immutable (?), store large objects in heap buffer - /** * Enum containing the possible types a variant may have. */ @@ -44,50 +44,28 @@ enum class VariantType { class Variant { private: - VariantType type; + const VariantType type; union { bool booleanValue; int64_t integerValue; double numberValue; - std::string stringValue; - std::vector<Variant> arrayValue; - std::map<std::string, Variant> mapValue; + void *objectValue = nullptr; }; - /** - * Private function calling the destructor of the currently used union - * member. - */ - void free() { - // Explicitly call the destructor - switch (type) { - case VariantType::string: - stringValue.std::string::~string(); - break; - case VariantType::array: - arrayValue.std::vector<Variant>::~vector(); - break; - case VariantType::map: - mapValue.std::map<std::string, Variant>::~map(); - break; - default: - break; - } +public: - // Reset the type - type = VariantType::none; - } + class EBadEntry {}; - /** - * Function for copying the content of the given instance v to this - * instance. Callers must make sure the storage space has been freed - * beforehand. - */ - void copy(const Variant &v) + Variant(const Variant &v) : + type(v.type) { - type = v.type; - switch (type) { + switch (v.type) { + case VariantType::null: + break; + case VariantType::boolean: + booleanValue = v.booleanValue; + break; case VariantType::integer: integerValue = v.integerValue; break; @@ -95,28 +73,34 @@ private: numberValue = v.numberValue; break; case VariantType::string: - new (&stringValue) std::string(v.stringValue); + objectValue = new std::string( + *static_cast<std::string*>(v.objectValue)); break; case VariantType::array: - new (&arrayValue) std::vector<Variant>(v.arrayValue); + objectValue = new std::vector<Variant>( + *static_cast<std::vector<Variant>*>(v.objectValue)); break; case VariantType::map: - new (&mapValue) std::map<std::string, Variant>(v.mapValue); + objectValue = new std::map<std::string, Variant>( + *static_cast<std::map<std::string, Variant>*>(v.objectValue)); break; - default: + case VariantType::function: + case VariantType::object: + case VariantType::buffer: + // TODO break; } } - /** - * Function for moving the content of the given instance v to this instance. - * No copy operation is used. Callers must make sure the storage space has - * been freed beforehand. - */ - void move(Variant &v) + Variant(Variant &&v) : + type(v.type) { - type = v.type; switch (type) { + case VariantType::null: + break; + case VariantType::boolean: + booleanValue = v.booleanValue; + break; case VariantType::integer: integerValue = v.integerValue; break; @@ -124,97 +108,96 @@ private: numberValue = v.numberValue; break; case VariantType::string: - new (&stringValue) std::string(std::move(v.stringValue)); + case VariantType::array: + case VariantType::map: + case VariantType::function: + case VariantType::object: + case VariantType::buffer: + objectValue = v.objectValue; + v.objectValue = nullptr; + break; + } + } + + ~Variant() + { + switch (type) { + case VariantType::string: + delete static_cast<std::string*>(objectValue); break; case VariantType::array: - new (&arrayValue) std::vector<Variant>(std::move(v.arrayValue)); + delete static_cast<std::vector<Variant>*>(objectValue); break; case VariantType::map: - new (&mapValue) std::map<std::string, Variant>(std::move(v.mapValue)); + delete static_cast<std::map<std::string, Variant>*>(objectValue); break; default: break; } - - // Reset the type of v to "none" - v.type = VariantType::none; } -public: + Variant& operator=(const Variant &v) = delete; + Variant& operator=(Variant &&v) = delete; - class EBadEntry {}; - - Variant(const Variant &v) - { - copy(v); - } - - Variant(Variant &&v) - { - move(v); - } - - Variant& operator=(const Variant &v) - { - free(); - copy(v); - return *this; - } - - Variant& operator=(Variant &&v) - { - free(); - move(v); - return *this; - } + Variant() : + type(VariantType::null) {} + Variant(bool b) : + type(VariantType::boolean), + booleanValue(b) {} Variant(int64_t i) : type(VariantType::integer), - integerValue(i) - { - // Do nothing here - } + integerValue(i) {} Variant(double d) : type(VariantType::number), - numberValue(d) - { - // Do nothing here - } + numberValue(d) {} Variant(const char *s) : - type(VariantType::string) - { - new (&stringValue) std::string(s); - } + type(VariantType::string), + objectValue(new std::string(s)) {} Variant(const std::vector<Variant> &a) : - type(VariantType::array) - { - new (&arrayValue) std::vector<Variant>(a); - } - + type(VariantType::array), + objectValue(new std::vector<Variant>(a)) {} Variant(const std::map<std::string, Variant> &m) : - type(VariantType::map) - { - new (&mapValue) std::map<std::string, Variant>(m); - } + type(VariantType::map), + objectValue(new std::map<std::string, Variant>(m)) {} - ~Variant() + VariantType getType() const { - free(); + return type; } - VariantType getType() const + bool getBooleanValue() const { - return type; + switch (type) { + case VariantType::null: + return false; + case VariantType::boolean: + return booleanValue; + case VariantType::integer: + return integerValue != 0; + case VariantType::number: + return numberValue != 0.0; + case VariantType::string: + return !getStringValue().empty(); + case VariantType::array: + return !getArrayValue().empty(); + case VariantType::map: + return !getMapValue().empty(); + default: + throw EBadEntry{}; + } } int64_t getIntegerValue() const { switch (type) { + case VariantType::boolean: + return booleanValue ? 1 : 0; case VariantType::integer: return integerValue; case VariantType::number: @@ -227,6 +210,8 @@ public: double getNumberValue() const { switch (type) { + case VariantType::boolean: + return booleanValue ? 1.0 : 0.0; case VariantType::integer: return static_cast<double>(integerValue); case VariantType::number: @@ -240,7 +225,7 @@ public: { switch (type) { case VariantType::string: - return stringValue; + return *(static_cast<std::string*>(objectValue)); default: throw EBadEntry {}; } @@ -250,7 +235,7 @@ public: { switch (type) { case VariantType::array: - return arrayValue; + return *(static_cast<std::vector<Variant>*>(objectValue)); default: throw EBadEntry {}; } @@ -260,7 +245,7 @@ public: { switch (type) { case VariantType::map: - return mapValue; + return *(static_cast<std::map<std::string, Variant>*>(objectValue)); default: throw EBadEntry {}; } diff --git a/src/core/utils/Utils.cpp b/src/core/utils/Utils.cpp new file mode 100644 index 0000000..184fdd0 --- /dev/null +++ b/src/core/utils/Utils.cpp @@ -0,0 +1,39 @@ +/* + 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 "Utils.hpp" + +namespace ousia { + +bool Utils::isIdentifier(const std::string &name) +{ + bool first = true; + for (char c : name) { + if (first && !(isAlphabetic(c) || c == '_')) { + return false; + } + if (first && !(isAlphanumeric(c) || c == '_' || c == '-')) { + return false; + } + first = false; + } + return true; +} + +} + diff --git a/src/core/utils/Utils.hpp b/src/core/utils/Utils.hpp new file mode 100644 index 0000000..2fcd794 --- /dev/null +++ b/src/core/utils/Utils.hpp @@ -0,0 +1,65 @@ +/* + 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_UTILS_H_ +#define _OUSIA_UTILS_H_ + +#include <string> + +namespace ousia { + +class Utils { + +public: + + /** + * Returns true if the given character is in [A-Za-z] + */ + static bool isAlphabetic(const char c) + { + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); + } + + /** + * Returns true if the given character is in [0-9] + */ + static bool isNumeric(const char c) + { + return (c >= '0') && (c <= '9'); + } + + /** + * Returns true if the given character is in [A-Za-z0-9] + */ + static bool isAlphanumeric(const char c) + { + return isAlphabetic(c) || isNumeric(c); + } + + /** + * Returns true if the given character is in [A-Za-z_][A-Za-z0-9_-]* + */ + static bool isIdentifier(const std::string &name); + +}; + +} + +#endif /* _OUSIA_UTILS_H_ */ + diff --git a/test/core/script/Variant.cpp b/test/core/script/Variant.cpp index 164bcab..cf8f3c7 100644 --- a/test/core/script/Variant.cpp +++ b/test/core/script/Variant.cpp @@ -23,6 +23,17 @@ namespace ousia { namespace script { +TEST(Variant, getBooleanValue) +{ + ASSERT_TRUE((Variant{true}).getBooleanValue()); + ASSERT_FALSE((Variant{false}).getBooleanValue()); + ASSERT_FALSE((Variant{(int64_t)0}).getBooleanValue()); + ASSERT_TRUE((Variant{(int64_t)1}).getBooleanValue()); + ASSERT_FALSE((Variant{0.0}).getBooleanValue()); + ASSERT_TRUE((Variant{1.2}).getBooleanValue()); + ASSERT_FALSE((Variant{""}).getBooleanValue()); +} + TEST(Variant, getIntegerValue) { Variant vi{(int64_t)42}; @@ -30,6 +41,8 @@ TEST(Variant, getIntegerValue) ASSERT_EQ(42, vi.getIntegerValue()); ASSERT_EQ(42, vf.getIntegerValue()); + ASSERT_EQ(1, (Variant{true}).getIntegerValue()); + ASSERT_EQ(0, (Variant{false}).getIntegerValue()); } TEST(Variant, getNumberValue) @@ -39,6 +52,8 @@ TEST(Variant, getNumberValue) ASSERT_EQ(42, vi.getNumberValue()); ASSERT_EQ(42.5, vf.getNumberValue()); + ASSERT_EQ(1.0, (Variant{true}).getIntegerValue()); + ASSERT_EQ(0.0, (Variant{false}).getIntegerValue()); } TEST(Variant, getStringValue) diff --git a/test/core/utils/Utils.cpp b/test/core/utils/Utils.cpp new file mode 100644 index 0000000..d349c6f --- /dev/null +++ b/test/core/utils/Utils.cpp @@ -0,0 +1,35 @@ +/* + 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 <gtest/gtest.h> + +#include <core/utils/Utils.hpp> + +namespace ousia { + +TEST(Utils, isIdentifier) +{ + ASSERT_TRUE(Utils::isIdentifier("test")); + ASSERT_TRUE(Utils::isIdentifier("t0-_est")); + ASSERT_TRUE(Utils::isIdentifier("_t0-_EST")); + ASSERT_FALSE(Utils::isIdentifier("-t0-_EST")); + ASSERT_FALSE(Utils::isIdentifier("0t-_EST")); +} + +} + |