Browse Source

repl.js style

v0.7.4-release
Ryan Dahl 14 years ago
parent
commit
c82fe30ca1
  1. 70
      lib/repl.js

70
lib/repl.js

@ -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;

Loading…
Cancel
Save