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