diff options
author | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2014-10-26 23:51:44 +0000 |
---|---|---|
committer | andreas <andreas@daaaf23c-2e50-4459-9457-1e69db5a47bf> | 2014-10-26 23:51:44 +0000 |
commit | 7ea06d81b263d23bbe3cb5a4480e63857cb36f0f (patch) | |
tree | 6faa2dbc88cb787b1383077b17da0dc17bdc3c57 | |
parent | a093d01e6a9b3dd8974a6a6d26706ed73a9c6217 (diff) |
implemented setting/getting host variables, including host functions but not yet host objects
git-svn-id: file:///var/local/svn/basicwriter@81 daaaf23c-2e50-4459-9457-1e69db5a47bf
-rw-r--r-- | src/core/script/Variant.cpp | 16 | ||||
-rw-r--r-- | src/core/script/Variant.hpp | 1 | ||||
-rw-r--r-- | src/plugins/mozjs/MozJsScriptEngine.cpp | 205 | ||||
-rw-r--r-- | src/plugins/mozjs/MozJsScriptEngine.hpp | 31 | ||||
-rw-r--r-- | test/plugins/genericjs/GenericJsScriptEngineTest.hpp | 198 | ||||
-rw-r--r-- | test/plugins/mozjs/MozJsScriptEngineTest.cpp | 1 |
6 files changed, 429 insertions, 23 deletions
diff --git a/src/core/script/Variant.cpp b/src/core/script/Variant.cpp index 72749b1..bb9f566 100644 --- a/src/core/script/Variant.cpp +++ b/src/core/script/Variant.cpp @@ -173,7 +173,7 @@ bool Variant::getBooleanValue() const case VariantType::map: return !getMapValue().empty(); default: - throw VariantTypeException{type, VariantType::boolean}; + throw VariantTypeException{VariantType::boolean, type}; } } @@ -187,7 +187,7 @@ int64_t Variant::getIntegerValue() const case VariantType::number: return static_cast<int64_t>(numberValue); default: - throw VariantTypeException{type, VariantType::integer}; + throw VariantTypeException{VariantType::integer, type}; } } @@ -201,7 +201,7 @@ double Variant::getNumberValue() const case VariantType::number: return numberValue; default: - throw VariantTypeException{type, VariantType::number}; + throw VariantTypeException{VariantType::number, type}; } } @@ -211,7 +211,7 @@ const std::string &Variant::getStringValue() const case VariantType::string: return *(static_cast<std::string *>(objectValue)); default: - throw VariantTypeException{type, VariantType::string}; + throw VariantTypeException{VariantType::string, type}; } } @@ -221,7 +221,7 @@ const std::vector<Variant> &Variant::getArrayValue() const case VariantType::array: return *(static_cast<std::vector<Variant> *>(objectValue)); default: - throw VariantTypeException{type, VariantType::array}; + throw VariantTypeException{VariantType::array, type}; } } @@ -232,7 +232,7 @@ const std::map<std::string, Variant> &Variant::getMapValue() const return *(static_cast<std::map<std::string, Variant> *>( objectValue)); default: - throw VariantTypeException{type, VariantType::map}; + throw VariantTypeException{VariantType::map, type}; } } @@ -241,7 +241,7 @@ const Function *Variant::getFunctionValue() const switch (type) { case VariantType::function: return static_cast<Function *>(objectValue); default: - throw VariantTypeException{type, VariantType::function}; + throw VariantTypeException{VariantType::function, type}; } } @@ -250,7 +250,7 @@ const Object &Variant::getObjectValue() const switch (type) { case VariantType::object: return *(static_cast<Object *>(objectValue)); default: - throw VariantTypeException{type, VariantType::function}; + throw VariantTypeException{VariantType::object, type}; } } diff --git a/src/core/script/Variant.hpp b/src/core/script/Variant.hpp index 8cb0e8f..848c595 100644 --- a/src/core/script/Variant.hpp +++ b/src/core/script/Variant.hpp @@ -31,6 +31,7 @@ // TODO: Use std::unique_ptr for *Function // TODO: Move semantic in complex constructors // TODO: Delete default constructors/assignment operators in pretty much everything +// TODO: Remove implicit type conversions, but add explicit conversion function! namespace ousia { namespace script { diff --git a/src/plugins/mozjs/MozJsScriptEngine.cpp b/src/plugins/mozjs/MozJsScriptEngine.cpp index e3f72b1..c67a3b4 100644 --- a/src/plugins/mozjs/MozJsScriptEngine.cpp +++ b/src/plugins/mozjs/MozJsScriptEngine.cpp @@ -16,6 +16,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <cassert> +#include <cstring> +#include <iostream> +#include <memory> + #include <jsapi.h> #include "MozJsScriptEngine.hpp" @@ -73,6 +78,102 @@ Variant MozJsScriptEngineFunction::call(const std::vector<Variant> &args) const /* Class MozJsScriptEngineScope */ +static const uint32_t MOZJS_FUNCTION_DATA_MAGIC = 0x87aac4ca; + +struct MozJsFunctionData { + /** + * Magic number used to make sure a pointer points to an instance of this + * struct. + */ + uint32_t magic; + + /** + * Reference to the script engine scope. + */ + MozJsScriptEngineScope &scope; + + /** + * Actual function associated with the object. + */ + std::unique_ptr<Function> function; + + /** + * Constructor of the MozJsPrivateFunctionData instance. + */ + MozJsFunctionData(MozJsScriptEngineScope &scope, Function *function) + : magic(MOZJS_FUNCTION_DATA_MAGIC), scope(scope), function(function) + { + } + + /** + * Destructor, resets the magic to zero, marking this instance as invalid. + */ + ~MozJsFunctionData() { magic = 0; } + + /** + * Returns true if the magic is set to the correct value, indicating that + * this actually is an instance of MozPrivateFunctionData. + */ + bool valid() { return magic == MOZJS_FUNCTION_DATA_MAGIC; } +}; + +/** + * Function used for deleting the private data that may be associated to a + * JSObject. + */ +void finalizeFunction(JSFreeOp *fop, JSObject *obj) +{ + MozJsFunctionData *data = + static_cast<MozJsFunctionData *>(JS_GetPrivate(obj)); + if (data) { + assert(data->valid()); + delete data; + } +} + +/** + * Function used for calling back into the host. + */ +JSBool callFunction(JSContext *cx, unsigned argc, JS::Value *vp) +{ + // Fetch the arguments (including the callee and the parent/this object) + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + // Fetch the underlying function object + JSObject &callee = args.callee(); + MozJsFunctionData *data = + static_cast<MozJsFunctionData *>(JS_GetPrivate(&callee)); + if (!data || !data->valid()) { + JS_ReportError(cx, "No valid function data attached to callable!"); + return JS_FALSE; + } + + // Assemble the function arguments + std::vector<Variant> arguments; + arguments.reserve(args.length()); + for (unsigned i = 0; i < args.length(); i++) { + JS::Value val = args.get(i); + arguments.push_back(data->scope.valueToVariant(val)); + } + + try { + // Call the host function + Variant res = data->function->call(arguments); + + // Convert the result to a JS::RootedValue + JS::RootedValue rval(cx); + data->scope.variantToValue(res, rval); + + // Return the result to the script code + args.rval().set(rval); + return JS_TRUE; + } + catch (ArgumentValidatorError ex) { + JS_ReportError(cx, ex.what()); + return JS_FALSE; + } +} + /** * The class of the global object. */ @@ -83,6 +184,13 @@ static JSClass globalClass = { nullptr, nullptr, nullptr, nullptr, nullptr}; +static JSClass functionClass = { + "function", JSCLASS_HAS_PRIVATE, JS_PropertyStub, + JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, + finalizeFunction, nullptr, callFunction, + nullptr, nullptr}; + MozJsScriptEngineScope::MozJsScriptEngineScope(JSRuntime *rt) : rt(rt) { // Create the execution context @@ -264,6 +372,94 @@ std::string MozJsScriptEngineScope::toString(JSString *str) return res; } +void MozJsScriptEngineScope::variantToValue(const Variant &var, + JS::RootedValue &val) +{ + switch (var.getType()) { + case VariantType::null: { + val.setNull(); + return; + } + case VariantType::boolean: { + val.setBoolean(var.getBooleanValue()); + return; + } + case VariantType::integer: { + val.setInt32(var.getIntegerValue()); + return; + } + case VariantType::number: { + val.setDouble(var.getNumberValue()); + return; + } + case VariantType::string: { + // Allocate enough memory for the string stored in the variant + const size_t size = var.getStringValue().size(); + const char *src = var.getStringValue().c_str(); + JS::RootedString s(cx, JS_NewStringCopyN(cx, src, size)); + if (!s) { + throw ScriptEngineException{"Out of JavaScript heap memory"}; + } + val.setString(s); + return; + } + case VariantType::array: { + const std::vector<Variant> &src = var.getArrayValue(); + JS::RootedObject a(cx, JS_NewArrayObject(cx, src.size(), nullptr)); + for (size_t i = 0; i < src.size(); i++) { + JS::RootedValue aval(cx); + variantToValue(src[i], aval); + JS_DefineElement(cx, a, i, aval, JS_PropertyStub, + JS_StrictPropertyStub, + JSPROP_ENUMERATE | JSPROP_INDEX); + } + val.setObjectOrNull(a.get()); + return; + } + case VariantType::map: { + const std::map<std::string, Variant> &src = var.getMapValue(); + JS::RootedObject m(cx, JS_NewObject(cx, nullptr, nullptr, nullptr)); + for (auto &e : src) { + setObjectProperty(m, e.first, e.second, false); + } + val.setObjectOrNull(m.get()); + return; + } + case VariantType::function: { + JS::RootedObject f(cx, JS_NewObject(cx, &functionClass, nullptr, nullptr)); + JS_SetPrivate(f, new MozJsFunctionData(*this, var.getFunctionValue()->clone())); + JS_FreezeObject(cx, f); + val.setObjectOrNull(f.get()); + return; + } + default: { + val.setNull(); + return; + } + } +} + +void MozJsScriptEngineScope::setObjectProperty(JS::RootedObject &obj, + const std::string &name, + const Variant &var, + bool constant) +{ + // Construct the property flags for the given variant type -- objects and + // functions are treated as readonly properties no matter what "constant" + // is set to. + int flags = JSPROP_PERMANENT | JSPROP_ENUMERATE; + if (constant || var.getType() == VariantType::object || + var.getType() == VariantType::function) { + flags |= JSPROP_READONLY; + } + + // Handle errors occuring while setting the property + JS::RootedValue val(cx); + variantToValue(var, val); + handleErr(JS_DefineProperty(cx, obj, name.c_str(), val, JS_PropertyStub, + JS_StrictPropertyStub, flags)); +} + Variant MozJsScriptEngineScope::doRun(const std::string &code) { JS::Value rval; @@ -273,15 +469,16 @@ Variant MozJsScriptEngineScope::doRun(const std::string &code) } void MozJsScriptEngineScope::doSetVariable(const std::string &name, - const Variant &val, bool constant) + const Variant &var, bool constant) { - // TODO + setObjectProperty(*global, name, var, constant); } Variant MozJsScriptEngineScope::doGetVariable(const std::string &name) { - // TODO - return Variant::Null; + JS::Value rval; + handleErr(JS_GetProperty(cx, *global, name.c_str(), &rval)); + return valueToVariant(rval); } /* Class MozJsScriptEngine */ diff --git a/src/plugins/mozjs/MozJsScriptEngine.hpp b/src/plugins/mozjs/MozJsScriptEngine.hpp index f98c871..02797bc 100644 --- a/src/plugins/mozjs/MozJsScriptEngine.hpp +++ b/src/plugins/mozjs/MozJsScriptEngine.hpp @@ -51,7 +51,8 @@ private: JS::RootedObject *parent; public: - MozJsScriptEngineFunction(MozJsScriptEngineScope &scope, JS::Value &fun, JSObject *parent); + MozJsScriptEngineFunction(MozJsScriptEngineScope &scope, JS::Value &fun, + JSObject *parent); ~MozJsScriptEngineFunction(); @@ -61,8 +62,7 @@ public: }; class MozJsScriptEngineScope : public ScriptEngineScope { - -friend MozJsScriptEngineFunction; + friend MozJsScriptEngineFunction; private: JSRuntime *rt; @@ -72,6 +72,19 @@ private: void handleErr(bool ok = false); +protected: + Variant doRun(const std::string &code) override; + void doSetVariable(const std::string &name, const Variant &var, + bool constant) override; + Variant doGetVariable(const std::string &name) override; + +public: + MozJsScriptEngineScope(JSRuntime *rt); + + ~MozJsScriptEngineScope() override; + + /* JS -> Host */ + Variant arrayToVariant(JSObject *obj); Variant objectToVariant(JSObject *obj); @@ -82,16 +95,12 @@ private: std::string toString(JSString *str); -protected: - Variant doRun(const std::string &code) override; - void doSetVariable(const std::string &name, const Variant &val, - bool constant) override; - Variant doGetVariable(const std::string &name) override; + /* Host -> JS */ -public: - MozJsScriptEngineScope(JSRuntime *rt); + void variantToValue(const Variant &var, JS::RootedValue &val); - ~MozJsScriptEngineScope() override; + void setObjectProperty(JS::RootedObject &obj, const std::string &name, + const Variant &var, bool constant); }; class MozJsScriptEngine : public ScriptEngine { diff --git a/test/plugins/genericjs/GenericJsScriptEngineTest.hpp b/test/plugins/genericjs/GenericJsScriptEngineTest.hpp index b7f95da..5b905ae 100644 --- a/test/plugins/genericjs/GenericJsScriptEngineTest.hpp +++ b/test/plugins/genericjs/GenericJsScriptEngineTest.hpp @@ -130,5 +130,203 @@ TEST(GENERIC_JS_TEST_NAME, returnFunction) ASSERT_EQ("Hello World", fres.getStringValue()); } +TEST(GENERIC_JS_TEST_NAME, exchangeNull) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant::Null); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::null, res.getType()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::null, res.getType()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeBoolean) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant{false}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::boolean, res.getType()); + ASSERT_FALSE(res.getBooleanValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::boolean, res.getType()); + ASSERT_FALSE(res.getBooleanValue()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeInteger) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant{(int64_t)42}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::integer, res.getType()); + ASSERT_EQ(42, res.getIntegerValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::integer, res.getType()); + ASSERT_EQ(42, res.getIntegerValue()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeNumber) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant{42.5}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::number, res.getType()); + ASSERT_EQ(42.5, res.getNumberValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::number, res.getType()); + ASSERT_EQ(42.5, res.getNumberValue()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeString) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant{"Hello World!"}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::string, res.getType()); + ASSERT_EQ("Hello World!", res.getStringValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::string, res.getType()); + ASSERT_EQ("Hello World!", res.getStringValue()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeArray) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable("test", Variant{{"Hello World!", (int64_t)42, false}}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::array, res.getType()); + std::vector<Variant> a = res.getArrayValue(); + ASSERT_EQ(3, a.size()); + + ASSERT_EQ(VariantType::string, a[0].getType()); + ASSERT_EQ("Hello World!", a[0].getStringValue()); + + ASSERT_EQ(VariantType::integer, a[1].getType()); + ASSERT_EQ(42, a[1].getIntegerValue()); + + ASSERT_EQ(VariantType::boolean, a[2].getType()); + ASSERT_FALSE(a[2].getBooleanValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::array, res.getType()); + std::vector<Variant> a = res.getArrayValue(); + ASSERT_EQ(3, a.size()); + + ASSERT_EQ(VariantType::string, a[0].getType()); + ASSERT_EQ("Hello World!", a[0].getStringValue()); + + ASSERT_EQ(VariantType::integer, a[1].getType()); + ASSERT_EQ(42, a[1].getIntegerValue()); + + ASSERT_EQ(VariantType::boolean, a[2].getType()); + ASSERT_FALSE(a[2].getBooleanValue()); + } +} + +TEST(GENERIC_JS_TEST_NAME, exchangeMap) +{ + GENERIC_JS_TEST_SCOPE + scope->setVariable( + "test", + Variant{{{"key1", "s1"}, {"key2", (int64_t)42}, {"key3", true}}}); + + { + Variant res = scope->run("test"); + ASSERT_EQ(VariantType::map, res.getType()); + std::map<std::string, Variant> m = res.getMapValue(); + ASSERT_EQ(3, m.size()); + + ASSERT_TRUE(m.find("key1") != m.end()); + ASSERT_TRUE(m.find("key2") != m.end()); + ASSERT_TRUE(m.find("key3") != m.end()); + + ASSERT_EQ(VariantType::string, m["key1"].getType()); + ASSERT_EQ("s1", m["key1"].getStringValue()); + + ASSERT_EQ(VariantType::integer, m["key2"].getType()); + ASSERT_EQ(42, m["key2"].getIntegerValue()); + + ASSERT_EQ(VariantType::boolean, m["key3"].getType()); + ASSERT_TRUE(m["key3"].getBooleanValue()); + } + + { + Variant res = scope->getVariable("test"); + ASSERT_EQ(VariantType::map, res.getType()); + std::map<std::string, Variant> m = res.getMapValue(); + ASSERT_EQ(3, m.size()); + + ASSERT_TRUE(m.find("key1") != m.end()); + ASSERT_TRUE(m.find("key2") != m.end()); + ASSERT_TRUE(m.find("key3") != m.end()); + + ASSERT_EQ(VariantType::string, m["key1"].getType()); + ASSERT_EQ("s1", m["key1"].getStringValue()); + + ASSERT_EQ(VariantType::integer, m["key2"].getType()); + ASSERT_EQ(42, m["key2"].getIntegerValue()); + + ASSERT_EQ(VariantType::boolean, m["key3"].getType()); + ASSERT_TRUE(m["key3"].getBooleanValue()); + } +} + +static HostFunction cat{[](const std::vector<Variant> &args, void *data) { + std::stringstream ss; + for (unsigned i = 0; i < args.size(); i++) { + ss << args[i].getStringValue(); + if (i + 1 < args.size()) { + ss << ' '; + } + } + return Variant{ss.str().c_str()}; +}}; + +TEST(GENERIC_JS_TEST_NAME, hostFunction) +{ + GENERIC_JS_TEST_SCOPE + + Variant f{&cat}; + + scope->setVariable("cat", f); + Variant res = scope->run("cat('Hello', 'World');"); + + ASSERT_EQ(VariantType::string, res.getType()); + ASSERT_EQ("Hello World", res.getStringValue()); +} + #endif /* _GENERIC_JS_SCRIPT_ENGINE_TEST_HPP_ */ diff --git a/test/plugins/mozjs/MozJsScriptEngineTest.cpp b/test/plugins/mozjs/MozJsScriptEngineTest.cpp index f8cba95..96e6cf6 100644 --- a/test/plugins/mozjs/MozJsScriptEngineTest.cpp +++ b/test/plugins/mozjs/MozJsScriptEngineTest.cpp @@ -16,6 +16,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <sstream> #include <memory> #include <gtest/gtest.h> |