From c82fe30ca14c815aa4c5d6126dd39ba1f5ea7c6d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sat, 1 Jan 2011 21:41:07 -0800 Subject: [PATCH] repl.js style --- lib/repl.js | 70 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 8e9c036880..aef0e9e2d3 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -20,20 +20,21 @@ */ var util = require('util'); -var Script = process.binding('evals').Script; -var evalcx = Script.runInContext; +var vm = require('vm'); var path = require('path'); var fs = require('fs'); var rl = require('readline'); -var context; +var context; var disableColors = process.env.NODE_DISABLE_COLORS ? true : false; + // hack for require.resolve("./relative") to work properly. module.filename = process.cwd() + '/repl'; + function resetContext() { - context = Script.createContext(); + context = vm.createContext(); for (var i in global) context[i] = global[i]; context.module = module; context.require = require; @@ -43,12 +44,13 @@ function resetContext() { // Can overridden with custom print functions, such as `probe` or `eyes.js` exports.writer = util.inspect; + function REPLServer(prompt, stream) { var self = this; if (!context) resetContext(); if (!exports.repl) exports.repl = this; self.context = context; - self.buffered_cmd = ''; + self.bufferedCommand = ''; self.stream = stream || process.openStdin(); self.prompt = prompt || '> '; @@ -70,9 +72,9 @@ function REPLServer(prompt, stream) { rli.setPrompt(self.prompt); rli.on('SIGINT', function() { - if (self.buffered_cmd && self.buffered_cmd.length > 0) { + if (self.bufferedCommand && self.bufferedCommand.length > 0) { rli.write('\n'); - self.buffered_cmd = ''; + self.bufferedCommand = ''; self.displayPrompt(); } else { rli.close(); @@ -104,7 +106,7 @@ function REPLServer(prompt, stream) { if (!skipCatchall) { // The catchall for errors try { - self.buffered_cmd += cmd + '\n'; + self.bufferedCommand += cmd + '\n'; // This try is for determining if the command is complete, or should // continue onto the next line. try { @@ -117,10 +119,10 @@ function REPLServer(prompt, stream) { try { // First we attempt to eval as expression with parens. // This catches '{a : 1}' properly. - ret = evalcx('(' + self.buffered_cmd + ')', context, 'repl'); + ret = vm.runInContext('(' + self.bufferedCommand + ')', context, 'repl'); } catch (e) { // Now as statement without parens. - ret = evalcx(self.buffered_cmd, context, 'repl'); + ret = vm.runInContext(self.bufferedCommand, context, 'repl'); } if (ret !== undefined) { @@ -128,7 +130,7 @@ function REPLServer(prompt, stream) { self.stream.write(exports.writer(ret) + '\n'); } - self.buffered_cmd = ''; + self.bufferedCommand = ''; } catch (e) { // instanceof doesn't work across context switches. if (!(e && e.constructor && e.constructor.name === 'SyntaxError')) { @@ -148,7 +150,7 @@ function REPLServer(prompt, stream) { } else { self.stream.write(e.toString() + '\n'); } - self.buffered_cmd = ''; + self.bufferedCommand = ''; } } @@ -163,21 +165,30 @@ function REPLServer(prompt, stream) { } exports.REPLServer = REPLServer; + // prompt is a string to print on each line for the prompt, // source is a stream to use for I/O, defaulting to stdin/stdout. exports.start = function(prompt, source) { return new REPLServer(prompt, source); }; + REPLServer.prototype.displayPrompt = function() { - this.rli.setPrompt(this.buffered_cmd.length ? '... ' : this.prompt); + this.rli.setPrompt(this.bufferedCommand.length ? '... ' : this.prompt); this.rli.prompt(); }; + // read a line from the stream, then eval it REPLServer.prototype.readline = function(cmd) { }; + +var requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/; +var simpleExpressionRE = + /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/; + + // Provide a list of completions for the given leading text. This is // given to the readline interface for handling tab completion. // @@ -205,8 +216,8 @@ REPLServer.prototype.complete = function(line) { if (match[1].length > 1) { filter = match[1]; } - } else if (match = - line.match(/\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/)) { + + } else if (match = line.match(requireRE)) { // require('...') //TODO: suggest require.exts be exposed to be introspec registered //extensions? @@ -274,7 +285,6 @@ REPLServer.prototype.complete = function(line) { 'url']; completionGroups.push(builtinLibs); } - } // Handle variable member lookup. // We support simple chained expressions like the following (no function @@ -286,10 +296,8 @@ REPLServer.prototype.complete = function(line) { // spam.eggs.<|> # completions for 'spam.eggs' with filter '' // foo<|> # all scope vars with filter 'foo' // foo.<|> # completions for 'foo' with filter '' - else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) { - var simpleExpressionPat = - /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/; - match = simpleExpressionPat.exec(line); + } else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) { + match = simpleExpressionRE.exec(line); if (line.length === 0 || match) { var expr; completeOn = (match ? match[0] : ''); @@ -333,7 +341,7 @@ REPLServer.prototype.complete = function(line) { } } else { try { - obj = evalcx(expr, this.context, 'repl'); + obj = vm.runInContext(expr, this.context, 'repl'); } catch (e) { //console.log("completion eval error, expr='"+expr+"': "+e); } @@ -386,6 +394,7 @@ REPLServer.prototype.complete = function(line) { } completionGroups = newCompletionGroups; } + if (completionGroups.length) { var uniq = {}; // unique completions across all groups completions = []; @@ -411,13 +420,13 @@ REPLServer.prototype.complete = function(line) { return [completions || [], completeOn]; }; + /** * Used to parse and execute the Node REPL commands. * * @param {keyword} keyword The command entered to check. * @return {Boolean} If true it means don't continue parsing the command. */ - REPLServer.prototype.parseREPLKeyword = function(keyword, rest) { var cmd = this.commands[keyword]; if (cmd) { @@ -427,20 +436,23 @@ REPLServer.prototype.parseREPLKeyword = function(keyword, rest) { return false; }; + REPLServer.prototype.defineCommand = function(keyword, cmd) { - if (typeof cmd === 'function') cmd = {action: cmd}; - else if (typeof cmd.action !== 'function') { + if (typeof cmd === 'function') { + cmd = {action: cmd}; + } else if (typeof cmd.action !== 'function') { throw new Error('bad argument, action must be a function'); } this.commands['.' + keyword] = cmd; }; + function defineDefaultCommands(repl) { // TODO remove me after 0.3.x repl.defineCommand('break', { help: 'Sometimes you get stuck, this gets you out', action: function() { - this.buffered_cmd = ''; + this.bufferedCommand = ''; this.displayPrompt(); } }); @@ -449,7 +461,7 @@ function defineDefaultCommands(repl) { help: 'Break, and also clear the local context', action: function() { this.stream.write('Clearing context...\n'); - this.buffered_cmd = ''; + this.bufferedCommand = ''; resetContext(); this.displayPrompt(); } @@ -475,6 +487,7 @@ function defineDefaultCommands(repl) { }); } + function trimWhitespace(cmd) { var trimmer = /^\s*(.+)\s*$/m, matches = trimmer.exec(cmd); @@ -484,6 +497,7 @@ function trimWhitespace(cmd) { } } + function regexpEscape(s) { return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } @@ -509,9 +523,9 @@ REPLServer.prototype.convertToContext = function(cmd) { } // Replaces: function foo() {}; with: foo = function foo() {}; - matches = scopeFunc.exec(self.buffered_cmd); + matches = scopeFunc.exec(self.bufferedCommand); if (matches && matches.length === 2) { - return matches[1] + ' = ' + self.buffered_cmd; + return matches[1] + ' = ' + self.bufferedCommand; } return cmd;