From 242161bef2e3631d315f8da8a62c088bdc100c87 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Thu, 24 Jun 2010 05:17:05 -0500 Subject: [PATCH] Added new API to Script, and implemented it in the REPL --- lib/repl.js | 41 +++---- src/node.cc | 7 +- src/node_cares.cc | 8 +- src/node_child_process.cc | 2 +- src/node_file.cc | 2 +- src/node_script.cc | 125 ++++++++++++++++++++-- src/node_script.h | 23 +++- test/simple/test-repl.js | 2 +- test/simple/test-script-context.js | 20 ++++ test/simple/test-script-static-context.js | 18 ++++ 10 files changed, 211 insertions(+), 37 deletions(-) create mode 100644 test/simple/test-script-context.js create mode 100644 test/simple/test-script-static-context.js diff --git a/lib/repl.js b/lib/repl.js index d0fefde854..ecb75f516b 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -10,13 +10,14 @@ // repl.start("node via TCP socket> ", socket); // }).listen(5001); -// repl.start("node > ").scope.foo = "stdin is fun"; // expose foo to repl scope +// repl.start("node > ").context.foo = "stdin is fun"; // expose foo to repl context var sys = require('sys'); -var evalcx = process.binding('evals').Script.runInNewContext; +var Script = process.binding('evals').Script; +var evalcx = Script.runInContext; var path = require("path"); var rl = require('readline'); -var scope; +var context; function cwdRequire (id) { if (id.match(/^\.\.\//) || id.match(/^\.\//)) { @@ -28,11 +29,11 @@ Object.keys(require).forEach(function (k) { cwdRequire[k] = require[k]; }); -function setScope (self) { - scope = {}; - for (var i in global) scope[i] = global[i]; - scope.module = module; - scope.require = cwdRequire; +function resetContext() { + context = Script.createContext(); + for (var i in global) context[i] = global[i]; + context.module = module; + context.require = cwdRequire; } @@ -41,8 +42,8 @@ exports.writer = sys.inspect; function REPLServer(prompt, stream) { var self = this; - if (!scope) setScope(); - self.scope = scope; + if (!context) resetContext(); + self.context = context; self.buffered_cmd = ''; self.stream = stream || process.openStdin(); @@ -70,10 +71,10 @@ function REPLServer(prompt, stream) { // This try is for determining if the command is complete, or should // continue onto the next line. try { - // Use evalcx to supply the global scope - var ret = evalcx(self.buffered_cmd, scope, "repl"); + // Use evalcx to supply the global context + var ret = evalcx(self.buffered_cmd, context, "repl"); if (ret !== undefined) { - scope._ = ret; + context._ = ret; flushed = self.stream.write(exports.writer(ret) + "\n"); } @@ -150,9 +151,9 @@ REPLServer.prototype.parseREPLKeyword = function (cmd) { self.displayPrompt(); return true; case ".clear": - self.stream.write("Clearing Scope...\n"); + self.stream.write("Clearing context...\n"); self.buffered_cmd = ''; - setScope(); + resetContext(); self.displayPrompt(); return true; case ".exit": @@ -160,7 +161,7 @@ REPLServer.prototype.parseREPLKeyword = function (cmd) { return true; case ".help": self.stream.write(".break\tSometimes you get stuck in a place you can't get out... This will get you out.\n"); - self.stream.write(".clear\tBreak, and also clear the local scope.\n"); + self.stream.write(".clear\tBreak, and also clear the local context.\n"); self.stream.write(".exit\tExit the prompt\n"); self.stream.write(".help\tShow repl options\n"); self.displayPrompt(); @@ -180,21 +181,21 @@ function trimWhitespace (cmd) { /** * Converts commands that use var and function () to use the - * local exports.scope when evaled. This provides a local scope + * local exports.context when evaled. This provides a local context * on the REPL. * * @param {String} cmd The cmd to convert * @returns {String} The converted command */ -REPLServer.prototype.convertToScope = function (cmd) { +REPLServer.prototype.convertToContext = function (cmd) { var self = this, matches, scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m, scopeFunc = /^\s*function\s*([_\w\$]+)/; - // Replaces: var foo = "bar"; with: self.scope.foo = bar; + // Replaces: var foo = "bar"; with: self.context.foo = bar; matches = scopeVar.exec(cmd); if (matches && matches.length === 3) { - return "self.scope." + matches[1] + matches[2]; + return "self.context." + matches[1] + matches[2]; } // Replaces: function foo() {}; with: foo = function foo() {}; diff --git a/src/node.cc b/src/node.cc index 3fc90cfccf..ea7de5fcd8 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1631,6 +1631,7 @@ static Handle Binding(const Arguments& args) { exports = binding_cache->Get(module)->ToObject(); } else { exports = Object::New(); + node::Context::Initialize(exports); node::Script::Initialize(exports); binding_cache->Set(module, exports); } @@ -1685,7 +1686,7 @@ static void Load(int argc, char *argv[]) { process = Persistent::New(process_template->GetFunction()->NewInstance()); // Add a reference to the global object - Local global = Context::GetCurrent()->Global(); + Local global = v8::Context::GetCurrent()->Global(); process->Set(String::NewSymbol("global"), global); // process.version @@ -1987,8 +1988,8 @@ int main(int argc, char *argv[]) { } // Create the one and only Context. - Persistent context = Context::New(); - Context::Scope context_scope(context); + Persistent context = v8::Context::New(); + v8::Context::Scope context_scope(context); atexit(node::AtExit); diff --git a/src/node_cares.cc b/src/node_cares.cc index 892f64863c..4b5077661d 100644 --- a/src/node_cares.cc +++ b/src/node_cares.cc @@ -223,7 +223,7 @@ static void ResolveError(Persistent &cb, int status) { TryCatch try_catch; - cb->Call(Context::GetCurrent()->Global(), 1, &e); + cb->Call(v8::Context::GetCurrent()->Global(), 1, &e); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -251,7 +251,7 @@ static void HostByNameCb(void *data, Local argv[2] = { Local::New(Null()), addresses}; - (*cb)->Call(Context::GetCurrent()->Global(), 2, argv); + (*cb)->Call(v8::Context::GetCurrent()->Global(), 2, argv); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -281,7 +281,7 @@ static void HostByAddrCb(void *data, Local argv[2] = { Local::New(Null()), names }; - (*cb)->Call(Context::GetCurrent()->Global(), 2, argv); + (*cb)->Call(v8::Context::GetCurrent()->Global(), 2, argv); if (try_catch.HasCaught()) { FatalException(try_catch); @@ -294,7 +294,7 @@ static void HostByAddrCb(void *data, static void cb_call(Persistent &cb, int argc, Local *argv) { TryCatch try_catch; - cb->Call(Context::GetCurrent()->Global(), argc, argv); + cb->Call(v8::Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { FatalException(try_catch); diff --git a/src/node_child_process.cc b/src/node_child_process.cc index 72787f0ddf..8a0c32ca25 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -156,7 +156,7 @@ Handle ChildProcess::Kill(const Arguments& args) { sig = args[0]->Int32Value(); } else if (args[0]->IsString()) { Local signame = args[0]->ToString(); - Local process = Context::GetCurrent()->Global(); + Local process = v8::Context::GetCurrent()->Global(); Local node_obj = process->Get(String::NewSymbol("process"))->ToObject(); Local sig_v = node_obj->Get(signame); diff --git a/src/node_file.cc b/src/node_file.cc index dde27402df..a2ec39858e 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -145,7 +145,7 @@ static int After(eio_req *req) { TryCatch try_catch; - (*callback)->Call(Context::GetCurrent()->Global(), argc, argv); + (*callback)->Call(v8::Context::GetCurrent()->Global(), argc, argv); if (try_catch.HasCaught()) { FatalException(try_catch); diff --git a/src/node_script.cc b/src/node_script.cc index 78136dd791..ab38199a9d 100644 --- a/src/node_script.cc +++ b/src/node_script.cc @@ -7,6 +7,52 @@ using namespace v8; using namespace node; +Persistent node::Context::constructor_template; + +void +node::Context::Initialize (Handle target) +{ + HandleScope scope; + + Local t = FunctionTemplate::New(node::Context::New); + constructor_template = Persistent::New(t); + constructor_template->InstanceTemplate()->SetInternalFieldCount(1); + constructor_template->SetClassName(String::NewSymbol("Context")); + + target->Set(String::NewSymbol("Context"), constructor_template->GetFunction()); +} + +Handle +node::Context::New (const Arguments& args) +{ + HandleScope scope; + + node::Context *t = new node::Context(); + t->Wrap(args.This()); + + return args.This(); +} + +node::Context::~Context() { + _context.Dispose(); +} + +Local +node::Context::NewInstance() +{ + Local context = constructor_template->GetFunction()->NewInstance(); + node::Context *nContext = ObjectWrap::Unwrap(context); + nContext->_context = v8::Context::New(); + return context; +} + +v8::Persistent +node::Context::GetV8Context() +{ + return _context; +} + + Persistent node::Script::constructor_template; void @@ -19,8 +65,12 @@ node::Script::Initialize (Handle target) constructor_template->InstanceTemplate()->SetInternalFieldCount(1); constructor_template->SetClassName(String::NewSymbol("Script")); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "createContext", node::Script::CreateContext); + NODE_SET_PROTOTYPE_METHOD(constructor_template, "runInContext", node::Script::RunInContext); NODE_SET_PROTOTYPE_METHOD(constructor_template, "runInThisContext", node::Script::RunInThisContext); NODE_SET_PROTOTYPE_METHOD(constructor_template, "runInNewContext", node::Script::RunInNewContext); + NODE_SET_METHOD(constructor_template, "createContext", node::Script::CreateContext); + NODE_SET_METHOD(constructor_template, "runInContext", node::Script::CompileRunInContext); NODE_SET_METHOD(constructor_template, "runInThisContext", node::Script::CompileRunInThisContext); NODE_SET_METHOD(constructor_template, "runInNewContext", node::Script::CompileRunInNewContext); @@ -44,6 +94,37 @@ node::Script::~Script() { } +Handle +node::Script::CreateContext (const Arguments& args) +{ + HandleScope scope; + + Local context = node::Context::NewInstance(); + + if (args.Length() > 0) { + + Local sandbox = args[0]->ToObject(); + Local keys = sandbox->GetPropertyNames(); + + for (int i = 0; i < keys->Length(); i++) { + Handle key = keys->Get(Integer::New(i))->ToString(); + Handle value = sandbox->Get(key); + context->Set(key, value); + } + } + + + return scope.Close(context); +} + +Handle +node::Script::RunInContext (const Arguments& args) +{ + return + node::Script::EvalMachine(args); +} + + Handle node::Script::RunInThisContext (const Arguments& args) { @@ -59,6 +140,14 @@ node::Script::RunInNewContext(const Arguments& args) { } +Handle +node::Script::CompileRunInContext (const Arguments& args) +{ + return + node::Script::EvalMachine(args); +} + + Handle node::Script::CompileRunInThisContext (const Arguments& args) { @@ -91,29 +180,50 @@ Handle node::Script::EvalMachine(const Arguments& args) { )); } + const int sbIndex = iFlag == compileCode ? 1 : 0; + if (cFlag == userContext && args.Length() < (sbIndex + 1)) { + return ThrowException(Exception::TypeError( + String::New("needs a 'context' argument.") + )); + } + + Local code; if (iFlag == compileCode) { code = args[0]->ToString(); } Local sandbox; - const int sbIndex = iFlag == compileCode ? 1 : 0; if (cFlag == newContext) { sandbox = args.Length() > sbIndex ? args[sbIndex]->ToObject() : Object::New(); } + else if (cFlag == userContext) { + sandbox = args[sbIndex]->ToObject(); + } const int fnIndex = sbIndex + (cFlag == newContext ? 1 : 0); Local filename = args.Length() > fnIndex ? args[fnIndex]->ToString() : String::New("evalmachine."); - Persistent context; + Persistent context; Local keys; unsigned int i; if (cFlag == newContext) { // Create the new context - context = Context::New(); + context = v8::Context::New(); - // Enter and compile script + } else if (cFlag == userContext) { + // Use the passed in context + Local contextArg = args[sbIndex]->ToObject(); + node::Context *nContext = ObjectWrap::Unwrap(sandbox); + context = nContext->GetV8Context(); + } + + // New and user context share code. DRY it up. + if (cFlag == userContext || cFlag == newContext) { + + // Enter the context context->Enter(); - // Copy objects from global context, to our brand new context + // Copy everything from the passed in sandbox (either the persistent + // context for runInContext(), or the sandbox arg to runInNewContext()). keys = sandbox->GetPropertyNames(); for (i = 0; i < keys->Length(); i++) { @@ -167,7 +277,7 @@ Handle node::Script::EvalMachine(const Arguments& args) { } if (result.IsEmpty()) { return try_catch.ReThrow(); - } else if (cFlag == newContext) { + } else if (cFlag == userContext || cFlag == newContext) { // success! copy changes back onto the sandbox object. keys = context->Global()->GetPropertyNames(); for (i = 0; i < keys->Length(); i++) { @@ -183,6 +293,9 @@ Handle node::Script::EvalMachine(const Arguments& args) { context->DetachGlobal(); context->Exit(); context.Dispose(); + } else if (cFlag == userContext) { + // Exit the passed in context. + context->Exit(); } return result == args.This() ? result : scope.Close(result); diff --git a/src/node_script.h b/src/node_script.h index edf51c36bd..8cac141644 100644 --- a/src/node_script.h +++ b/src/node_script.h @@ -8,12 +8,30 @@ namespace node { +class Context : ObjectWrap { + public: + static void Initialize (v8::Handle target); + static v8::Handle New (const v8::Arguments& args); + + v8::Persistent GetV8Context(); + static v8::Local NewInstance(); + + protected: + + static v8::Persistent constructor_template; + + Context () : ObjectWrap () {} + ~Context(); + + v8::Persistent _context; +}; + class Script : ObjectWrap { public: static void Initialize (v8::Handle target); enum EvalInputFlags { compileCode, unwrapExternal }; - enum EvalContextFlags { thisContext, newContext }; + enum EvalContextFlags { thisContext, newContext, userContext }; enum EvalOutputFlags { returnResult, wrapExternal }; template @@ -26,8 +44,11 @@ class Script : ObjectWrap { ~Script(); static v8::Handle New (const v8::Arguments& args); + static v8::Handle CreateContext (const v8::Arguments& arg); + static v8::Handle RunInContext (const v8::Arguments& args); static v8::Handle RunInThisContext (const v8::Arguments& args); static v8::Handle RunInNewContext (const v8::Arguments& args); + static v8::Handle CompileRunInContext (const v8::Arguments& args); static v8::Handle CompileRunInThisContext (const v8::Arguments& args); static v8::Handle CompileRunInNewContext (const v8::Arguments& args); diff --git a/test/simple/test-repl.js b/test/simple/test-repl.js index b5857a1ffb..6cdcada54f 100644 --- a/test/simple/test-repl.js +++ b/test/simple/test-repl.js @@ -99,7 +99,7 @@ function unix_test() { socket.end(); }); - repl.start(prompt_unix, socket).scope.message = message; + repl.start(prompt_unix, socket).context.message = message; }); server_unix.addListener('listening', function () { diff --git a/test/simple/test-script-context.js b/test/simple/test-script-context.js new file mode 100644 index 0000000000..983006278b --- /dev/null +++ b/test/simple/test-script-context.js @@ -0,0 +1,20 @@ +require("../common"); + +var Script = process.binding('evals').Script; +var script = new Script('"passed";'); + +debug('run in a new empty context'); +var context = script.createContext(); +var result = script.runInContext(context); +assert.equal('passed', result); + +debug('create a new pre-populated context'); +context = script.createContext({'foo': 'bar', 'thing': 'lala'}); +assert.equal('bar', context.foo); +assert.equal('lala', context.thing); + +debug('test updating context'); +script = new Script('foo = 3;'); +result = script.runInContext(context); +assert.equal(3, context.foo); +assert.equal('lala', context.thing); diff --git a/test/simple/test-script-static-context.js b/test/simple/test-script-static-context.js new file mode 100644 index 0000000000..12f3363d3d --- /dev/null +++ b/test/simple/test-script-static-context.js @@ -0,0 +1,18 @@ +require("../common"); + +var Script = process.binding('evals').Script; + +debug('run in a new empty context'); +var context = Script.createContext(); +var result = Script.runInContext('"passed";', context); +assert.equal('passed', result); + +debug('create a new pre-populated context'); +context = Script.createContext({'foo': 'bar', 'thing': 'lala'}); +assert.equal('bar', context.foo); +assert.equal('lala', context.thing); + +debug('test updating context'); +result = Script.runInContext('var foo = 3;', context); +assert.equal(3, context.foo); +assert.equal('lala', context.thing);