Added new API to Script, and implemented it in the REPL
authorRuben Rodriguez <cha0s@therealcha0s.net>
Thu, 24 Jun 2010 10:17:05 +0000 (05:17 -0500)
committerRyan Dahl <ry@tinyclouds.org>
Mon, 28 Jun 2010 22:16:26 +0000 (15:16 -0700)
lib/repl.js
src/node.cc
src/node_cares.cc
src/node_child_process.cc
src/node_file.cc
src/node_script.cc
src/node_script.h
test/simple/test-repl.js
test/simple/test-script-context.js [new file with mode: 0644]
test/simple/test-script-static-context.js [new file with mode: 0644]

index d0fefde..ecb75f5 100644 (file)
 //   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 <name>() 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() {};
index 3fc90cf..ea7de5f 100644 (file)
@@ -1631,6 +1631,7 @@ static Handle<Value> 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<Object>::New(process_template->GetFunction()->NewInstance());
 
   // Add a reference to the global object
-  Local<Object> global = Context::GetCurrent()->Global();
+  Local<Object> 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 = Context::New();
-  Context::Scope context_scope(context);
+  Persistent<v8::Context> context = v8::Context::New();
+  v8::Context::Scope context_scope(context);
 
   atexit(node::AtExit);
 
index 892f648..4b50776 100644 (file)
@@ -223,7 +223,7 @@ static void ResolveError(Persistent<Function> &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<Value> argv[2] = { Local<Value>::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<Value> argv[2] = { Local<Value>::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<Function> &cb, int argc, Local<Value> *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);
index 72787f0..8a0c32c 100644 (file)
@@ -156,7 +156,7 @@ Handle<Value> ChildProcess::Kill(const Arguments& args) {
       sig = args[0]->Int32Value();
     } else if (args[0]->IsString()) {
       Local<String> signame = args[0]->ToString();
-      Local<Object> process = Context::GetCurrent()->Global();
+      Local<Object> process = v8::Context::GetCurrent()->Global();
       Local<Object> node_obj = process->Get(String::NewSymbol("process"))->ToObject();
 
       Local<Value> sig_v = node_obj->Get(signame);
index dde2740..a2ec398 100644 (file)
@@ -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);
index 78136dd..ab38199 100644 (file)
@@ -7,6 +7,52 @@
 using namespace v8;
 using namespace node;
 
+Persistent<FunctionTemplate> node::Context::constructor_template;
+
+void
+node::Context::Initialize (Handle<Object> target)
+{
+  HandleScope scope;
+
+  Local<FunctionTemplate> t = FunctionTemplate::New(node::Context::New);
+  constructor_template = Persistent<FunctionTemplate>::New(t);
+  constructor_template->InstanceTemplate()->SetInternalFieldCount(1);
+  constructor_template->SetClassName(String::NewSymbol("Context"));
+
+  target->Set(String::NewSymbol("Context"), constructor_template->GetFunction());
+}
+
+Handle<Value>
+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<Object>
+node::Context::NewInstance()
+{
+  Local<Object> context = constructor_template->GetFunction()->NewInstance();
+  node::Context *nContext = ObjectWrap::Unwrap<node::Context>(context);
+  nContext->_context = v8::Context::New();
+  return context;
+}
+
+v8::Persistent<v8::Context>
+node::Context::GetV8Context()
+{
+       return _context;
+}
+
+
 Persistent<FunctionTemplate> node::Script::constructor_template;
 
 void
@@ -19,8 +65,12 @@ node::Script::Initialize (Handle<Object> 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);
 
@@ -45,6 +95,37 @@ node::Script::~Script() {
 
 
 Handle<Value>
+node::Script::CreateContext (const Arguments& args)
+{
+  HandleScope scope;
+
+  Local<v8::Object> context = node::Context::NewInstance();
+
+  if (args.Length() > 0) {
+
+    Local<Object> sandbox = args[0]->ToObject();
+    Local<Array> keys = sandbox->GetPropertyNames();
+
+    for (int i = 0; i < keys->Length(); i++) {
+      Handle<String> key = keys->Get(Integer::New(i))->ToString();
+      Handle<Value> value = sandbox->Get(key);
+      context->Set(key, value);
+    }
+  }
+
+
+  return scope.Close(context);
+}
+
+Handle<Value>
+node::Script::RunInContext (const Arguments& args)
+{
+  return
+    node::Script::EvalMachine<unwrapExternal, userContext, returnResult>(args);
+}
+
+
+Handle<Value>
 node::Script::RunInThisContext (const Arguments& args)
 {
   return
@@ -60,6 +141,14 @@ node::Script::RunInNewContext(const Arguments& args) {
 
 
 Handle<Value>
+node::Script::CompileRunInContext (const Arguments& args)
+{
+  return
+    node::Script::EvalMachine<compileCode, userContext, returnResult>(args);
+}
+
+
+Handle<Value>
 node::Script::CompileRunInThisContext (const Arguments& args)
 {
   return
@@ -91,29 +180,50 @@ Handle<Value> 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<String> code;
   if (iFlag == compileCode) { code = args[0]->ToString(); }
 
   Local<Object> 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<String> filename = args.Length() > fnIndex ? args[fnIndex]->ToString()
                                              : String::New("evalmachine.<anonymous>");
 
-  Persistent<Context> context;
+  Persistent<v8::Context> context;
   Local<Array> 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<Object> contextArg = args[sbIndex]->ToObject();
+    node::Context *nContext = ObjectWrap::Unwrap<node::Context>(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<Value> 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<Value> 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);
index edf51c3..8cac141 100644 (file)
@@ -8,12 +8,30 @@
 
 namespace node {
 
+class Context : ObjectWrap {
+ public:
+  static void Initialize (v8::Handle<v8::Object> target);
+  static v8::Handle<v8::Value> New (const v8::Arguments& args);
+
+  v8::Persistent<v8::Context> GetV8Context();
+  static v8::Local<v8::Object> NewInstance();
+
+ protected:
+
+  static v8::Persistent<v8::FunctionTemplate> constructor_template;
+
+  Context () : ObjectWrap () {}
+  ~Context();
+
+  v8::Persistent<v8::Context> _context;
+};
+
 class Script : ObjectWrap {
  public:
   static void Initialize (v8::Handle<v8::Object> target);
 
   enum EvalInputFlags { compileCode, unwrapExternal };
-  enum EvalContextFlags { thisContext, newContext };
+  enum EvalContextFlags { thisContext, newContext, userContext };
   enum EvalOutputFlags { returnResult, wrapExternal };
 
   template <EvalInputFlags iFlag, EvalContextFlags cFlag, EvalOutputFlags oFlag>
@@ -26,8 +44,11 @@ class Script : ObjectWrap {
   ~Script();
 
   static v8::Handle<v8::Value> New (const v8::Arguments& args);
+  static v8::Handle<v8::Value> CreateContext (const v8::Arguments& arg);
+  static v8::Handle<v8::Value> RunInContext (const v8::Arguments& args);
   static v8::Handle<v8::Value> RunInThisContext (const v8::Arguments& args);
   static v8::Handle<v8::Value> RunInNewContext (const v8::Arguments& args);
+  static v8::Handle<v8::Value> CompileRunInContext (const v8::Arguments& args);
   static v8::Handle<v8::Value> CompileRunInThisContext (const v8::Arguments& args);
   static v8::Handle<v8::Value> CompileRunInNewContext (const v8::Arguments& args);
 
index b5857a1..6cdcada 100644 (file)
@@ -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 (file)
index 0000000..9830062
--- /dev/null
@@ -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 (file)
index 0000000..12f3363
--- /dev/null
@@ -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);