From 0f557ff24f280bfb7c04372baa53172eb576071d Mon Sep 17 00:00:00 2001 From: "Jay Freeman (saurik)" Date: Mon, 21 Dec 2015 06:14:20 -0800 Subject: [PATCH] Support requiring simple modules installed by npm. --- .gitignore | 1 + Execute.cpp | 97 ++++++++++++------------- libcycript.cy | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 637968d..cabf90a 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ Cycript.osx Cycript.lib DerivedCoreProperties.txt PropList.txt +node_modules diff --git a/Execute.cpp b/Execute.cpp index c6812a2..e575f94 100644 --- a/Execute.cpp +++ b/Execute.cpp @@ -2042,63 +2042,62 @@ static const char *CYPoolLibraryPath(CYPool &pool) { return lib; } -static JSValueRef require(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry { +static JSValueRef require_callAsFunction(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry { _assert(count == 1); CYPool pool; - const char *lib(CYPoolLibraryPath(pool)); - - CYJSString property("exports"); - JSObjectRef module; - const char *name(CYPoolCString(pool, context, arguments[0])); - const char *path(pool.strcat(lib, "/cycript0.9/", name, ".cy", NULL)); - - CYJSString key(path); - JSObjectRef modules(CYGetCachedObject(context, CYJSString("modules"))); - JSValueRef cache(CYGetProperty(context, modules, key)); - - if (!JSValueIsUndefined(context, cache)) - module = CYCastJSObject(context, cache); - else { - CYUTF8String code(CYPoolFileUTF8String(pool, path)); - - if (code.data == NULL) { - if (strchr(name, '/') == NULL && ( + if (strchr(name, '/') == NULL && ( #ifdef __APPLE__ - dlopen(pool.strcat("/System/Library/Frameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL || - dlopen(pool.strcat("/System/Library/PrivateFrameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL || + dlopen(pool.strcat("/System/Library/Frameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL || + dlopen(pool.strcat("/System/Library/PrivateFrameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL || #endif - false)) - return CYJSUndefined(NULL); - - CYThrow("Can't find module: %s", name); - } + false)) + return CYJSUndefined(context); - module = JSObjectMake(context, NULL, NULL); - CYSetProperty(context, modules, key, module); + JSObjectRef resolve(CYCastJSObject(context, CYGetProperty(context, object, CYJSString("resolve")))); + CYJSString path(context, CYCallAsFunction(context, resolve, NULL, 1, arguments)); - JSObjectRef exports(JSObjectMake(context, NULL, NULL)); - CYSetProperty(context, module, property, exports); - - std::stringstream wrap; - wrap << "(function (exports, require, module) { " << code << "\n});"; - code = CYPoolCode(pool, *wrap.rdbuf()); + CYJSString property("exports"); - JSValueRef value(_jsccall(JSEvaluateScript, context, CYJSString(code), NULL, NULL, 0)); - JSObjectRef function(CYCastJSObject(context, value)); + JSObjectRef modules(CYGetCachedObject(context, CYJSString("modules"))); + JSValueRef cache(CYGetProperty(context, modules, path)); - JSValueRef arguments[3] = { exports, JSObjectMakeFunctionWithCallback(context, CYJSString("require"), &require), module }; - CYCallAsFunction(context, function, NULL, 3, arguments); + JSValueRef result; + if (!JSValueIsUndefined(context, cache)) { + JSObjectRef module(CYCastJSObject(context, cache)); + result = CYGetProperty(context, module, property); + } else { + CYUTF8String code(CYPoolFileUTF8String(pool, CYPoolCString(pool, context, path))); + _assert(code.data != NULL); + + size_t length(strlen(name)); + if (length >= 5 && strcmp(name + length - 5, ".json") == 0) { + JSObjectRef JSON(CYGetCachedObject(context, CYJSString("JSON"))); + JSObjectRef parse(CYCastJSObject(context, CYGetProperty(context, JSON, CYJSString("parse")))); + JSValueRef arguments[1] = { CYCastJSValue(context, CYJSString(code)) }; + result = CYCallAsFunction(context, parse, JSON, 1, arguments); + } else { + JSObjectRef module(JSObjectMake(context, NULL, NULL)); + CYSetProperty(context, modules, path, module); + + JSObjectRef exports(JSObjectMake(context, NULL, NULL)); + CYSetProperty(context, module, property, exports); + + std::stringstream wrap; + wrap << "(function (exports, require, module, __filename) { " << code << "\n});"; + code = CYPoolCode(pool, *wrap.rdbuf()); + + JSValueRef value(_jsccall(JSEvaluateScript, context, CYJSString(code), NULL, NULL, 0)); + JSObjectRef function(CYCastJSObject(context, value)); + + JSValueRef arguments[4] = { exports, object, module, CYCastJSValue(context, path) }; + CYCallAsFunction(context, function, NULL, 4, arguments); + result = CYGetProperty(context, module, property); + } } - JSObjectRef exports(CYCastJSObject(context, CYGetProperty(context, module, property))); - - CYJSString _default("default"); - if (JSValueIsUndefined(context, CYGetProperty(context, exports, _default))) - CYSetProperty(context, exports, _default, exports, kJSPropertyAttributeDontEnum); - - return exports; + return result; } CYCatch(NULL) } static bool CYRunScript(JSGlobalContextRef context, const char *path) { @@ -2146,6 +2145,9 @@ extern "C" void CYSetupContext(JSGlobalContextRef context) { JSObjectRef Function_prototype(CYCastJSObject(context, CYGetProperty(context, Function, prototype_s))); CYSetProperty(context, cy, CYJSString("Function_prototype"), Function_prototype); + JSObjectRef JSON(CYCastJSObject(context, CYGetProperty(context, global, CYJSString("JSON")))); + CYSetProperty(context, cy, CYJSString("JSON"), JSON); + JSObjectRef Number(CYCastJSObject(context, CYGetProperty(context, global, CYJSString("Number")))); CYSetProperty(context, cy, CYJSString("Number"), Number); @@ -2214,13 +2216,14 @@ extern "C" void CYSetupContext(JSGlobalContextRef context) { JSObjectRef System(JSObjectMake(context, NULL, NULL)); CYSetProperty(context, cy, CYJSString("System"), System); - CYSetProperty(context, all, CYJSString("require"), &require, kJSPropertyAttributeDontEnum); + CYSetProperty(context, all, CYJSString("require"), &require_callAsFunction, kJSPropertyAttributeDontEnum); CYSetProperty(context, global, CYJSString("system"), System); CYSetProperty(context, System, CYJSString("args"), CYJSNull(context)); - //CYSetProperty(context, System, CYJSString("global"), global); CYSetProperty(context, System, CYJSString("print"), &System_print); + CYSetProperty(context, global, CYJSString("global"), global); + #ifdef __APPLE__ if (&JSWeakObjectMapCreate != NULL) { JSWeakObjectMapRef weak(JSWeakObjectMapCreate(context, NULL, &CYDestroyWeak)); diff --git a/libcycript.cy b/libcycript.cy index e9a612f..6a751aa 100644 --- a/libcycript.cy +++ b/libcycript.cy @@ -1,3 +1,9 @@ +var process = { + env: {}, +}; + +(function() { + let $cy_set = function(object, properties) { for (const name in properties) Object.defineProperty(object, name, { @@ -8,6 +14,17 @@ let $cy_set = function(object, properties) { }); }; +const F_OK = 0; +const X_OK = (1<<0); +const W_OK = (1<<1); +const R_OK = (1<<2); + +typedef long size_t; + +extern "C" int access(const char *path, int amode); +extern "C" char *getcwd(char *buf, size_t size); +extern "C" int getpid(); + $cy_set(Date.prototype, { toCYON: function() { return `new ${this.constructor.name}(${this.toUTCString().toCYON()})`; @@ -19,3 +36,177 @@ $cy_set(Error.prototype, { return `new ${this.constructor.name}(${this.message.toCYON()})`; }, }); + +let IsFile = function(path) { + // XXX: this doesn't work on symlinks, but I don't want to fix stat :/ + return access(path, F_OK) == 0 && access(path + '/', F_OK) == -1; +}; + +let StartsWith = function(lhs, rhs) { + return lhs.substring(0, rhs.length) == rhs; +}; + +let ResolveFile = function(exact, name) { + if (exact && IsFile(name)) + return name; + for (let suffix of ['.js', '.json']) + if (IsFile(name + suffix)) + return name + suffix; + return null; +}; + + +let GetLibraryPath = function() { + let handle = dlopen("/usr/lib/libcycript.dylib", RTLD_NOLOAD); + if (handle == null) + return null; + + try { + let CYHandleServer = dlsym(handle, "CYHandleServer"); + if (CYHandleServer == null) + return null; + + let info = new Dl_info; + if (dladdr(CYHandleServer, info) == 0) + return null; + + let path = info->dli_fname; + let slash = path.lastIndexOf('/'); + if (slash == -1) + return null; + + return path.substr(0, slash); + } finally { + dlclose(handle); + } +}; + +let ResolveFolder = function(name) { + if (access(name + '/', F_OK) == -1) + return null; + + if (IsFile(name + "/package.json")) { + let package = require(name + "/package.json"); + let path = ResolveFile(true, name + "/" + package.main); + if (path != null) + return path; + } + + return ResolveFile(false, name + "/index"); +}; + +let ResolveEither = function(name) { + let path = null; + if (path == null) + path = ResolveFile(true, name); + if (path == null) + path = ResolveFolder(name); + return path; +}; + +require.resolve = function(name) { + if (StartsWith(name, '/')) { + let path = ResolveEither(name); + if (path != null) + return path; + } else { + let cwd = new (typedef char[1024]); + cwd = getcwd(cwd, cwd.length).toString(); + cwd = cwd.split('/'); + + if (StartsWith(name, './') || StartsWith(name, '../')) { + let path = ResolveEither(cwd + '/' + name); + if (path != null) + return path; + } else { + for (let i = cwd.length; i != 0; --i) { + let modules = cwd.slice(0, i).concat("node_modules").join('/'); + let path = ResolveEither(modules + "/" + name); + if (path != null) + return path; + } + + let library = GetLibraryPath(); + let path = ResolveFile(true, library + "/cycript0.9/" + name + ".cy"); + if (path != null) + return path; + } + } + + throw new Error("Cannot find module '" + name + "'"); +}; + +var bindings = {}; + +process.binding = function(name) { + let binding = bindings[name]; + if (typeof binding != 'undefined') + return binding; + + switch (name) { + case 'buffer': binding = { + setupBufferJS() { + }, + }; break; + + case 'cares_wrap': binding = { + }; break; + + case 'constants': binding = { + }; break; + + case 'fs': binding = { + FSInitialize() { + }, + + lstat() { + throw new Error("stat(" + arguments[0] + ")"); + }, + }; break; + + case 'pipe_wrap': binding = { + }; break; + + case 'smalloc': binding = { + alloc() { + }, + }; break; + + case 'stream_wrap': binding = { + }; break; + + case 'tcp_wrap': binding = { + }; break; + + case 'timer_wrap': binding = { + kOnTimeout: 0, + Timer: { + }, + }; break; + + case 'tty_wrap': binding = { + }; break; + + case 'uv': binding = { + }; break; + + default: + throw new Error('No such module: ' + name); + } + + bindings[name] = binding; + return binding; +}; + +let environ = *(typedef char ***)(dlsym(RTLD_DEFAULT, "environ")); +for (let i = 0; environ[i] != null; ++i) { + let assign = environ[i]; + let equal = assign.indexOf('='); + let name = assign.substr(0, equal); + let value = assign.substr(equal + 1); + process.env[name.toString()] = value; +} + +process.pid = getpid(); + +})(); -- 2.45.2