From 4794fa1884215982384535d25bfcc8e37b496629 Mon Sep 17 00:00:00 2001 From: Oliver Buchtala Date: Fri, 6 Sep 2013 00:38:07 +0300 Subject: [PATCH] Refactored custom javascript engines to support primitive 'require' statements. --- Tools/javascript/javascript.cxx | 24 +----- Tools/javascript/js_shell.cxx | 47 +++++++---- Tools/javascript/js_shell.h | 7 +- Tools/javascript/jsc_shell.cxx | 134 +++++++++++++++++++++++--------- Tools/javascript/v8_shell.cxx | 122 ++++++++++++++++------------- 5 files changed, 203 insertions(+), 131 deletions(-) diff --git a/Tools/javascript/javascript.cxx b/Tools/javascript/javascript.cxx index 82e94f434..ec4ac4e3f 100644 --- a/Tools/javascript/javascript.cxx +++ b/Tools/javascript/javascript.cxx @@ -14,21 +14,12 @@ void print_usage() { int main(int argc, char* argv[]) { std::string scriptPath = ""; - std::vector module_names; bool interactive = false; JSShell* shell = 0; for (int idx = 1; idx < argc; ++idx) { - if(strcmp(argv[idx], "-l") == 0) { - idx++; - if(idx > argc) { - print_usage(); - exit(-1); - } - std::string module_name(argv[idx]); - module_names.push_back(module_name); - } else if(strcmp(argv[idx], "-v8") == 0) { + if(strcmp(argv[idx], "-v8") == 0) { shell = JSShell::Create(JSShell::V8); } else if(strcmp(argv[idx], "-jsc") == 0) { shell = JSShell::Create(JSShell::JSC); @@ -44,19 +35,6 @@ int main(int argc, char* argv[]) { } bool failed = false; - for(std::vector::iterator it = module_names.begin(); - it != module_names.end(); ++it) { - std::string module_name = *it; - - bool success = shell->ImportModule(module_name); - failed |= !success; - } - - if (failed) { - delete shell; - printf("FAIL: Some modules could not be loaded.\n"); - return -1; - } if(interactive) { failed = !(shell->RunShell()); diff --git a/Tools/javascript/js_shell.cxx b/Tools/javascript/js_shell.cxx index fa5500f8e..ca5ca7ecd 100644 --- a/Tools/javascript/js_shell.cxx +++ b/Tools/javascript/js_shell.cxx @@ -21,7 +21,6 @@ #endif - JSShell::~JSShell() { for(std::vector::iterator it = loaded_modules.begin(); @@ -32,32 +31,46 @@ JSShell::~JSShell() { } -bool JSShell::ImportModule(const std::string& name) { +// TODO: this could be done more intelligent... +// - can we achieve source file relative loading? +// - better path resolution +std::string JSShell::LoadModule(const std::string& name, HANDLE* library) { - std::string lib_name = LIBRARYFILE(name); + // works only for posix like OSs + size_t pathIdx = name.find_last_of("/"); - HANDLE handle = LOAD_LIBRARY(lib_name.c_str()); - if(handle == 0) { - std::cout << "Could not load library " << lib_name << ":" - << std::endl << LIBRARY_ERROR() << std::endl; - return false; - } + std::string lib_name; + std::string module_name; + if (pathIdx == std::string::npos) { + module_name = name; + lib_name = std::string(name).append(LIBRARY_EXT); + } else { + std::string path = name.substr(0, pathIdx+1); + module_name = name.substr(pathIdx+1); + lib_name = path.append(module_name).append(LIBRARY_EXT); + } - if(!RegisterModule(handle, name)) { - std::cout << "Could not find initializer function in " << lib_name << std::endl; - CLOSE_LIBRARY(handle); - return false; - } + HANDLE handle = LOAD_LIBRARY(lib_name.c_str()); + if(handle == 0) { + std::cout << "Could not load library " << lib_name << ":" + << std::endl << LIBRARY_ERROR() << std::endl; + return 0; + } - loaded_modules.push_back(handle); + loaded_modules.push_back(handle); - return true; + *library = handle; + + return module_name; } bool JSShell::RunScript(const std::string& scriptPath) { std::string source = ReadFile(scriptPath); if(!InitializeEngine()) return false; + // Node.js compatibility: make `print` available as `console.log()` + ExecuteScript("var console = {}; console.log = print;", ""); + if(!ExecuteScript(source, scriptPath)) { return false; } @@ -126,7 +139,7 @@ V8Shell_Create, JSShell *JSShell::Create(Engine engine) { if(js_shell_factories[engine] == 0) { - throw "Engine not supported."; + throw "Engine not available."; } return js_shell_factories[engine](); } diff --git a/Tools/javascript/js_shell.h b/Tools/javascript/js_shell.h index 54f55b69d..84a8534d6 100644 --- a/Tools/javascript/js_shell.h +++ b/Tools/javascript/js_shell.h @@ -5,6 +5,7 @@ #include typedef void* HANDLE; +typedef void* MODULE; class JSShell { @@ -22,7 +23,7 @@ public: static JSShell* Create(Engine engine = JSC); - bool ImportModule(const std::string& name); + std::string LoadModule(const std::string& name, HANDLE* library); virtual bool RunScript(const std::string& scriptPath); @@ -30,11 +31,9 @@ public: protected: - virtual bool RegisterModule(HANDLE library, const std::string& module_name) = 0; - virtual bool InitializeEngine() = 0; - virtual bool ExecuteScript(const std::string& source, const std::string& name) = 0; + virtual bool ExecuteScript(const std::string& source, const std::string& scriptPath) = 0; virtual bool DisposeEngine() = 0; diff --git a/Tools/javascript/jsc_shell.cxx b/Tools/javascript/jsc_shell.cxx index ee95e04a5..e4e8cdd96 100644 --- a/Tools/javascript/jsc_shell.cxx +++ b/Tools/javascript/jsc_shell.cxx @@ -14,7 +14,7 @@ class JSCShell: public JSShell { -typedef int (*JSCIntializer)(JSGlobalContextRef context); +typedef int (*JSCIntializer)(JSGlobalContextRef context, JSObjectRef *module); public: @@ -24,26 +24,26 @@ public: protected: - virtual bool RegisterModule(HANDLE library, const std::string& module_name); - virtual bool InitializeEngine(); - virtual bool ExecuteScript(const std::string& source, const std::string& name); + virtual bool ExecuteScript(const std::string& source, const std::string& scriptPath); virtual bool DisposeEngine(); private: - static JSValueRef Print(JSContextRef context,JSObjectRef object, JSObjectRef globalobj, size_t argc, const JSValueRef args[], JSValueRef* ex); + JSObjectRef Import(const std::string &moduleName); + + static JSValueRef Print(JSContextRef context, JSObjectRef object, JSObjectRef globalobj, size_t argc, const JSValueRef args[], JSValueRef* ex); + + static JSValueRef Require(JSContextRef context, JSObjectRef object, JSObjectRef globalobj, size_t argc, const JSValueRef args[], JSValueRef* ex); static bool RegisterFunction(JSGlobalContextRef context, JSObjectRef object, const char* functionName, JSObjectCallAsFunctionCallback cbFunction); - static void PrintError(JSContextRef, JSValueRef, const std::string&); + static void PrintError(JSContextRef, JSValueRef); private: - std::vector module_initializers; - JSGlobalContextRef context; }; @@ -54,16 +54,6 @@ JSCShell::~JSCShell() { } } -bool JSCShell::RegisterModule(HANDLE library, const std::string& module_name) { - std::string symname = std::string(module_name).append("_initialize"); - - JSCIntializer init_function = reinterpret_cast((long) LOAD_SYMBOL(library, symname.c_str())); - if(init_function == 0) return false; - - module_initializers.push_back(init_function); - return true; -} - bool JSCShell::InitializeEngine() { if(context != 0) { JSGlobalContextRelease(context); @@ -73,26 +63,33 @@ bool JSCShell::InitializeEngine() { context = JSGlobalContextCreate(NULL); if(context == 0) return false; JSObjectRef globalObject = JSContextGetGlobalObject(context); + + + // store this for later use + JSClassDefinition __shell_classdef__; + JSClassRef __shell_class__ = JSClassCreate(&__shell_classdef__); + JSObjectRef __shell__ = JSObjectMake(context, __shell_class__, 0); + bool success = JSObjectSetPrivate(__shell__, (void*) (long) this); + JSStringRef shellKey = JSStringCreateWithUTF8CString("__shell__"); + JSObjectSetProperty(context, globalObject, shellKey, __shell__, kJSPropertyAttributeReadOnly, NULL); + JSStringRelease(shellKey); + JSCShell::RegisterFunction(context, globalObject, "print", JSCShell::Print); - // Call module initializers - for(std::vector::iterator it = module_initializers.begin(); - it != module_initializers.end(); ++it) { - JSCIntializer init_function = *it; - if(!init_function(context)) { - return false; - } - } + JSCShell::RegisterFunction(context, globalObject, "require", JSCShell::Require); + return true; } -bool JSCShell::ExecuteScript(const std::string& source, const std::string& name) { +bool JSCShell::ExecuteScript(const std::string& source, const std::string& scriptPath) { JSStringRef jsScript; + JSStringRef sourceURL; JSValueRef ex; jsScript = JSStringCreateWithUTF8CString(source.c_str()); - JSValueRef jsResult = JSEvaluateScript(context, jsScript, 0, 0, 0, &ex); + sourceURL = JSStringCreateWithUTF8CString(scriptPath.c_str()); + JSValueRef jsResult = JSEvaluateScript(context, jsScript, 0, sourceURL, 0, &ex); JSStringRelease(jsScript); if (jsResult == NULL && ex != NULL) { - JSCShell::PrintError(context, ex, name); + JSCShell::PrintError(context, ex); return false; } return true; @@ -121,6 +118,65 @@ JSValueRef JSCShell::Print(JSContextRef context, JSObjectRef object, return JSValueMakeUndefined(context); } +// Attention: this feature should not create too high expectations. +// It is only capable of loading things relative to the execution directory +// and not relative to the parent script. +JSValueRef JSCShell::Require(JSContextRef context, JSObjectRef object, + JSObjectRef globalObj, size_t argc, + const JSValueRef args[], JSValueRef* ex) { + JSObjectRef module; + + JSStringRef shellKey = JSStringCreateWithUTF8CString("__shell__"); + JSValueRef shellAsVal = JSObjectGetProperty(context, globalObj, shellKey, NULL); + JSStringRelease(shellKey); + JSObjectRef shell = JSValueToObject(context, shellAsVal, 0); + JSCShell *_this = (JSCShell*) (long) JSObjectGetPrivate(shell); + + if (argc > 0) + { + JSStringRef string = JSValueToStringCopy(context, args[0], NULL); + size_t numChars = JSStringGetMaximumUTF8CStringSize(string); + char *stringUTF8 = new char[numChars]; + JSStringGetUTF8CString(string, stringUTF8, numChars); + + std::string modulePath(stringUTF8); + module = _this->Import(modulePath); + + delete[] stringUTF8; + } + + if (module) { + return module; + } else { + printf("Ooops.\n"); + return JSValueMakeUndefined(context); + } +} + +JSObjectRef JSCShell::Import(const std::string& module_path) { + + HANDLE library; + std::string module_name = LoadModule(module_path, &library); + + if (library == 0) { + printf("Could not load module."); + return 0; + } + + std::string symname = std::string(module_name).append("_initialize"); + + JSCIntializer init_function = reinterpret_cast((long) LOAD_SYMBOL(library, symname.c_str())); + if(init_function == 0) { + printf("Could not find module's initializer function."); + return 0; + } + + JSObjectRef module; + init_function(context, &module); + + return module; +} + bool JSCShell::RegisterFunction(JSGlobalContextRef context, JSObjectRef object, const char* functionName, JSObjectCallAsFunctionCallback callback) { JSStringRef js_functionName = JSStringCreateWithUTF8CString(functionName); @@ -131,12 +187,12 @@ bool JSCShell::RegisterFunction(JSGlobalContextRef context, JSObjectRef object, return true; } -void JSCShell::PrintError(JSContextRef ctx, JSValueRef err, const std::string& name) { +void JSCShell::PrintError(JSContextRef ctx, JSValueRef err) { char *buffer; + size_t length; JSStringRef string = JSValueToStringCopy(ctx, err, 0); - size_t length = JSStringGetLength(string); - + length = JSStringGetLength(string); buffer = new char[length+1]; JSStringGetUTF8CString(string, buffer, length+1); std::string errMsg(buffer); @@ -150,14 +206,22 @@ void JSCShell::PrintError(JSContextRef ctx, JSValueRef err, const std::string& n return; } - // Note: usually you would also retrieve the property "sourceURL" - // though, it happened that this was always "" + JSStringRef sourceURLKey = JSStringCreateWithUTF8CString("sourceURL"); + JSStringRef sourceURLStr = JSValueToStringCopy(ctx, JSObjectGetProperty(ctx, errObj, sourceURLKey, 0), 0); + length = JSStringGetLength(sourceURLStr); + buffer = new char[length+1]; + JSStringGetUTF8CString(sourceURLStr, buffer, length+1); + std::string sourceURL(buffer); + delete[] buffer; + JSStringRelease(sourceURLStr); + JSStringRelease(sourceURLKey); + JSStringRef lineKey = JSStringCreateWithUTF8CString("line"); JSValueRef jsLine = JSObjectGetProperty(ctx, errObj, lineKey, 0); int line = (int) JSValueToNumber(ctx, jsLine, 0); JSStringRelease(lineKey); - std::cerr << name << ":" << line << ":" << errMsg << std::endl; + std::cerr << sourceURL << ":" << line << ":" << errMsg << std::endl; } JSShell* JSCShell_Create() { diff --git a/Tools/javascript/v8_shell.cxx b/Tools/javascript/v8_shell.cxx index e2ab1541d..8a571e0f1 100755 --- a/Tools/javascript/v8_shell.cxx +++ b/Tools/javascript/v8_shell.cxx @@ -9,7 +9,7 @@ #include "js_shell.h" -typedef int (*V8ExtensionRegistrar) (v8::Handle); +typedef int (*V8ExtensionInitializer) (v8::Handle module); class V8Shell: public JSShell { @@ -25,36 +25,33 @@ public: protected: - virtual bool RegisterModule(HANDLE library, const std::string& module_name); - virtual bool InitializeEngine(); - virtual bool ExecuteScript(const std::string& source, const std::string& name); + virtual bool ExecuteScript(const std::string& source, const std::string& scriptPath); virtual bool DisposeEngine(); private: + v8::Handle Import(const std::string& moduleName); + v8::Persistent CreateShellContext(); void ReportException(v8::TryCatch* handler); static v8::Handle Print(const v8::Arguments& args); + static v8::Handle Require(const v8::Arguments& args); + static v8::Handle Quit(const v8::Arguments& args); static v8::Handle Version(const v8::Arguments& args); static const char* ToCString(const v8::String::Utf8Value& value); - void ExtendEngine(); - protected: - std::vector module_initializers; - v8::Persistent context; - }; #ifdef __GNUC__ @@ -73,16 +70,6 @@ V8Shell::~V8Shell() { v8::V8::Dispose(); } -bool V8Shell::RegisterModule(HANDLE library, const std::string& module_name) { - std::string symname = std::string(module_name).append("_initialize"); - - V8ExtensionRegistrar init_function = reinterpret_cast((long) LOAD_SYMBOL(library, symname.c_str())); - if(init_function == 0) return false; - - module_initializers.push_back(init_function); - return true; -} - bool V8Shell::RunScript(const std::string& scriptPath) { if (!context.IsEmpty()) { @@ -97,9 +84,16 @@ bool V8Shell::RunScript(const std::string& scriptPath) { return false; } context->Enter(); + //v8::Context::Scope context_scope(context); + v8::HandleScope scope; - v8::Context::Scope context_scope(context); - ExtendEngine(); + // Store a pointer to this shell for later use + v8::Handle global = context->Global(); + v8::Local __shell__ = v8::External::New((void*) (long) this); + global->SetHiddenValue(v8::String::New("__shell__"), __shell__); + + // Node.js compatibility: make `print` available as `console.log()` + ExecuteScript("var console = {}; console.log = print;", ""); if(!ExecuteScript(source, scriptPath)) { return false; @@ -127,7 +121,8 @@ bool V8Shell::RunShell() { context->Enter(); v8::Context::Scope context_scope(context); - ExtendEngine(); + + ExecuteScript("var console = {}; console.log = print;", ""); static const int kBufferSize = 1024; while (true) { @@ -152,45 +147,26 @@ bool V8Shell::InitializeEngine() { return true; } -void V8Shell::ExtendEngine() { - - v8::HandleScope scope; - v8::Local global = context->Global(); - - // register extensions - for(std::vector::iterator it=module_initializers.begin(); - it != module_initializers.end(); ++it) { - (*it)(global); - } - -} - bool V8Shell::ExecuteScript(const std::string& source, const std::string& name) { v8::HandleScope handle_scope; v8::TryCatch try_catch; v8::Handle script = v8::Script::Compile(v8::String::New(source.c_str()), v8::String::New(name.c_str())); + + // Stop if script is empty if (script.IsEmpty()) { // Print errors that happened during compilation. ReportException(&try_catch); return false; + } + + v8::Handle result = script->Run(); + + // Print errors that happened during execution. + if (try_catch.HasCaught()) { + ReportException(&try_catch); + return false; } else { - v8::Handle result = script->Run(); - if (result.IsEmpty()) { - assert(try_catch.HasCaught()); - // Print errors that happened during execution. - ReportException(&try_catch); - return false; - } else { - assert(!try_catch.HasCaught()); - if (!result->IsUndefined()) { - // If all went well and the result wasn't undefined then print - // the returned value. - //v8::String::Utf8Value str(result); - //const char* cstr = V8Shell::ToCString(str); - //printf("%s\n", cstr); - } - return true; - } + return true; } } @@ -207,6 +183,7 @@ v8::Persistent V8Shell::CreateShellContext() { // Bind global functions global->Set(v8::String::New("print"), v8::FunctionTemplate::New(V8Shell::Print)); global->Set(v8::String::New("quit"), v8::FunctionTemplate::New(V8Shell::Quit)); + global->Set(v8::String::New("require"), v8::FunctionTemplate::New(V8Shell::Require)); global->Set(v8::String::New("version"), v8::FunctionTemplate::New(V8Shell::Version)); v8::Persistent _context = v8::Context::New(NULL, global); @@ -214,6 +191,26 @@ v8::Persistent V8Shell::CreateShellContext() { return _context; } +v8::Handle V8Shell::Import(const std::string& module_path) +{ + v8::HandleScope scope; + + HANDLE library; + std::string module_name = LoadModule(module_path, &library); + + std::string symname = std::string(module_name).append("_initialize"); + + V8ExtensionInitializer init_function = reinterpret_cast((long) LOAD_SYMBOL(library, symname.c_str())); + + if(init_function == 0) { + printf("Could not find initializer function."); + return v8::Undefined(); + } + + v8::Local module = v8::Object::New(); + init_function(module); + return scope.Close(module); +} v8::Handle V8Shell::Print(const v8::Arguments& args) { bool first = true; @@ -233,6 +230,27 @@ v8::Handle V8Shell::Print(const v8::Arguments& args) { return v8::Undefined(); } +v8::Handle V8Shell::Require(const v8::Arguments& args) { + v8::HandleScope scope; + + if (args.Length() != 1) { + printf("Illegal arguments for `require`"); + }; + + v8::String::Utf8Value str(args[0]); + const char* cstr = V8Shell::ToCString(str); + std::string moduleName(cstr); + + v8::Local global = v8::Context::GetCurrent()->Global(); + v8::Local hidden = global->GetHiddenValue(v8::String::New("__shell__")); + v8::Local __shell__ = v8::Local::Cast(hidden); + V8Shell* _this = (V8Shell*) (long) __shell__->Value(); + + v8::Handle module = _this->Import(moduleName); + + return scope.Close(module); +} + v8::Handle V8Shell::Quit(const v8::Arguments& args) { int exit_code = args[0]->Int32Value(); fflush(stdout);