dns: add missing exports.BADNAME
[platform/upstream/nodejs.git] / lib / repl.js
1 /* A repl library that you can include in your own code to get a runtime
2  * interface to your program.
3  *
4  *   var repl = require("repl");
5  *   // start repl on stdin
6  *   repl.start("prompt> ");
7  *
8  *   // listen for unix socket connections and start repl on them
9  *   net.createServer(function(socket) {
10  *     repl.start("node via Unix socket> ", socket);
11  *   }).listen("/tmp/node-repl-sock");
12  *
13  *   // listen for TCP socket connections and start repl on them
14  *   net.createServer(function(socket) {
15  *     repl.start("node via TCP socket> ", socket);
16  *   }).listen(5001);
17  *
18  *   // expose foo to repl context
19  *   repl.start("node > ").context.foo = "stdin is fun";
20  */
21
22 'use strict';
23
24 const util = require('util');
25 const inherits = util.inherits;
26 const Stream = require('stream');
27 const vm = require('vm');
28 const path = require('path');
29 const fs = require('fs');
30 const rl = require('readline');
31 const Console = require('console').Console;
32 const domain = require('domain');
33 const debug = util.debuglog('repl');
34
35 try {
36   // hack for require.resolve("./relative") to work properly.
37   module.filename = path.resolve('repl');
38 } catch (e) {
39   // path.resolve('repl') fails when the current working directory has been
40   // deleted.  Fall back to the directory name of the (absolute) executable
41   // path.  It's not really correct but what are the alternatives?
42   const dirname = path.dirname(process.execPath);
43   module.filename = path.resolve(dirname, 'repl');
44 }
45
46 // hack for repl require to work properly with node_modules folders
47 module.paths = require('module')._nodeModulePaths(module.filename);
48
49 // If obj.hasOwnProperty has been overridden, then calling
50 // obj.hasOwnProperty(prop) will break.
51 // See: https://github.com/joyent/node/issues/1707
52 function hasOwnProperty(obj, prop) {
53   return Object.prototype.hasOwnProperty.call(obj, prop);
54 }
55
56
57 // Can overridden with custom print functions, such as `probe` or `eyes.js`.
58 // This is the default "writer" value if none is passed in the REPL options.
59 exports.writer = util.inspect;
60
61 exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
62   'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
63   'os', 'path', 'punycode', 'querystring', 'readline', 'stream',
64   'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
65
66
67 const BLOCK_SCOPED_ERROR = 'Block-scoped declarations (let, ' +
68     'const, function, class) not yet supported outside strict mode';
69
70
71 function REPLServer(prompt,
72                     stream,
73                     eval_,
74                     useGlobal,
75                     ignoreUndefined,
76                     replMode) {
77   if (!(this instanceof REPLServer)) {
78     return new REPLServer(prompt,
79                           stream,
80                           eval_,
81                           useGlobal,
82                           ignoreUndefined,
83                           replMode);
84   }
85
86   var options, input, output, dom;
87   if (prompt !== null && typeof prompt === 'object') {
88     // an options object was given
89     options = prompt;
90     stream = options.stream || options.socket;
91     input = options.input;
92     output = options.output;
93     eval_ = options.eval;
94     useGlobal = options.useGlobal;
95     ignoreUndefined = options.ignoreUndefined;
96     prompt = options.prompt;
97     dom = options.domain;
98     replMode = options.replMode;
99   } else if (typeof prompt !== 'string') {
100     throw new Error('An options Object, or a prompt String are required');
101   } else {
102     options = {};
103   }
104
105   var self = this;
106
107   self._domain = dom || domain.create();
108
109   self.useGlobal = !!useGlobal;
110   self.ignoreUndefined = !!ignoreUndefined;
111   self.replMode = replMode || exports.REPL_MODE_SLOPPY;
112
113   self._inTemplateLiteral = false;
114
115   // just for backwards compat, see github.com/joyent/node/pull/7127
116   self.rli = this;
117
118   const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
119   const sep = '\u0000\u0000\u0000';
120   const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
121                                   `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
122                                   `${sep}(.*)$`);
123
124   eval_ = eval_ || defaultEval;
125
126   function defaultEval(code, context, file, cb) {
127     var err, result, retry = false;
128     // first, create the Script object to check the syntax
129     while (true) {
130       try {
131         if (!/^\s*$/.test(code) &&
132             (self.replMode === exports.REPL_MODE_STRICT || retry)) {
133           // "void 0" keeps the repl from returning "use strict" as the
134           // result value for let/const statements.
135           code = `'use strict'; void 0; ${code}`;
136         }
137         var script = vm.createScript(code, {
138           filename: file,
139           displayErrors: false
140         });
141       } catch (e) {
142         debug('parse error %j', code, e);
143         if (self.replMode === exports.REPL_MODE_MAGIC &&
144             e.message === BLOCK_SCOPED_ERROR &&
145             !retry) {
146           retry = true;
147           continue;
148         }
149         if (isRecoverableError(e, self))
150           err = new Recoverable(e);
151         else
152           err = e;
153       }
154       break;
155     }
156
157     // This will set the values from `savedRegExMatches` to corresponding
158     // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
159     regExMatcher.test(savedRegExMatches.join(sep));
160
161     if (!err) {
162       try {
163         if (self.useGlobal) {
164           result = script.runInThisContext({ displayErrors: false });
165         } else {
166           result = script.runInContext(context, { displayErrors: false });
167         }
168       } catch (e) {
169         err = e;
170         if (err && process.domain) {
171           debug('not recoverable, send to domain');
172           process.domain.emit('error', err);
173           process.domain.exit();
174           return;
175         }
176       }
177     }
178
179     // After executing the current expression, store the values of RegExp
180     // predefined properties back in `savedRegExMatches`
181     for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
182       savedRegExMatches[idx] = RegExp[`$${idx}`];
183     }
184
185     cb(err, result);
186   }
187
188   self.eval = self._domain.bind(eval_);
189
190   self._domain.on('error', function(e) {
191     debug('domain error');
192     self.outputStream.write((e.stack || e) + '\n');
193     self._currentStringLiteral = null;
194     self.bufferedCommand = '';
195     self.lines.level = [];
196     self.displayPrompt();
197   });
198
199   if (!input && !output) {
200     // legacy API, passing a 'stream'/'socket' option
201     if (!stream) {
202       // use stdin and stdout as the default streams if none were given
203       stream = process;
204     }
205     if (stream.stdin && stream.stdout) {
206       // We're given custom object with 2 streams, or the `process` object
207       input = stream.stdin;
208       output = stream.stdout;
209     } else {
210       // We're given a duplex readable/writable Stream, like a `net.Socket`
211       input = stream;
212       output = stream;
213     }
214   }
215
216   self.inputStream = input;
217   self.outputStream = output;
218
219   self.resetContext();
220   // Initialize the current string literal found, to be null
221   self._currentStringLiteral = null;
222   self.bufferedCommand = '';
223   self.lines.level = [];
224
225   function complete(text, callback) {
226     self.complete(text, callback);
227   }
228
229   rl.Interface.call(this, {
230     input: self.inputStream,
231     output: self.outputStream,
232     completer: complete,
233     terminal: options.terminal,
234     historySize: options.historySize
235   });
236
237   self.setPrompt(prompt !== undefined ? prompt : '> ');
238
239   this.commands = Object.create(null);
240   defineDefaultCommands(this);
241
242   // figure out which "writer" function to use
243   self.writer = options.writer || exports.writer;
244
245   if (options.useColors === undefined) {
246     options.useColors = self.terminal;
247   }
248   self.useColors = !!options.useColors;
249
250   if (self.useColors && self.writer === util.inspect) {
251     // Turn on ANSI coloring.
252     self.writer = function(obj, showHidden, depth) {
253       return util.inspect(obj, showHidden, depth, true);
254     };
255   }
256
257   self.setPrompt(self._prompt);
258
259   self.on('close', function() {
260     self.emit('exit');
261   });
262
263   var sawSIGINT = false;
264   self.on('SIGINT', function() {
265     var empty = self.line.length === 0;
266     self.clearLine();
267
268     if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) {
269       if (sawSIGINT) {
270         self.close();
271         sawSIGINT = false;
272         return;
273       }
274       self.output.write('(^C again to quit)\n');
275       sawSIGINT = true;
276     } else {
277       sawSIGINT = false;
278     }
279
280     self._currentStringLiteral = null;
281     self.bufferedCommand = '';
282     self.lines.level = [];
283     self.displayPrompt();
284   });
285
286   function parseLine(line, currentStringLiteral) {
287     var previous = null, current = null;
288
289     for (var i = 0; i < line.length; i += 1) {
290       if (previous === '\\') {
291         // if it is a valid escaping, then skip processing and the previous
292         // character doesn't matter anymore.
293         previous = null;
294         continue;
295       }
296
297       current = line.charAt(i);
298       if (current === currentStringLiteral) {
299         currentStringLiteral = null;
300       } else if (current === '\'' ||
301                  current === '"' &&
302                  currentStringLiteral === null) {
303         currentStringLiteral = current;
304       }
305       previous = current;
306     }
307
308     return currentStringLiteral;
309   }
310
311   function getFinisherFunction(cmd, defaultFn) {
312     if ((self._currentStringLiteral === null &&
313          cmd.charAt(cmd.length - 1) === '\\') ||
314         (self._currentStringLiteral !== null &&
315          cmd.charAt(cmd.length - 1) !== '\\')) {
316
317       // If the line continuation is used outside string literal or if the
318       // string continuation happens with out line continuation, then fail hard.
319       // Even if the error is recoverable, get the underlying error and use it.
320       return function(e, ret) {
321         var error = e instanceof Recoverable ? e.err : e;
322
323         if (arguments.length === 2) {
324           // using second argument only if it is actually passed. Otherwise
325           // `undefined` will be printed when invalid REPL commands are used.
326           return defaultFn(error, ret);
327         }
328
329         return defaultFn(error);
330       };
331     }
332     return defaultFn;
333   }
334
335   self.on('line', function(cmd) {
336     debug('line %j', cmd);
337     sawSIGINT = false;
338     var skipCatchall = false;
339     var finisherFn = finish;
340
341     // leading whitespaces in template literals should not be trimmed.
342     if (self._inTemplateLiteral) {
343       self._inTemplateLiteral = false;
344     } else {
345       const wasWithinStrLiteral = self._currentStringLiteral !== null;
346       self._currentStringLiteral = parseLine(cmd, self._currentStringLiteral);
347       const isWithinStrLiteral = self._currentStringLiteral !== null;
348
349       if (!wasWithinStrLiteral && !isWithinStrLiteral) {
350         // Current line has nothing to do with String literals, trim both ends
351         cmd = cmd.trim();
352       } else if (wasWithinStrLiteral && !isWithinStrLiteral) {
353         // was part of a string literal, but it is over now, trim only the end
354         cmd = cmd.trimRight();
355       } else if (isWithinStrLiteral && !wasWithinStrLiteral) {
356         // was not part of a string literal, but it is now, trim only the start
357         cmd = cmd.trimLeft();
358       }
359
360       finisherFn = getFinisherFunction(cmd, finish);
361     }
362
363     // Check to see if a REPL keyword was used. If it returns true,
364     // display next prompt and return.
365     if (cmd && cmd.charAt(0) === '.' && isNaN(parseFloat(cmd))) {
366       var matches = cmd.match(/^\.([^\s]+)\s*(.*)$/);
367       var keyword = matches && matches[1];
368       var rest = matches && matches[2];
369       if (self.parseREPLKeyword(keyword, rest) === true) {
370         return;
371       } else {
372         self.outputStream.write('Invalid REPL keyword\n');
373         skipCatchall = true;
374       }
375     }
376
377     if (!skipCatchall && (cmd || (!cmd && self.bufferedCommand))) {
378       var evalCmd = self.bufferedCommand + cmd;
379       if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
380         // It's confusing for `{ a : 1 }` to be interpreted as a block
381         // statement rather than an object literal.  So, we first try
382         // to wrap it in parentheses, so that it will be interpreted as
383         // an expression.
384         evalCmd = '(' + evalCmd + ')\n';
385       } else {
386         // otherwise we just append a \n so that it will be either
387         // terminated, or continued onto the next expression if it's an
388         // unexpected end of input.
389         evalCmd = evalCmd + '\n';
390       }
391
392       debug('eval %j', evalCmd);
393       self.eval(evalCmd, self.context, 'repl', finisherFn);
394     } else {
395       finisherFn(null);
396     }
397
398     function finish(e, ret) {
399       debug('finish', e, ret);
400       self.memory(cmd);
401
402       if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) {
403         self.outputStream.write('npm should be run outside of the ' +
404                                 'node repl, in your normal shell.\n' +
405                                 '(Press Control-D to exit.)\n');
406         self._currentStringLiteral = null;
407         self.bufferedCommand = '';
408         self.displayPrompt();
409         return;
410       }
411
412       // If error was SyntaxError and not JSON.parse error
413       if (e) {
414         if (e instanceof Recoverable) {
415           // Start buffering data like that:
416           // {
417           // ...  x: 1
418           // ... }
419           self.bufferedCommand += cmd + '\n';
420           self.displayPrompt();
421           return;
422         } else {
423           self._domain.emit('error', e);
424         }
425       }
426
427       // Clear buffer if no SyntaxErrors
428       self._currentStringLiteral = null;
429       self.bufferedCommand = '';
430
431       // If we got any output - print it (if no error)
432       if (!e &&
433           // When an invalid REPL command is used, error message is printed
434           // immediately. We don't have to print anything else. So, only when
435           // the second argument to this function is there, print it.
436           arguments.length === 2 &&
437           (!self.ignoreUndefined || ret !== undefined)) {
438         self.context._ = ret;
439         self.outputStream.write(self.writer(ret) + '\n');
440       }
441
442       // Display prompt again
443       self.displayPrompt();
444     }
445   });
446
447   self.on('SIGCONT', function() {
448     self.displayPrompt(true);
449   });
450
451   self.displayPrompt();
452 }
453 inherits(REPLServer, rl.Interface);
454 exports.REPLServer = REPLServer;
455
456 exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy');
457 exports.REPL_MODE_STRICT = Symbol('repl-strict');
458 exports.REPL_MODE_MAGIC = Symbol('repl-magic');
459
460 // prompt is a string to print on each line for the prompt,
461 // source is a stream to use for I/O, defaulting to stdin/stdout.
462 exports.start = function(prompt,
463                          source,
464                          eval_,
465                          useGlobal,
466                          ignoreUndefined,
467                          replMode) {
468   var repl = new REPLServer(prompt,
469                             source,
470                             eval_,
471                             useGlobal,
472                             ignoreUndefined,
473                             replMode);
474   if (!exports.repl) exports.repl = repl;
475   return repl;
476 };
477
478 REPLServer.prototype.createContext = function() {
479   var context;
480   if (this.useGlobal) {
481     context = global;
482   } else {
483     context = vm.createContext();
484     for (var i in global) context[i] = global[i];
485     context.console = new Console(this.outputStream);
486     context.global = context;
487     context.global.global = context;
488   }
489
490   context.module = module;
491   context.require = require;
492
493   this.lines = [];
494   this.lines.level = [];
495
496   // make built-in modules available directly
497   // (loaded lazily)
498   exports._builtinLibs.forEach(function(name) {
499     Object.defineProperty(context, name, {
500       get: function() {
501         var lib = require(name);
502         context._ = context[name] = lib;
503         return lib;
504       },
505       // allow the creation of other globals with this name
506       set: function(val) {
507         delete context[name];
508         context[name] = val;
509       },
510       configurable: true
511     });
512   });
513
514   return context;
515 };
516
517 REPLServer.prototype.resetContext = function() {
518   this.context = this.createContext();
519
520   // Allow REPL extensions to extend the new context
521   this.emit('reset', this.context);
522 };
523
524 REPLServer.prototype.displayPrompt = function(preserveCursor) {
525   var prompt = this._initialPrompt;
526   if (this.bufferedCommand.length) {
527     prompt = '...';
528     var levelInd = new Array(this.lines.level.length).join('..');
529     prompt += levelInd + ' ';
530   }
531
532   // Do not overwrite `_initialPrompt` here
533   REPLServer.super_.prototype.setPrompt.call(this, prompt);
534   this.prompt(preserveCursor);
535 };
536
537 // When invoked as an API method, overwrite _initialPrompt
538 REPLServer.prototype.setPrompt = function setPrompt(prompt) {
539   this._initialPrompt = prompt;
540   REPLServer.super_.prototype.setPrompt.call(this, prompt);
541 };
542
543 // A stream to push an array into a REPL
544 // used in REPLServer.complete
545 function ArrayStream() {
546   Stream.call(this);
547
548   this.run = function(data) {
549     var self = this;
550     data.forEach(function(line) {
551       self.emit('data', line + '\n');
552     });
553   };
554 }
555 util.inherits(ArrayStream, Stream);
556 ArrayStream.prototype.readable = true;
557 ArrayStream.prototype.writable = true;
558 ArrayStream.prototype.resume = function() {};
559 ArrayStream.prototype.write = function() {};
560
561 const requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
562 const simpleExpressionRE =
563     /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;
564
565 function intFilter(item) {
566   // filters out anything not starting with A-Z, a-z, $ or _
567   return /^[A-Za-z_$]/.test(item);
568 }
569
570 function filteredOwnPropertyNames(obj) {
571   if (!obj) return [];
572   return Object.getOwnPropertyNames(obj).filter(intFilter);
573 }
574
575 // Provide a list of completions for the given leading text. This is
576 // given to the readline interface for handling tab completion.
577 //
578 // Example:
579 //  complete('var foo = util.')
580 //    -> [['util.print', 'util.debug', 'util.log', 'util.inspect', 'util.pump'],
581 //        'util.' ]
582 //
583 // Warning: This eval's code like "foo.bar.baz", so it will run property
584 // getter code.
585 REPLServer.prototype.complete = function(line, callback) {
586   // There may be local variables to evaluate, try a nested REPL
587   if (this.bufferedCommand !== undefined && this.bufferedCommand.length) {
588     // Get a new array of inputed lines
589     var tmp = this.lines.slice();
590     // Kill off all function declarations to push all local variables into
591     // global scope
592     this.lines.level.forEach(function(kill) {
593       if (kill.isFunction) {
594         tmp[kill.line] = '';
595       }
596     });
597     var flat = new ArrayStream();         // make a new "input" stream
598     var magic = new REPLServer('', flat); // make a nested REPL
599     magic.context = magic.createContext();
600     flat.run(tmp);                        // eval the flattened code
601     // all this is only profitable if the nested REPL
602     // does not have a bufferedCommand
603     if (!magic.bufferedCommand) {
604       return magic.complete(line, callback);
605     }
606   }
607
608   var completions;
609
610   // list of completion lists, one for each inheritance "level"
611   var completionGroups = [];
612
613   var completeOn, match, filter, i, group, c;
614
615   // REPL commands (e.g. ".break").
616   var match = null;
617   match = line.match(/^\s*(\.\w*)$/);
618   if (match) {
619     completionGroups.push(Object.keys(this.commands));
620     completeOn = match[1];
621     if (match[1].length > 1) {
622       filter = match[1];
623     }
624
625     completionGroupsLoaded();
626   } else if (match = line.match(requireRE)) {
627     // require('...<Tab>')
628     var exts = Object.keys(require.extensions);
629     var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') +
630                              ')$');
631
632     completeOn = match[1];
633     var subdir = match[2] || '';
634     var filter = match[1];
635     var dir, files, f, name, base, ext, abs, subfiles, s;
636     group = [];
637     var paths = module.paths.concat(require('module').globalPaths);
638     for (i = 0; i < paths.length; i++) {
639       dir = path.resolve(paths[i], subdir);
640       try {
641         files = fs.readdirSync(dir);
642       } catch (e) {
643         continue;
644       }
645       for (f = 0; f < files.length; f++) {
646         name = files[f];
647         ext = path.extname(name);
648         base = name.slice(0, -ext.length);
649         if (base.match(/-\d+\.\d+(\.\d+)?/) || name === '.npm') {
650           // Exclude versioned names that 'npm' installs.
651           continue;
652         }
653         if (exts.indexOf(ext) !== -1) {
654           if (!subdir || base !== 'index') {
655             group.push(subdir + base);
656           }
657         } else {
658           abs = path.resolve(dir, name);
659           try {
660             if (fs.statSync(abs).isDirectory()) {
661               group.push(subdir + name + '/');
662               subfiles = fs.readdirSync(abs);
663               for (s = 0; s < subfiles.length; s++) {
664                 if (indexRe.test(subfiles[s])) {
665                   group.push(subdir + name);
666                 }
667               }
668             }
669           } catch (e) {}
670         }
671       }
672     }
673     if (group.length) {
674       completionGroups.push(group);
675     }
676
677     if (!subdir) {
678       completionGroups.push(exports._builtinLibs);
679     }
680
681     completionGroupsLoaded();
682
683   // Handle variable member lookup.
684   // We support simple chained expressions like the following (no function
685   // calls, etc.). That is for simplicity and also because we *eval* that
686   // leading expression so for safety (see WARNING above) don't want to
687   // eval function calls.
688   //
689   //   foo.bar<|>     # completions for 'foo' with filter 'bar'
690   //   spam.eggs.<|>  # completions for 'spam.eggs' with filter ''
691   //   foo<|>         # all scope vars with filter 'foo'
692   //   foo.<|>        # completions for 'foo' with filter ''
693   } else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) {
694     match = simpleExpressionRE.exec(line);
695     if (line.length === 0 || match) {
696       var expr;
697       completeOn = (match ? match[0] : '');
698       if (line.length === 0) {
699         filter = '';
700         expr = '';
701       } else if (line[line.length - 1] === '.') {
702         filter = '';
703         expr = match[0].slice(0, match[0].length - 1);
704       } else {
705         var bits = match[0].split('.');
706         filter = bits.pop();
707         expr = bits.join('.');
708       }
709
710       // Resolve expr and get its completions.
711       var memberGroups = [];
712       if (!expr) {
713         // If context is instance of vm.ScriptContext
714         // Get global vars synchronously
715         if (this.useGlobal || vm.isContext(this.context)) {
716           var contextProto = this.context;
717           while (contextProto = Object.getPrototypeOf(contextProto)) {
718             completionGroups.push(filteredOwnPropertyNames(contextProto));
719           }
720           completionGroups.push(filteredOwnPropertyNames(this.context));
721           addStandardGlobals(completionGroups, filter);
722           completionGroupsLoaded();
723         } else {
724           this.eval('.scope', this.context, 'repl', function(err, globals) {
725             if (err || !Array.isArray(globals)) {
726               addStandardGlobals(completionGroups, filter);
727             } else if (Array.isArray(globals[0])) {
728               // Add grouped globals
729               globals.forEach(function(group) {
730                 completionGroups.push(group);
731               });
732             } else {
733               completionGroups.push(globals);
734               addStandardGlobals(completionGroups, filter);
735             }
736             completionGroupsLoaded();
737           });
738         }
739       } else {
740         this.eval(expr, this.context, 'repl', function(e, obj) {
741           // if (e) console.log(e);
742
743           if (obj != null) {
744             if (typeof obj === 'object' || typeof obj === 'function') {
745               try {
746                 memberGroups.push(filteredOwnPropertyNames(obj));
747               } catch (ex) {
748                 // Probably a Proxy object without `getOwnPropertyNames` trap.
749                 // We simply ignore it here, as we don't want to break the
750                 // autocompletion. Fixes the bug
751                 // https://github.com/nodejs/node/issues/2119
752               }
753             }
754             // works for non-objects
755             try {
756               var sentinel = 5;
757               var p;
758               if (typeof obj === 'object' || typeof obj === 'function') {
759                 p = Object.getPrototypeOf(obj);
760               } else {
761                 p = obj.constructor ? obj.constructor.prototype : null;
762               }
763               while (p !== null) {
764                 memberGroups.push(filteredOwnPropertyNames(p));
765                 p = Object.getPrototypeOf(p);
766                 // Circular refs possible? Let's guard against that.
767                 sentinel--;
768                 if (sentinel <= 0) {
769                   break;
770                 }
771               }
772             } catch (e) {
773               //console.log("completion error walking prototype chain:" + e);
774             }
775           }
776
777           if (memberGroups.length) {
778             for (i = 0; i < memberGroups.length; i++) {
779               completionGroups.push(memberGroups[i].map(function(member) {
780                 return expr + '.' + member;
781               }));
782             }
783             if (filter) {
784               filter = expr + '.' + filter;
785             }
786           }
787
788           completionGroupsLoaded();
789         });
790       }
791     } else {
792       completionGroupsLoaded();
793     }
794   } else {
795     completionGroupsLoaded();
796   }
797
798   // Will be called when all completionGroups are in place
799   // Useful for async autocompletion
800   function completionGroupsLoaded(err) {
801     if (err) throw err;
802
803     // Filter, sort (within each group), uniq and merge the completion groups.
804     if (completionGroups.length && filter) {
805       var newCompletionGroups = [];
806       for (i = 0; i < completionGroups.length; i++) {
807         group = completionGroups[i].filter(function(elem) {
808           return elem.indexOf(filter) == 0;
809         });
810         if (group.length) {
811           newCompletionGroups.push(group);
812         }
813       }
814       completionGroups = newCompletionGroups;
815     }
816
817     if (completionGroups.length) {
818       var uniq = {};  // unique completions across all groups
819       completions = [];
820       // Completion group 0 is the "closest"
821       // (least far up the inheritance chain)
822       // so we put its completions last: to be closest in the REPL.
823       for (i = completionGroups.length - 1; i >= 0; i--) {
824         group = completionGroups[i];
825         group.sort();
826         for (var j = 0; j < group.length; j++) {
827           c = group[j];
828           if (!hasOwnProperty(uniq, c)) {
829             completions.push(c);
830             uniq[c] = true;
831           }
832         }
833         completions.push(''); // separator btwn groups
834       }
835       while (completions.length && completions[completions.length - 1] === '') {
836         completions.pop();
837       }
838     }
839
840     callback(null, [completions || [], completeOn]);
841   }
842 };
843
844
845 /**
846  * Used to parse and execute the Node REPL commands.
847  *
848  * @param {keyword} keyword The command entered to check.
849  * @return {Boolean} If true it means don't continue parsing the command.
850  */
851 REPLServer.prototype.parseREPLKeyword = function(keyword, rest) {
852   var cmd = this.commands[keyword];
853   if (cmd) {
854     cmd.action.call(this, rest);
855     return true;
856   }
857   return false;
858 };
859
860
861 REPLServer.prototype.defineCommand = function(keyword, cmd) {
862   if (typeof cmd === 'function') {
863     cmd = {action: cmd};
864   } else if (typeof cmd.action !== 'function') {
865     throw new Error('bad argument, action must be a function');
866   }
867   this.commands[keyword] = cmd;
868 };
869
870 REPLServer.prototype.memory = function memory(cmd) {
871   var self = this;
872
873   self.lines = self.lines || [];
874   self.lines.level = self.lines.level || [];
875
876   // save the line so I can do magic later
877   if (cmd) {
878     // TODO should I tab the level?
879     self.lines.push(new Array(self.lines.level.length).join('  ') + cmd);
880   } else {
881     // I don't want to not change the format too much...
882     self.lines.push('');
883   }
884
885   // I need to know "depth."
886   // Because I can not tell the difference between a } that
887   // closes an object literal and a } that closes a function
888   if (cmd) {
889     // going down is { and (   e.g. function() {
890     // going up is } and )
891     var dw = cmd.match(/{|\(/g);
892     var up = cmd.match(/}|\)/g);
893     up = up ? up.length : 0;
894     dw = dw ? dw.length : 0;
895     var depth = dw - up;
896
897     if (depth) {
898       (function workIt() {
899         if (depth > 0) {
900           // going... down.
901           // push the line#, depth count, and if the line is a function.
902           // Since JS only has functional scope I only need to remove
903           // "function() {" lines, clearly this will not work for
904           // "function()
905           // {" but nothing should break, only tab completion for local
906           // scope will not work for this function.
907           self.lines.level.push({
908             line: self.lines.length - 1,
909             depth: depth,
910             isFunction: /\s*function\s*/.test(cmd)
911           });
912         } else if (depth < 0) {
913           // going... up.
914           var curr = self.lines.level.pop();
915           if (curr) {
916             var tmp = curr.depth + depth;
917             if (tmp < 0) {
918               //more to go, recurse
919               depth += curr.depth;
920               workIt();
921             } else if (tmp > 0) {
922               //remove and push back
923               curr.depth += depth;
924               self.lines.level.push(curr);
925             }
926           }
927         }
928       }());
929     }
930
931     // it is possible to determine a syntax error at this point.
932     // if the REPL still has a bufferedCommand and
933     // self.lines.level.length === 0
934     // TODO? keep a log of level so that any syntax breaking lines can
935     // be cleared on .break and in the case of a syntax error?
936     // TODO? if a log was kept, then I could clear the bufferedComand and
937     // eval these lines and throw the syntax error
938   } else {
939     self.lines.level = [];
940   }
941 };
942
943 function addStandardGlobals(completionGroups, filter) {
944   // Global object properties
945   // (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
946   completionGroups.push(['NaN', 'Infinity', 'undefined',
947     'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
948     'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
949     'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
950     'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
951     'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
952     'Math', 'JSON']);
953   // Common keywords. Exclude for completion on the empty string, b/c
954   // they just get in the way.
955   if (filter) {
956     completionGroups.push(['break', 'case', 'catch', 'const',
957       'continue', 'debugger', 'default', 'delete', 'do', 'else',
958       'export', 'false', 'finally', 'for', 'function', 'if',
959       'import', 'in', 'instanceof', 'let', 'new', 'null', 'return',
960       'switch', 'this', 'throw', 'true', 'try', 'typeof', 'undefined',
961       'var', 'void', 'while', 'with', 'yield']);
962   }
963 }
964
965 function defineDefaultCommands(repl) {
966   repl.defineCommand('break', {
967     help: 'Sometimes you get stuck, this gets you out',
968     action: function() {
969       this._currentStringLiteral = null;
970       this.bufferedCommand = '';
971       this.displayPrompt();
972     }
973   });
974
975   var clearMessage;
976   if (repl.useGlobal) {
977     clearMessage = 'Alias for .break';
978   } else {
979     clearMessage = 'Break, and also clear the local context';
980   }
981   repl.defineCommand('clear', {
982     help: clearMessage,
983     action: function() {
984       this._currentStringLiteral = null;
985       this.bufferedCommand = '';
986       if (!this.useGlobal) {
987         this.outputStream.write('Clearing context...\n');
988         this.resetContext();
989       }
990       this.displayPrompt();
991     }
992   });
993
994   repl.defineCommand('exit', {
995     help: 'Exit the repl',
996     action: function() {
997       this.close();
998     }
999   });
1000
1001   repl.defineCommand('help', {
1002     help: 'Show repl options',
1003     action: function() {
1004       var self = this;
1005       Object.keys(this.commands).sort().forEach(function(name) {
1006         var cmd = self.commands[name];
1007         self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
1008       });
1009       this.displayPrompt();
1010     }
1011   });
1012
1013   repl.defineCommand('save', {
1014     help: 'Save all evaluated commands in this REPL session to a file',
1015     action: function(file) {
1016       try {
1017         fs.writeFileSync(file, this.lines.join('\n') + '\n');
1018         this.outputStream.write('Session saved to:' + file + '\n');
1019       } catch (e) {
1020         this.outputStream.write('Failed to save:' + file + '\n');
1021       }
1022       this.displayPrompt();
1023     }
1024   });
1025
1026   repl.defineCommand('load', {
1027     help: 'Load JS from a file into the REPL session',
1028     action: function(file) {
1029       try {
1030         var stats = fs.statSync(file);
1031         if (stats && stats.isFile()) {
1032           var self = this;
1033           var data = fs.readFileSync(file, 'utf8');
1034           var lines = data.split('\n');
1035           this.displayPrompt();
1036           lines.forEach(function(line) {
1037             if (line) {
1038               self.write(line + '\n');
1039             }
1040           });
1041         }
1042       } catch (e) {
1043         this.outputStream.write('Failed to load:' + file + '\n');
1044       }
1045       this.displayPrompt();
1046     }
1047   });
1048 }
1049
1050 function regexpEscape(s) {
1051   return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
1052 }
1053
1054
1055 /**
1056  * Converts commands that use var and function <name>() to use the
1057  * local exports.context when evaled. This provides a local context
1058  * on the REPL.
1059  *
1060  * @param {String} cmd The cmd to convert.
1061  * @return {String} The converted command.
1062  */
1063 REPLServer.prototype.convertToContext = function(cmd) {
1064   const scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m;
1065   const scopeFunc = /^\s*function\s*([_\w\$]+)/;
1066   var self = this, matches;
1067
1068   // Replaces: var foo = "bar";  with: self.context.foo = bar;
1069   matches = scopeVar.exec(cmd);
1070   if (matches && matches.length === 3) {
1071     return 'self.context.' + matches[1] + matches[2];
1072   }
1073
1074   // Replaces: function foo() {};  with: foo = function foo() {};
1075   matches = scopeFunc.exec(self.bufferedCommand);
1076   if (matches && matches.length === 2) {
1077     return matches[1] + ' = ' + self.bufferedCommand;
1078   }
1079
1080   return cmd;
1081 };
1082
1083
1084 // If the error is that we've unexpectedly ended the input,
1085 // then let the user try to recover by adding more input.
1086 function isRecoverableError(e, self) {
1087   if (e && e.name === 'SyntaxError') {
1088     var message = e.message;
1089     if (message === 'Unterminated template literal' ||
1090         message === 'Missing } in template expression') {
1091       self._inTemplateLiteral = true;
1092       return true;
1093     }
1094     return /^(Unexpected end of input|Unexpected token)/.test(message);
1095   }
1096   return false;
1097 }
1098
1099 function Recoverable(err) {
1100   this.err = err;
1101 }
1102 inherits(Recoverable, SyntaxError);