1 /* A repl library that you can include in your own code to get a runtime
2 * interface to your program.
4 * var repl = require("repl");
5 * // start repl on stdin
6 * repl.start("prompt> ");
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");
13 * // listen for TCP socket connections and start repl on them
14 * net.createServer(function(socket) {
15 * repl.start("node via TCP socket> ", socket);
18 * // expose foo to repl context
19 * repl.start("node > ").context.foo = "stdin is fun";
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');
36 // hack for require.resolve("./relative") to work properly.
37 module.filename = path.resolve('repl');
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');
46 // hack for repl require to work properly with node_modules folders
47 module.paths = require('module')._nodeModulePaths(module.filename);
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);
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;
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'];
67 const BLOCK_SCOPED_ERROR = 'Block-scoped declarations (let, ' +
68 'const, function, class) not yet supported outside strict mode';
71 function REPLServer(prompt,
77 if (!(this instanceof REPLServer)) {
78 return new REPLServer(prompt,
86 var options, input, output, dom;
87 if (prompt !== null && typeof prompt === 'object') {
88 // an options object was given
90 stream = options.stream || options.socket;
91 input = options.input;
92 output = options.output;
94 useGlobal = options.useGlobal;
95 ignoreUndefined = options.ignoreUndefined;
96 prompt = options.prompt;
98 replMode = options.replMode;
99 } else if (typeof prompt !== 'string') {
100 throw new Error('An options Object, or a prompt String are required');
107 self._domain = dom || domain.create();
109 self.useGlobal = !!useGlobal;
110 self.ignoreUndefined = !!ignoreUndefined;
111 self.replMode = replMode || exports.REPL_MODE_SLOPPY;
113 self._inTemplateLiteral = false;
115 // just for backwards compat, see github.com/joyent/node/pull/7127
118 const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
119 const sep = '\u0000\u0000\u0000';
120 const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
121 `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
124 eval_ = eval_ || defaultEval;
126 function defaultEval(code, context, file, cb) {
127 var err, result, retry = false;
128 // first, create the Script object to check the syntax
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}`;
137 var script = vm.createScript(code, {
142 debug('parse error %j', code, e);
143 if (self.replMode === exports.REPL_MODE_MAGIC &&
144 e.message === BLOCK_SCOPED_ERROR &&
149 if (isRecoverableError(e, self))
150 err = new Recoverable(e);
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));
163 if (self.useGlobal) {
164 result = script.runInThisContext({ displayErrors: false });
166 result = script.runInContext(context, { displayErrors: false });
170 if (err && process.domain) {
171 debug('not recoverable, send to domain');
172 process.domain.emit('error', err);
173 process.domain.exit();
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}`];
188 self.eval = self._domain.bind(eval_);
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();
199 if (!input && !output) {
200 // legacy API, passing a 'stream'/'socket' option
202 // use stdin and stdout as the default streams if none were given
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;
210 // We're given a duplex readable/writable Stream, like a `net.Socket`
216 self.inputStream = input;
217 self.outputStream = output;
220 // Initialize the current string literal found, to be null
221 self._currentStringLiteral = null;
222 self.bufferedCommand = '';
223 self.lines.level = [];
225 function complete(text, callback) {
226 self.complete(text, callback);
229 rl.Interface.call(this, {
230 input: self.inputStream,
231 output: self.outputStream,
233 terminal: options.terminal,
234 historySize: options.historySize
237 self.setPrompt(prompt !== undefined ? prompt : '> ');
239 this.commands = Object.create(null);
240 defineDefaultCommands(this);
242 // figure out which "writer" function to use
243 self.writer = options.writer || exports.writer;
245 if (options.useColors === undefined) {
246 options.useColors = self.terminal;
248 self.useColors = !!options.useColors;
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);
257 self.setPrompt(self._prompt);
259 self.on('close', function() {
263 var sawSIGINT = false;
264 self.on('SIGINT', function() {
265 var empty = self.line.length === 0;
268 if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) {
274 self.output.write('(^C again to quit)\n');
280 self._currentStringLiteral = null;
281 self.bufferedCommand = '';
282 self.lines.level = [];
283 self.displayPrompt();
286 function parseLine(line, currentStringLiteral) {
287 var previous = null, current = null;
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.
297 current = line.charAt(i);
298 if (current === currentStringLiteral) {
299 currentStringLiteral = null;
300 } else if (current === '\'' ||
302 currentStringLiteral === null) {
303 currentStringLiteral = current;
308 return currentStringLiteral;
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) !== '\\')) {
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;
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);
329 return defaultFn(error);
335 self.on('line', function(cmd) {
336 debug('line %j', cmd);
338 var skipCatchall = false;
339 var finisherFn = finish;
341 // leading whitespaces in template literals should not be trimmed.
342 if (self._inTemplateLiteral) {
343 self._inTemplateLiteral = false;
345 const wasWithinStrLiteral = self._currentStringLiteral !== null;
346 self._currentStringLiteral = parseLine(cmd, self._currentStringLiteral);
347 const isWithinStrLiteral = self._currentStringLiteral !== null;
349 if (!wasWithinStrLiteral && !isWithinStrLiteral) {
350 // Current line has nothing to do with String literals, trim both ends
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();
360 finisherFn = getFinisherFunction(cmd, finish);
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) {
372 self.outputStream.write('Invalid REPL keyword\n');
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
384 evalCmd = '(' + evalCmd + ')\n';
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';
392 debug('eval %j', evalCmd);
393 self.eval(evalCmd, self.context, 'repl', finisherFn);
398 function finish(e, ret) {
399 debug('finish', e, ret);
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();
412 // If error was SyntaxError and not JSON.parse error
414 if (e instanceof Recoverable) {
415 // Start buffering data like that:
419 self.bufferedCommand += cmd + '\n';
420 self.displayPrompt();
423 self._domain.emit('error', e);
427 // Clear buffer if no SyntaxErrors
428 self._currentStringLiteral = null;
429 self.bufferedCommand = '';
431 // If we got any output - print it (if no error)
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');
442 // Display prompt again
443 self.displayPrompt();
447 self.on('SIGCONT', function() {
448 self.displayPrompt(true);
451 self.displayPrompt();
453 inherits(REPLServer, rl.Interface);
454 exports.REPLServer = REPLServer;
456 exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy');
457 exports.REPL_MODE_STRICT = Symbol('repl-strict');
458 exports.REPL_MODE_MAGIC = Symbol('repl-magic');
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,
468 var repl = new REPLServer(prompt,
474 if (!exports.repl) exports.repl = repl;
478 REPLServer.prototype.createContext = function() {
480 if (this.useGlobal) {
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;
490 context.module = module;
491 context.require = require;
494 this.lines.level = [];
496 // make built-in modules available directly
498 exports._builtinLibs.forEach(function(name) {
499 Object.defineProperty(context, name, {
501 var lib = require(name);
502 context._ = context[name] = lib;
505 // allow the creation of other globals with this name
507 delete context[name];
517 REPLServer.prototype.resetContext = function() {
518 this.context = this.createContext();
520 // Allow REPL extensions to extend the new context
521 this.emit('reset', this.context);
524 REPLServer.prototype.displayPrompt = function(preserveCursor) {
525 var prompt = this._initialPrompt;
526 if (this.bufferedCommand.length) {
528 var levelInd = new Array(this.lines.level.length).join('..');
529 prompt += levelInd + ' ';
532 // Do not overwrite `_initialPrompt` here
533 REPLServer.super_.prototype.setPrompt.call(this, prompt);
534 this.prompt(preserveCursor);
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);
543 // A stream to push an array into a REPL
544 // used in REPLServer.complete
545 function ArrayStream() {
548 this.run = function(data) {
550 data.forEach(function(line) {
551 self.emit('data', line + '\n');
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() {};
561 const requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
562 const simpleExpressionRE =
563 /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;
565 function intFilter(item) {
566 // filters out anything not starting with A-Z, a-z, $ or _
567 return /^[A-Za-z_$]/.test(item);
570 function filteredOwnPropertyNames(obj) {
572 return Object.getOwnPropertyNames(obj).filter(intFilter);
575 // Provide a list of completions for the given leading text. This is
576 // given to the readline interface for handling tab completion.
579 // complete('var foo = util.')
580 // -> [['util.print', 'util.debug', 'util.log', 'util.inspect', 'util.pump'],
583 // Warning: This eval's code like "foo.bar.baz", so it will run property
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
592 this.lines.level.forEach(function(kill) {
593 if (kill.isFunction) {
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);
610 // list of completion lists, one for each inheritance "level"
611 var completionGroups = [];
613 var completeOn, match, filter, i, group, c;
615 // REPL commands (e.g. ".break").
617 match = line.match(/^\s*(\.\w*)$/);
619 completionGroups.push(Object.keys(this.commands));
620 completeOn = match[1];
621 if (match[1].length > 1) {
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('|') +
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;
637 var paths = module.paths.concat(require('module').globalPaths);
638 for (i = 0; i < paths.length; i++) {
639 dir = path.resolve(paths[i], subdir);
641 files = fs.readdirSync(dir);
645 for (f = 0; f < files.length; 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.
653 if (exts.indexOf(ext) !== -1) {
654 if (!subdir || base !== 'index') {
655 group.push(subdir + base);
658 abs = path.resolve(dir, name);
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);
674 completionGroups.push(group);
678 completionGroups.push(exports._builtinLibs);
681 completionGroupsLoaded();
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.
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) {
697 completeOn = (match ? match[0] : '');
698 if (line.length === 0) {
701 } else if (line[line.length - 1] === '.') {
703 expr = match[0].slice(0, match[0].length - 1);
705 var bits = match[0].split('.');
707 expr = bits.join('.');
710 // Resolve expr and get its completions.
711 var memberGroups = [];
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));
720 completionGroups.push(filteredOwnPropertyNames(this.context));
721 addStandardGlobals(completionGroups, filter);
722 completionGroupsLoaded();
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);
733 completionGroups.push(globals);
734 addStandardGlobals(completionGroups, filter);
736 completionGroupsLoaded();
740 this.eval(expr, this.context, 'repl', function(e, obj) {
741 // if (e) console.log(e);
744 if (typeof obj === 'object' || typeof obj === 'function') {
746 memberGroups.push(filteredOwnPropertyNames(obj));
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
754 // works for non-objects
758 if (typeof obj === 'object' || typeof obj === 'function') {
759 p = Object.getPrototypeOf(obj);
761 p = obj.constructor ? obj.constructor.prototype : null;
764 memberGroups.push(filteredOwnPropertyNames(p));
765 p = Object.getPrototypeOf(p);
766 // Circular refs possible? Let's guard against that.
773 //console.log("completion error walking prototype chain:" + e);
777 if (memberGroups.length) {
778 for (i = 0; i < memberGroups.length; i++) {
779 completionGroups.push(memberGroups[i].map(function(member) {
780 return expr + '.' + member;
784 filter = expr + '.' + filter;
788 completionGroupsLoaded();
792 completionGroupsLoaded();
795 completionGroupsLoaded();
798 // Will be called when all completionGroups are in place
799 // Useful for async autocompletion
800 function completionGroupsLoaded(err) {
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;
811 newCompletionGroups.push(group);
814 completionGroups = newCompletionGroups;
817 if (completionGroups.length) {
818 var uniq = {}; // unique completions across all groups
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];
826 for (var j = 0; j < group.length; j++) {
828 if (!hasOwnProperty(uniq, c)) {
833 completions.push(''); // separator btwn groups
835 while (completions.length && completions[completions.length - 1] === '') {
840 callback(null, [completions || [], completeOn]);
846 * Used to parse and execute the Node REPL commands.
848 * @param {keyword} keyword The command entered to check.
849 * @return {Boolean} If true it means don't continue parsing the command.
851 REPLServer.prototype.parseREPLKeyword = function(keyword, rest) {
852 var cmd = this.commands[keyword];
854 cmd.action.call(this, rest);
861 REPLServer.prototype.defineCommand = function(keyword, cmd) {
862 if (typeof cmd === 'function') {
864 } else if (typeof cmd.action !== 'function') {
865 throw new Error('bad argument, action must be a function');
867 this.commands[keyword] = cmd;
870 REPLServer.prototype.memory = function memory(cmd) {
873 self.lines = self.lines || [];
874 self.lines.level = self.lines.level || [];
876 // save the line so I can do magic later
878 // TODO should I tab the level?
879 self.lines.push(new Array(self.lines.level.length).join(' ') + cmd);
881 // I don't want to not change the format too much...
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
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;
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
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,
910 isFunction: /\s*function\s*/.test(cmd)
912 } else if (depth < 0) {
914 var curr = self.lines.level.pop();
916 var tmp = curr.depth + depth;
918 //more to go, recurse
921 } else if (tmp > 0) {
922 //remove and push back
924 self.lines.level.push(curr);
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
939 self.lines.level = [];
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',
953 // Common keywords. Exclude for completion on the empty string, b/c
954 // they just get in the way.
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']);
965 function defineDefaultCommands(repl) {
966 repl.defineCommand('break', {
967 help: 'Sometimes you get stuck, this gets you out',
969 this._currentStringLiteral = null;
970 this.bufferedCommand = '';
971 this.displayPrompt();
976 if (repl.useGlobal) {
977 clearMessage = 'Alias for .break';
979 clearMessage = 'Break, and also clear the local context';
981 repl.defineCommand('clear', {
984 this._currentStringLiteral = null;
985 this.bufferedCommand = '';
986 if (!this.useGlobal) {
987 this.outputStream.write('Clearing context...\n');
990 this.displayPrompt();
994 repl.defineCommand('exit', {
995 help: 'Exit the repl',
1001 repl.defineCommand('help', {
1002 help: 'Show repl options',
1003 action: function() {
1005 Object.keys(this.commands).sort().forEach(function(name) {
1006 var cmd = self.commands[name];
1007 self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
1009 this.displayPrompt();
1013 repl.defineCommand('save', {
1014 help: 'Save all evaluated commands in this REPL session to a file',
1015 action: function(file) {
1017 fs.writeFileSync(file, this.lines.join('\n') + '\n');
1018 this.outputStream.write('Session saved to:' + file + '\n');
1020 this.outputStream.write('Failed to save:' + file + '\n');
1022 this.displayPrompt();
1026 repl.defineCommand('load', {
1027 help: 'Load JS from a file into the REPL session',
1028 action: function(file) {
1030 var stats = fs.statSync(file);
1031 if (stats && stats.isFile()) {
1033 var data = fs.readFileSync(file, 'utf8');
1034 var lines = data.split('\n');
1035 this.displayPrompt();
1036 lines.forEach(function(line) {
1038 self.write(line + '\n');
1043 this.outputStream.write('Failed to load:' + file + '\n');
1045 this.displayPrompt();
1050 function regexpEscape(s) {
1051 return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
1056 * Converts commands that use var and function <name>() to use the
1057 * local exports.context when evaled. This provides a local context
1060 * @param {String} cmd The cmd to convert.
1061 * @return {String} The converted command.
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;
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];
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;
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;
1094 return /^(Unexpected end of input|Unexpected token)/.test(message);
1099 function Recoverable(err) {
1102 inherits(Recoverable, SyntaxError);