// Aseprite Scripting Library // Copyright (c) 2015 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "scripting/engine.h" #include "base/convert_to.h" #include "base/exception.h" #include "base/file_handle.h" #include "base/memory.h" #include "scripting/engine_delegate.h" #define DUK_OPT_PANIC_HANDLER scripting_engine_panic_handler #include class ScriptingEngineException : public base::Exception { public: ScriptingEngineException(duk_errcode_t code, const char* msg) : Exception(std::string(msg) + " (code " + base::convert_to(code) + ")") { } }; static void scripting_engine_panic_handler(duk_errcode_t code, const char* msg) { throw ScriptingEngineException(code, msg); } extern "C" { #include } namespace scripting { bool Context::isConstructorCall() { return (duk_is_constructor_call(m_handle) ? true: false); } bool Context::isUndefined(index_t i) { return (duk_is_undefined(m_handle, i) ? true: false); } bool Context::isNull(index_t i) { return (duk_is_null(m_handle, i) ? true: false); } bool Context::isNullOrUndefined(index_t i) { return (duk_is_null_or_undefined(m_handle, i) ? true: false); } bool Context::isBool(index_t i) { return (duk_is_boolean(m_handle, i) ? true: false); } bool Context::isNumber(index_t i) { return (duk_is_number(m_handle, i) ? true: false); } bool Context::isNaN(index_t i) { return (duk_is_nan(m_handle, i) ? true: false); } bool Context::isString(index_t i) { return (duk_is_string(m_handle, i) ? true: false); } bool Context::getBool(index_t i) { return (duk_get_boolean(m_handle, i) ? true: false); } double Context::getNumber(index_t i) { return duk_get_number(m_handle, i); } int Context::getInt(index_t i) { return duk_get_int(m_handle, i); } unsigned int Context::getUInt(index_t i) { return duk_get_uint(m_handle, i); } const char* Context::getString(index_t i) { return duk_get_string(m_handle, i); } bool Context::requireBool(index_t i) { return (duk_require_boolean(m_handle, i) ? true: false); } double Context::requireNumber(index_t i) { return duk_require_number(m_handle, i); } int Context::requireInt(index_t i) { return duk_require_int(m_handle, i); } unsigned int Context::requireUInt(index_t i) { return duk_require_uint(m_handle, i); } const char* Context::requireString(index_t i) { return duk_require_string(m_handle, i); } void* Context::requireObject(index_t i, const char* className) { duk_get_prop_string(m_handle, i, "\xFF" "\xFF" "ptr"); void* result = (void*)duk_to_pointer(m_handle, -1); // TODO check pointer type duk_pop(m_handle); return result; } void Context::pushUndefined() { duk_push_undefined(m_handle); } void Context::pushNull() { duk_push_null(m_handle); } void Context::pushBool(bool val) { duk_push_boolean(m_handle, val); } void Context::pushNumber(double val) { duk_push_number(m_handle, val); } void Context::pushNaN() { duk_push_nan(m_handle); } void Context::pushInt(int val) { duk_push_int(m_handle, val); } void Context::pushUInt(unsigned int val) { duk_push_uint(m_handle, val); } void Context::pushString(const char* str) { duk_push_string(m_handle, str); } void Context::pushThis(void* ptr) { duk_push_this(m_handle); duk_push_pointer(m_handle, ptr); duk_put_prop_string(m_handle, -2, "\xFF" "\xFF" "ptr"); } void Context::pushObject(void* ptr, const char* className) { duk_push_object(m_handle); duk_push_pointer(m_handle, ptr); duk_put_prop_string(m_handle, -2, "\xFF" "\xFF" "ptr"); duk_get_global_string(m_handle, className); duk_get_prop_string(m_handle, -1, "prototype"); duk_set_prototype(m_handle, -3); duk_pop(m_handle); } void* Context::getThis() { duk_push_this(m_handle); duk_get_prop_string(m_handle, -1, "\xFF" "\xFF" "ptr"); void* result = (void*)duk_to_pointer(m_handle, -1); duk_pop(m_handle); return result; } Engine::Engine(EngineDelegate* delegate) : m_ctx(duk_create_heap_default()) , m_delegate(delegate) { } Engine::~Engine() { duk_destroy_heap(m_ctx.handle()); } void Engine::eval(const std::string& jsCode) { try { ContextHandle handle = m_ctx.handle(); duk_eval_string(handle, jsCode.c_str()); if (!duk_is_null_or_undefined(handle, -1)) m_delegate->onConsolePrint(duk_safe_to_string(handle, -1)); duk_pop(handle); } catch (const std::exception& ex) { std::string err = "Error: "; err += ex.what(); m_delegate->onConsolePrint(err.c_str()); } } void Engine::evalFile(const std::string& file) { try { ContextHandle handle = m_ctx.handle(); base::FileHandle fhandle(base::open_file(file, "rb")); FILE* f = fhandle.get(); if (!f) return; if (fseek(f, 0, SEEK_END) < 0) return; int sz = ftell(f); if (sz < 0) return; if (fseek(f, 0, SEEK_SET) < 0) return; char* buf = (char*)duk_push_fixed_buffer(handle, sz); ASSERT(buf != nullptr); if (fread(buf, 1, sz, f) != sz) return; fclose(f); f = nullptr; duk_push_string(handle, duk_to_string(handle, -1)); duk_eval_raw(handle, nullptr, 0, DUK_COMPILE_EVAL); if (!duk_is_null_or_undefined(handle, -1)) m_delegate->onConsolePrint(duk_safe_to_string(handle, -1)); duk_pop(handle); } catch (const std::exception& ex) { std::string err = "Error: "; err += ex.what(); m_delegate->onConsolePrint(err.c_str()); } } void Engine::registerFunction(const char* id, Function function, int nargs) { ContextHandle handle = m_ctx.handle(); duk_push_global_object(handle); duk_push_c_function(handle, function, nargs); duk_put_prop_string(handle, -2, id); } void Engine::registerClass(const char* id, Function ctorFunc, int ctorNargs, const FunctionEntry* methods, const PropertyEntry* props) { ContextHandle handle = m_ctx.handle(); duk_push_c_function(handle, ctorFunc, ctorNargs); duk_push_object(handle); if (methods) duk_put_function_list(handle, -1, (const duk_function_list_entry*)methods); if (props) { for (int i=0; props[i].key; ++i) { duk_push_string(handle, props[i].key); duk_idx_t objidx = -2; duk_uint_t flags = 0; if (props[i].getter) { flags |= DUK_DEFPROP_HAVE_GETTER; duk_push_c_function(handle, props[i].getter, 0); --objidx; } if (props[i].setter) { flags |= DUK_DEFPROP_HAVE_SETTER; duk_push_c_function(handle, props[i].setter, 1); --objidx; } duk_def_prop(handle, objidx, flags); } } duk_put_prop_string(handle, -2, "prototype"); duk_put_global_string(handle, id); } void Engine::registerGlobal(const char* id, Function getter, Function setter) { ContextHandle handle = m_ctx.handle(); duk_push_global_object(handle); duk_push_string(handle, id); duk_idx_t objidx = -2; duk_uint_t flags = 0; if (getter) { flags |= DUK_DEFPROP_HAVE_GETTER; duk_push_c_function(handle, getter, 0); --objidx; } if (setter) { flags |= DUK_DEFPROP_HAVE_SETTER; duk_push_c_function(handle, setter, 1); --objidx; } duk_def_prop(handle, objidx, flags); } } // namespace scripting