diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/script/Function.cpp | 52 | ||||
-rw-r--r-- | src/core/script/Function.hpp | 67 | ||||
-rw-r--r-- | src/core/script/Variant.cpp | 7 | ||||
-rw-r--r-- | src/core/script/Variant.hpp | 4 | ||||
-rw-r--r-- | test/core/script/Function.cpp | 62 |
6 files changed, 186 insertions, 7 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c48c48..df4155f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ IF(test) ADD_EXECUTABLE(ousia_test test/core/model/RangeSet test/core/utils/Utils + test/core/script/Function test/core/script/Variant ) diff --git a/src/core/script/Function.cpp b/src/core/script/Function.cpp index d72df4c..ce6e6b6 100644 --- a/src/core/script/Function.cpp +++ b/src/core/script/Function.cpp @@ -21,9 +21,57 @@ namespace ousia { namespace script { -std::pair<bool, std::vector<Variant>> validate(const std::vector<Variant> &args) +std::pair<bool, std::vector<Variant>> ArgumentValidator::setError(int idx, + const std::string &msg, std::vector<Variant> &res) { - return std::make_pair(true, std::vector<Variant>{}); + errorIndex = idx; + errorMessage = msg; + return std::make_pair(false, res); +} + +void ArgumentValidator::resetError() +{ + errorIndex = -1; + errorMessage = ""; +} + +std::pair<bool, std::vector<Variant>> ArgumentValidator::validate( + const std::vector<Variant> &args) +{ + std::vector<Variant> res; + + // Reset any old error + resetError(); + + // Sanity check: Do not allow too many arguments + if (args.size() > descriptors.size()) { + return setError(descriptors.size(), "Expected " + std::to_string(descriptors.size()) + + " arguments but got " + std::to_string(args.size()), res); + } + + // Iterate over the given arguments and check their type + res.reserve(descriptors.size()); + for (unsigned int i = 0; i < args.size(); i++) { + // TODO: Implicit type conversion + const VariantType tGiven = args[i].getType(); + const VariantType tExpected = descriptors[i].type; + if (tGiven != tExpected) { + return setError(i, std::string("Expected type ") + Variant::getTypeName(tExpected) + + " but got " + Variant::getTypeName(tGiven), res); + } + res.push_back(args[i]); + } + + // Make sure the remaining arguments have a default value, and if they have + // one, add it to the result + for (unsigned int i = args.size(); i < descriptors.size(); i++) { + if (!descriptors[i].hasDefault) { + return setError(i, "Expected argument " + std::to_string(i), res); + } + res.push_back(descriptors[i].defaultValue); + } + + return std::make_pair(true, res); } } diff --git a/src/core/script/Function.hpp b/src/core/script/Function.hpp index 35e2205..4c13dbc 100644 --- a/src/core/script/Function.hpp +++ b/src/core/script/Function.hpp @@ -46,6 +46,13 @@ public: */ virtual Variant call(const std::vector<Variant> &args) const = 0; + /** + * Calls the underlying function with no arguments. + * + * @return a Variant containing the return value. + */ + Variant call() const { return call({}); } + }; /** @@ -70,7 +77,7 @@ struct ArgumentDescriptor { * ArgumentValidatorError is an exception type used to represent argument * validator errors. */ -class ArgumentValidatorError : std::exception { +class ArgumentValidatorError : public std::exception { public: @@ -110,6 +117,11 @@ private: */ std::string errorMessage; + std::pair<bool, std::vector<Variant>> setError(int idx, + const std::string &msg, std::vector<Variant> &res); + + void resetError(); + public: /** @@ -153,6 +165,59 @@ public: /** * The HostFunction class represents a function that resides in the script host. */ +template<class T> +class HostFunction : public Function { + +private: + T callback; + ArgumentValidator *validator; + void *data; + +public: + + HostFunction(T callback, std::vector<ArgumentDescriptor> signature, + void *data = nullptr) : + callback(callback), validator(new ArgumentValidator(signature)), + data(data) {} + + HostFunction(T callback, void *data = nullptr) : + callback(callback), validator(nullptr), data(data) {} + + ~HostFunction() + { + delete validator; + } + + virtual Variant call(const std::vector<Variant> &args) const override + { + if (validator) { + std::pair<bool, std::vector<Variant>> res = validator->validate(args); + if (!res.first) { + throw validator->error(); + } + return callback(res.second, data); + } else { + return callback(args, data); + } + } + + using Function::call; + +}; + +template<class T> +static HostFunction<T> createHostFunction(T callback, + std::vector<ArgumentDescriptor> signature, void *data = nullptr) +{ + return HostFunction<T>(callback, signature, data); +} + +template<class T> +static HostFunction<T> createHostFunction(T callback, void *data = nullptr) +{ + return HostFunction<T>(callback, data); +} + } } diff --git a/src/core/script/Variant.cpp b/src/core/script/Variant.cpp index b70f361..3858b68 100644 --- a/src/core/script/Variant.cpp +++ b/src/core/script/Variant.cpp @@ -23,7 +23,7 @@ namespace script { /* Class VariantTypeException */ -static const char* getVariantTypeName(VariantType type) +const char* Variant::getTypeName(VariantType type) { switch (type) { case VariantType::null: @@ -53,8 +53,9 @@ static const char* getVariantTypeName(VariantType type) VariantTypeException::VariantTypeException(VariantType actualType, VariantType requestedType) : msg(std::string("Cannot get value of variant of type \"") - + getVariantTypeName(actualType) - + std::string("\" as \"") + getVariantTypeName(requestedType)), + + Variant::getTypeName(actualType) + + std::string("\" as \"") + Variant::getTypeName(requestedType) + + std::string("\"")), actualType(actualType), requestedType(requestedType) {} const char* VariantTypeException::what() const noexcept diff --git a/src/core/script/Variant.hpp b/src/core/script/Variant.hpp index ee4a97a..923c8ca 100644 --- a/src/core/script/Variant.hpp +++ b/src/core/script/Variant.hpp @@ -49,7 +49,7 @@ enum class VariantType : int16_t { * Exception thrown whenever a variant is accessed via a getter function that * is not supported for the current variant type. */ -class VariantTypeException : std::exception { +class VariantTypeException : public std::exception { private: /** @@ -299,6 +299,8 @@ public: } } + static const char* getTypeName(VariantType type); + friend std::ostream& operator<< (std::ostream& os, const Variant &v); }; diff --git a/test/core/script/Function.cpp b/test/core/script/Function.cpp new file mode 100644 index 0000000..bd2c9d6 --- /dev/null +++ b/test/core/script/Function.cpp @@ -0,0 +1,62 @@ +/* + 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/script/Function.hpp> + +namespace ousia { +namespace script { + +TEST(HostFunction, callDirect) +{ + // Local variable + int v = 0; + + // Host function which sets the local variable + auto f = createHostFunction( + [&v](const std::vector<Variant> &args, void *data) { + v = args[0].getIntegerValue(); + return VarNull; + }, {ArgumentDescriptor{VariantType::integer}}); + + // Call the host function + ASSERT_EQ(VariantType::null, f.call({{(int64_t)42}}).getType()); + ASSERT_EQ(42, v); +} + +TEST(HostFunction, callDefaults) +{ + // Local variable + int v = 0; + + // Host function which sets the local variable + auto f = createHostFunction( + [&v](const std::vector<Variant> &args, void *data) { + v = args[0].getIntegerValue(); + return Variant{"Hallo Welt"}; + }, {ArgumentDescriptor{VariantType::integer, {(int64_t)42}}}); + + // Call the host function + ASSERT_EQ("Hallo Welt", f.call().getStringValue()); + ASSERT_EQ(42, v); +} + +} +} + |