38
44
function REPLServer(prompt, stream) {
40
if (!scope) setScope();
46
if (!context) resetContext();
47
self.context = context;
42
48
self.buffered_cmd = '';
50
self.stream = stream || process.openStdin();
43
51
self.prompt = prompt || "node> ";
44
self.stream = stream || process.openStdin();
45
self.stream.setEncoding('utf8');
53
var rli = self.rli = rl.createInterface(self.stream, function (text) {
54
return self.complete(text);
56
rli.setPrompt(self.prompt);
46
58
self.stream.addListener("data", function (chunk) {
47
self.readline.call(self, chunk);
62
rli.addListener('line', function (cmd) {
63
cmd = trimWhitespace(cmd);
67
// Check to see if a REPL keyword was used. If it returns true,
68
// display next prompt and return.
69
if (self.parseREPLKeyword(cmd) === true) return;
71
// The catchall for errors
73
self.buffered_cmd += cmd;
74
// This try is for determining if the command is complete, or should
75
// continue onto the next line.
77
// Use evalcx to supply the global context
78
var ret = evalcx(self.buffered_cmd, context, "repl");
79
if (ret !== undefined) {
81
flushed = self.stream.write(exports.writer(ret) + "\n");
84
self.buffered_cmd = '';
86
// instanceof doesn't work across context switches.
87
if (!(e && e.constructor && e.constructor.name === "SyntaxError")) {
92
// On error: Print the error and clear the buffer
94
flushed = self.stream.write(e.stack + "\n");
96
flushed = self.stream.write(e.toString() + "\n");
98
self.buffered_cmd = '';
101
// need to make sure the buffer is flushed before displaying the prompt
102
// again. This is really ugly. Need to have callbacks from
103
// net.Stream.write()
105
self.displayPrompt();
107
self.displayPromptOnDrain = true;
111
self.stream.addListener('drain', function () {
112
if (self.displayPromptOnDrain) {
113
self.displayPrompt();
114
self.displayPromptOnDrain = false;
118
rli.addListener('close', function () {
119
self.stream.destroy();
49
122
self.displayPrompt();
51
124
exports.REPLServer = REPLServer;
59
132
REPLServer.prototype.displayPrompt = function () {
61
self.stream.write(self.buffered_cmd.length ? '... ' : self.prompt);
133
this.rli.setPrompt(this.buffered_cmd.length ? '... ' : this.prompt);
64
137
// read a line from the stream, then eval it
65
138
REPLServer.prototype.readline = function (cmd) {
68
cmd = self.trimWhitespace(cmd);
70
// Check to see if a REPL keyword was used. If it returns true,
71
// display next prompt and return.
72
if (self.parseREPLKeyword(cmd) === true) {
76
// The catchall for errors
78
self.buffered_cmd += cmd;
79
// This try is for determining if the command is complete, or should
80
// continue onto the next line.
82
// Scope the readline with self.scope
83
// with(){} and eval() are considered bad.
84
var ret = evalcx(self.buffered_cmd, scope, "repl");
85
if (ret !== undefined) {
87
self.stream.write(exports.writer(ret) + "\n");
90
self.buffered_cmd = '';
92
// instanceof doesn't work across context switches.
93
if (!(e && e.constructor && e.constructor.name === "SyntaxError")) {
98
// On error: Print the error and clear the buffer
100
self.stream.write(e.stack + "\n");
102
self.stream.write(e.toString() + "\n");
104
self.buffered_cmd = '';
107
self.displayPrompt();
142
* Provide a list of completions for the given leading text. This is
143
* given to the readline interface for handling tab completion.
145
* @param {line} The text (preceding the cursor) to complete
146
* @returns {Array} Two elements: (1) an array of completions; and
147
* (2) the leading text completed.
150
* complete('var foo = sys.')
151
* -> [['sys.print', 'sys.debug', 'sys.log', 'sys.inspect', 'sys.pump'],
154
* Warning: This eval's code like "foo.bar.baz", so it will run property
158
REPLServer.prototype.complete = function (line) {
160
completionGroups = [], // list of completion lists, one for each inheritance "level"
162
match, filter, i, j, group, c;
164
// REPL commands (e.g. ".break").
166
match = line.match(/^\s*(\.\w*)$/);
168
completionGroups.push(['.break', '.clear', '.exit', '.help']);
169
completeOn = match[1];
170
if (match[1].length > 1) {
175
// require('...<Tab>')
176
else if (match = line.match(/\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/)) {
177
//TODO: suggest require.exts be exposed to be introspec registered extensions?
178
//TODO: suggest include the '.' in exts in internal repr: parity with `path.extname`.
179
var exts = [".js", ".node"];
180
var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') + ')$');
182
completeOn = match[1];
183
var subdir = match[2] || "";
184
var filter = match[1];
185
var dir, files, f, name, base, ext, abs, subfiles, s;
187
for (i = 0; i < require.paths.length; i++) {
188
dir = require.paths[i];
189
if (subdir && subdir[0] === '/') {
192
dir = path.join(dir, subdir);
195
files = fs.readdirSync(dir);
199
for (f = 0; f < files.length; f++) {
201
ext = path.extname(name);
202
base = name.slice(0, -ext.length);
203
if (base.match(/-\d+\.\d+(\.\d+)?/) || name === ".npm") {
204
// Exclude versioned names that 'npm' installs.
207
if (exts.indexOf(ext) !== -1) {
208
if (!subdir || base !== "index") {
209
group.push(subdir + base);
212
abs = path.join(dir, name);
214
if (fs.statSync(abs).isDirectory()) {
215
group.push(subdir + name + '/');
216
subfiles = fs.readdirSync(abs);
217
for (s = 0; s < subfiles.length; s++) {
218
if (indexRe.test(subfiles[s])) {
219
group.push(subdir + name);
228
completionGroups.push(group);
232
// Kind of lame that this needs to be updated manually.
233
// Intentionally excluding moved modules: posix, utils.
234
var builtinLibs = ['assert', 'buffer', 'child_process', 'crypto', 'dgram',
235
'dns', 'events', 'file', 'freelist', 'fs', 'http', 'net', 'path',
236
'querystring', 'readline', 'repl', 'string_decoder', 'sys', 'tcp', 'url'];
237
completionGroups.push(builtinLibs);
241
// Handle variable member lookup.
242
// We support simple chained expressions like the following (no function
243
// calls, etc.). That is for simplicity and also because we *eval* that
244
// leading expression so for safety (see WARNING above) don't want to
245
// eval function calls.
247
// foo.bar<|> # completions for 'foo' with filter 'bar'
248
// spam.eggs.<|> # completions for 'spam.eggs' with filter ''
249
// foo<|> # all scope vars with filter 'foo'
250
// foo.<|> # completions for 'foo' with filter ''
251
else if (line.length === 0 || line[line.length-1].match(/\w|\./)) {
252
var simpleExpressionPat = /(([a-zA-Z_]\w*)\.)*([a-zA-Z_]\w*)\.?$/;
253
match = simpleExpressionPat.exec(line);
254
if (line.length === 0 || match) {
256
completeOn = (match ? match[0] : "");
257
if (line.length === 0) {
260
} else if (line[line.length-1] === '.') {
262
expr = match[0].slice(0, match[0].length-1);
264
var bits = match[0].split('.');
266
expr = bits.join('.');
268
//console.log("expression completion: completeOn='"+completeOn+"' expr='"+expr+"'");
270
// Resolve expr and get its completions.
271
var obj, memberGroups = [];
273
completionGroups.push(Object.getOwnPropertyNames(this.context));
274
// Global object properties
275
// (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
276
completionGroups.push(["NaN", "Infinity", "undefined",
277
"eval", "parseInt", "parseFloat", "isNaN", "isFinite", "decodeURI",
278
"decodeURIComponent", "encodeURI", "encodeURIComponent",
279
"Object", "Function", "Array", "String", "Boolean", "Number",
280
"Date", "RegExp", "Error", "EvalError", "RangeError",
281
"ReferenceError", "SyntaxError", "TypeError", "URIError",
283
// Common keywords. Exclude for completion on the empty string, b/c
284
// they just get in the way.
286
completionGroups.push(["break", "case", "catch", "const",
287
"continue", "debugger", "default", "delete", "do", "else", "export",
288
"false", "finally", "for", "function", "if", "import", "in",
289
"instanceof", "let", "new", "null", "return", "switch", "this",
290
"throw", "true", "try", "typeof", "undefined", "var", "void",
291
"while", "with", "yield"])
295
obj = evalcx(expr, this.context, "repl");
297
//console.log("completion eval error, expr='"+expr+"': "+e);
300
if (typeof obj === "object" || typeof obj === "function") {
301
memberGroups.push(Object.getOwnPropertyNames(obj));
303
var p = obj.constructor.prototype; // works for non-objects
307
memberGroups.push(Object.getOwnPropertyNames(p));
308
p = Object.getPrototypeOf(p);
309
// Circular refs possible? Let's guard against that.
316
//console.log("completion error walking prototype chain:" + e);
320
if (memberGroups.length) {
321
for (i = 0; i < memberGroups.length; i++) {
322
completionGroups.push(memberGroups[i].map(function(member) {
323
return expr + '.' + member;
327
filter = expr + '.' + filter;
334
// Filter, sort (within each group), uniq and merge the completion groups.
335
if (completionGroups.length && filter) {
336
var newCompletionGroups = [];
337
for (i = 0; i < completionGroups.length; i++) {
338
group = completionGroups[i].filter(function(elem) {
339
return elem.indexOf(filter) == 0;
342
newCompletionGroups.push(group);
345
completionGroups = newCompletionGroups;
347
if (completionGroups.length) {
348
var uniq = {}; // unique completions across all groups
350
// Completion group 0 is the "closest" (least far up the inheritance chain)
351
// so we put its completions last: to be closest in the REPL.
352
for (i = completionGroups.length - 1; i >= 0; i--) {
353
group = completionGroups[i];
355
for (var j = 0; j < group.length; j++) {
357
if (!uniq.hasOwnProperty(c)) {
362
completions.push(""); // separator btwn groups
364
while (completions.length && completions[completions.length-1] === "") {
369
return [completions || [], completeOn];
111
373
* Used to parse and execute the Node REPL commands.
113
375
* @param {cmd} cmd The command entered to check
114
* @returns {Boolean} If true it means don't continue parsing the command
376
* @returns {Boolean} If true it means don't continue parsing the command
117
379
REPLServer.prototype.parseREPLKeyword = function (cmd) {
122
384
self.buffered_cmd = '';
123
385
self.displayPrompt();
126
self.stream.write("Clearing Scope...\n");
388
self.stream.write("Clearing context...\n");
127
389
self.buffered_cmd = '';
129
391
self.displayPrompt();