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');
35 const replMap = new WeakMap();
38 // hack for require.resolve("./relative") to work properly.
39 module.filename = path.resolve('repl');
41 // path.resolve('repl') fails when the current working directory has been
42 // deleted. Fall back to the directory name of the (absolute) executable
43 // path. It's not really correct but what are the alternatives?
44 const dirname = path.dirname(process.execPath);
45 module.filename = path.resolve(dirname, 'repl');
48 // hack for repl require to work properly with node_modules folders
49 module.paths = require('module')._nodeModulePaths(module.filename);
51 // If obj.hasOwnProperty has been overridden, then calling
52 // obj.hasOwnProperty(prop) will break.
53 // See: https://github.com/joyent/node/issues/1707
54 function hasOwnProperty(obj, prop) {
55 return Object.prototype.hasOwnProperty.call(obj, prop);
59 // Can overridden with custom print functions, such as `probe` or `eyes.js`.
60 // This is the default "writer" value if none is passed in the REPL options.
61 exports.writer = util.inspect;
63 exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
64 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
65 'os', 'path', 'punycode', 'querystring', 'readline', 'stream',
66 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
69 const BLOCK_SCOPED_ERROR = 'Block-scoped declarations (let, ' +
70 'const, function, class) not yet supported outside strict mode';
81 this.shouldFail = false;
82 this.blockComment = false;
87 this.shouldFail = false;
88 const wasWithinStrLiteral = this._literal !== null;
90 for (const current of line) {
91 if (previous === '\\') {
92 // valid escaping, skip processing. previous doesn't matter anymore
98 if (previous === '*' && current === '/') {
99 if (this.blockComment) {
100 this.blockComment = false;
104 this.shouldFail = true;
109 // ignore rest of the line if `current` and `previous` are `/`s
110 if (previous === current && previous === '/' && !this.blockComment) {
114 if (previous === '/' && current === '*') {
115 this.blockComment = true;
120 if (this.blockComment) continue;
122 if (current === this._literal) {
123 this._literal = null;
124 } else if (current === '\'' || current === '"') {
125 this._literal = this._literal || current;
131 const isWithinStrLiteral = this._literal !== null;
133 if (!wasWithinStrLiteral && !isWithinStrLiteral) {
134 // Current line has nothing to do with String literals, trim both ends
136 } else if (wasWithinStrLiteral && !isWithinStrLiteral) {
137 // was part of a string literal, but it is over now, trim only the end
138 line = line.trimRight();
139 } else if (isWithinStrLiteral && !wasWithinStrLiteral) {
140 // was not part of a string literal, but it is now, trim only the start
141 line = line.trimLeft();
144 const lastChar = line.charAt(line.length - 1);
146 this.shouldFail = this.shouldFail ||
147 ((!this._literal && lastChar === '\\') ||
148 (this._literal && lastChar !== '\\'));
155 function REPLServer(prompt,
161 if (!(this instanceof REPLServer)) {
162 return new REPLServer(prompt,
170 var options, input, output, dom;
171 if (prompt !== null && typeof prompt === 'object') {
172 // an options object was given
174 stream = options.stream || options.socket;
175 input = options.input;
176 output = options.output;
177 eval_ = options.eval;
178 useGlobal = options.useGlobal;
179 ignoreUndefined = options.ignoreUndefined;
180 prompt = options.prompt;
181 dom = options.domain;
182 replMode = options.replMode;
183 } else if (typeof prompt !== 'string') {
184 throw new Error('An options Object, or a prompt String are required');
191 self._domain = dom || domain.create();
193 self.useGlobal = !!useGlobal;
194 self.ignoreUndefined = !!ignoreUndefined;
195 self.replMode = replMode || exports.REPL_MODE_SLOPPY;
197 self._inTemplateLiteral = false;
199 // just for backwards compat, see github.com/joyent/node/pull/7127
202 const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
203 const sep = '\u0000\u0000\u0000';
204 const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
205 `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
208 eval_ = eval_ || defaultEval;
210 function defaultEval(code, context, file, cb) {
211 var err, result, retry = false;
212 // first, create the Script object to check the syntax
215 if (!/^\s*$/.test(code) &&
216 (self.replMode === exports.REPL_MODE_STRICT || retry)) {
217 // "void 0" keeps the repl from returning "use strict" as the
218 // result value for let/const statements.
219 code = `'use strict'; void 0; ${code}`;
221 var script = vm.createScript(code, {
226 debug('parse error %j', code, e);
227 if (self.replMode === exports.REPL_MODE_MAGIC &&
228 e.message === BLOCK_SCOPED_ERROR &&
233 if (isRecoverableError(e, self))
234 err = new Recoverable(e);
241 // This will set the values from `savedRegExMatches` to corresponding
242 // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
243 regExMatcher.test(savedRegExMatches.join(sep));
247 if (self.useGlobal) {
248 result = script.runInThisContext({ displayErrors: false });
250 result = script.runInContext(context, { displayErrors: false });
254 if (err && process.domain) {
255 debug('not recoverable, send to domain');
256 process.domain.emit('error', err);
257 process.domain.exit();
263 // After executing the current expression, store the values of RegExp
264 // predefined properties back in `savedRegExMatches`
265 for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
266 savedRegExMatches[idx] = RegExp[`$${idx}`];
272 self.eval = self._domain.bind(eval_);
274 self._domain.on('error', function(e) {
275 debug('domain error');
276 const top = replMap.get(self);
277 top.outputStream.write((e.stack || e) + '\n');
278 top.lineParser.reset();
279 top.bufferedCommand = '';
280 top.lines.level = [];
284 if (!input && !output) {
285 // legacy API, passing a 'stream'/'socket' option
287 // use stdin and stdout as the default streams if none were given
290 if (stream.stdin && stream.stdout) {
291 // We're given custom object with 2 streams, or the `process` object
292 input = stream.stdin;
293 output = stream.stdout;
295 // We're given a duplex readable/writable Stream, like a `net.Socket`
301 self.inputStream = input;
302 self.outputStream = output;
305 self.lineParser = new LineParser();
306 self.bufferedCommand = '';
307 self.lines.level = [];
309 function complete(text, callback) {
310 self.complete(text, callback);
313 rl.Interface.call(this, {
314 input: self.inputStream,
315 output: self.outputStream,
317 terminal: options.terminal,
318 historySize: options.historySize
321 self.setPrompt(prompt !== undefined ? prompt : '> ');
323 this.commands = Object.create(null);
324 defineDefaultCommands(this);
326 // figure out which "writer" function to use
327 self.writer = options.writer || exports.writer;
329 if (options.useColors === undefined) {
330 options.useColors = self.terminal;
332 self.useColors = !!options.useColors;
334 if (self.useColors && self.writer === util.inspect) {
335 // Turn on ANSI coloring.
336 self.writer = function(obj, showHidden, depth) {
337 return util.inspect(obj, showHidden, depth, true);
341 self.setPrompt(self._prompt);
343 self.on('close', function() {
347 var sawSIGINT = false;
348 self.on('SIGINT', function() {
349 var empty = self.line.length === 0;
352 if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) {
358 self.output.write('(To exit, press ^C again or type .exit)\n');
364 self.lineParser.reset();
365 self.bufferedCommand = '';
366 self.lines.level = [];
367 self.displayPrompt();
370 self.on('line', function(cmd) {
371 debug('line %j', cmd);
373 var skipCatchall = false;
375 // leading whitespaces in template literals should not be trimmed.
376 if (self._inTemplateLiteral) {
377 self._inTemplateLiteral = false;
379 cmd = self.lineParser.parseLine(cmd);
382 // Check to see if a REPL keyword was used. If it returns true,
383 // display next prompt and return.
384 if (cmd && cmd.charAt(0) === '.' && isNaN(parseFloat(cmd))) {
385 var matches = cmd.match(/^\.([^\s]+)\s*(.*)$/);
386 var keyword = matches && matches[1];
387 var rest = matches && matches[2];
388 if (self.parseREPLKeyword(keyword, rest) === true) {
391 self.outputStream.write('Invalid REPL keyword\n');
396 if (!skipCatchall && (cmd || (!cmd && self.bufferedCommand))) {
397 var evalCmd = self.bufferedCommand + cmd;
398 if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
399 // It's confusing for `{ a : 1 }` to be interpreted as a block
400 // statement rather than an object literal. So, we first try
401 // to wrap it in parentheses, so that it will be interpreted as
403 evalCmd = '(' + evalCmd + ')\n';
405 // otherwise we just append a \n so that it will be either
406 // terminated, or continued onto the next expression if it's an
407 // unexpected end of input.
408 evalCmd = evalCmd + '\n';
411 debug('eval %j', evalCmd);
412 self.eval(evalCmd, self.context, 'repl', finish);
417 function finish(e, ret) {
418 debug('finish', e, ret);
421 if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) {
422 self.outputStream.write('npm should be run outside of the ' +
423 'node repl, in your normal shell.\n' +
424 '(Press Control-D to exit.)\n');
425 self.lineParser.reset();
426 self.bufferedCommand = '';
427 self.displayPrompt();
431 // If error was SyntaxError and not JSON.parse error
433 if (e instanceof Recoverable && !self.lineParser.shouldFail) {
434 // Start buffering data like that:
438 self.bufferedCommand += cmd + '\n';
439 self.displayPrompt();
442 self._domain.emit('error', e.err || e);
446 // Clear buffer if no SyntaxErrors
447 self.lineParser.reset();
448 self.bufferedCommand = '';
450 // If we got any output - print it (if no error)
452 // When an invalid REPL command is used, error message is printed
453 // immediately. We don't have to print anything else. So, only when
454 // the second argument to this function is there, print it.
455 arguments.length === 2 &&
456 (!self.ignoreUndefined || ret !== undefined)) {
457 self.context._ = ret;
458 self.outputStream.write(self.writer(ret) + '\n');
461 // Display prompt again
462 self.displayPrompt();
466 self.on('SIGCONT', function() {
467 self.displayPrompt(true);
470 self.displayPrompt();
472 inherits(REPLServer, rl.Interface);
473 exports.REPLServer = REPLServer;
475 exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy');
476 exports.REPL_MODE_STRICT = Symbol('repl-strict');
477 exports.REPL_MODE_MAGIC = Symbol('repl-magic');
479 // prompt is a string to print on each line for the prompt,
480 // source is a stream to use for I/O, defaulting to stdin/stdout.
481 exports.start = function(prompt,
487 var repl = new REPLServer(prompt,
493 if (!exports.repl) exports.repl = repl;
494 replMap.set(repl, repl);
498 REPLServer.prototype.createContext = function() {
500 if (this.useGlobal) {
503 context = vm.createContext();
504 for (var i in global) context[i] = global[i];
505 context.console = new Console(this.outputStream);
506 context.global = context;
507 context.global.global = context;
510 context.module = module;
511 context.require = require;
514 this.lines.level = [];
516 // make built-in modules available directly
518 exports._builtinLibs.forEach(function(name) {
519 Object.defineProperty(context, name, {
521 var lib = require(name);
522 context._ = context[name] = lib;
525 // allow the creation of other globals with this name
527 delete context[name];
537 REPLServer.prototype.resetContext = function() {
538 this.context = this.createContext();
540 // Allow REPL extensions to extend the new context
541 this.emit('reset', this.context);
544 REPLServer.prototype.displayPrompt = function(preserveCursor) {
545 var prompt = this._initialPrompt;
546 if (this.bufferedCommand.length) {
548 var levelInd = new Array(this.lines.level.length).join('..');
549 prompt += levelInd + ' ';
552 // Do not overwrite `_initialPrompt` here
553 REPLServer.super_.prototype.setPrompt.call(this, prompt);
554 this.prompt(preserveCursor);
557 // When invoked as an API method, overwrite _initialPrompt
558 REPLServer.prototype.setPrompt = function setPrompt(prompt) {
559 this._initialPrompt = prompt;
560 REPLServer.super_.prototype.setPrompt.call(this, prompt);
563 // A stream to push an array into a REPL
564 // used in REPLServer.complete
565 function ArrayStream() {
568 this.run = function(data) {
570 data.forEach(function(line) {
571 self.emit('data', line + '\n');
575 util.inherits(ArrayStream, Stream);
576 ArrayStream.prototype.readable = true;
577 ArrayStream.prototype.writable = true;
578 ArrayStream.prototype.resume = function() {};
579 ArrayStream.prototype.write = function() {};
581 const requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
582 const simpleExpressionRE =
583 /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;
585 function intFilter(item) {
586 // filters out anything not starting with A-Z, a-z, $ or _
587 return /^[A-Za-z_$]/.test(item);
590 function filteredOwnPropertyNames(obj) {
592 return Object.getOwnPropertyNames(obj).filter(intFilter);
595 // Provide a list of completions for the given leading text. This is
596 // given to the readline interface for handling tab completion.
599 // complete('var foo = util.')
600 // -> [['util.print', 'util.debug', 'util.log', 'util.inspect', 'util.pump'],
603 // Warning: This eval's code like "foo.bar.baz", so it will run property
605 REPLServer.prototype.complete = function(line, callback) {
606 // There may be local variables to evaluate, try a nested REPL
607 if (this.bufferedCommand !== undefined && this.bufferedCommand.length) {
608 // Get a new array of inputed lines
609 var tmp = this.lines.slice();
610 // Kill off all function declarations to push all local variables into
612 this.lines.level.forEach(function(kill) {
613 if (kill.isFunction) {
617 var flat = new ArrayStream(); // make a new "input" stream
618 var magic = new REPLServer('', flat); // make a nested REPL
619 magic.context = magic.createContext();
620 flat.run(tmp); // eval the flattened code
621 // all this is only profitable if the nested REPL
622 // does not have a bufferedCommand
623 if (!magic.bufferedCommand) {
624 replMap.set(magic, replMap.get(this));
625 return magic.complete(line, callback);
631 // list of completion lists, one for each inheritance "level"
632 var completionGroups = [];
634 var completeOn, match, filter, i, group, c;
636 // REPL commands (e.g. ".break").
638 match = line.match(/^\s*(\.\w*)$/);
640 completionGroups.push(Object.keys(this.commands));
641 completeOn = match[1];
642 if (match[1].length > 1) {
646 completionGroupsLoaded();
647 } else if (match = line.match(requireRE)) {
648 // require('...<Tab>')
649 var exts = Object.keys(require.extensions);
650 var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') +
653 completeOn = match[1];
654 var subdir = match[2] || '';
655 var filter = match[1];
656 var dir, files, f, name, base, ext, abs, subfiles, s;
658 var paths = module.paths.concat(require('module').globalPaths);
659 for (i = 0; i < paths.length; i++) {
660 dir = path.resolve(paths[i], subdir);
662 files = fs.readdirSync(dir);
666 for (f = 0; f < files.length; f++) {
668 ext = path.extname(name);
669 base = name.slice(0, -ext.length);
670 if (base.match(/-\d+\.\d+(\.\d+)?/) || name === '.npm') {
671 // Exclude versioned names that 'npm' installs.
674 if (exts.indexOf(ext) !== -1) {
675 if (!subdir || base !== 'index') {
676 group.push(subdir + base);
679 abs = path.resolve(dir, name);
681 if (fs.statSync(abs).isDirectory()) {
682 group.push(subdir + name + '/');
683 subfiles = fs.readdirSync(abs);
684 for (s = 0; s < subfiles.length; s++) {
685 if (indexRe.test(subfiles[s])) {
686 group.push(subdir + name);
695 completionGroups.push(group);
699 completionGroups.push(exports._builtinLibs);
702 completionGroupsLoaded();
704 // Handle variable member lookup.
705 // We support simple chained expressions like the following (no function
706 // calls, etc.). That is for simplicity and also because we *eval* that
707 // leading expression so for safety (see WARNING above) don't want to
708 // eval function calls.
710 // foo.bar<|> # completions for 'foo' with filter 'bar'
711 // spam.eggs.<|> # completions for 'spam.eggs' with filter ''
712 // foo<|> # all scope vars with filter 'foo'
713 // foo.<|> # completions for 'foo' with filter ''
714 } else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) {
715 match = simpleExpressionRE.exec(line);
716 if (line.length === 0 || match) {
718 completeOn = (match ? match[0] : '');
719 if (line.length === 0) {
722 } else if (line[line.length - 1] === '.') {
724 expr = match[0].slice(0, match[0].length - 1);
726 var bits = match[0].split('.');
728 expr = bits.join('.');
731 // Resolve expr and get its completions.
732 var memberGroups = [];
734 // If context is instance of vm.ScriptContext
735 // Get global vars synchronously
736 if (this.useGlobal || vm.isContext(this.context)) {
737 var contextProto = this.context;
738 while (contextProto = Object.getPrototypeOf(contextProto)) {
739 completionGroups.push(filteredOwnPropertyNames(contextProto));
741 completionGroups.push(filteredOwnPropertyNames(this.context));
742 addStandardGlobals(completionGroups, filter);
743 completionGroupsLoaded();
745 this.eval('.scope', this.context, 'repl', function(err, globals) {
746 if (err || !Array.isArray(globals)) {
747 addStandardGlobals(completionGroups, filter);
748 } else if (Array.isArray(globals[0])) {
749 // Add grouped globals
750 globals.forEach(function(group) {
751 completionGroups.push(group);
754 completionGroups.push(globals);
755 addStandardGlobals(completionGroups, filter);
757 completionGroupsLoaded();
761 this.eval(expr, this.context, 'repl', function(e, obj) {
762 // if (e) console.log(e);
765 if (typeof obj === 'object' || typeof obj === 'function') {
767 memberGroups.push(filteredOwnPropertyNames(obj));
769 // Probably a Proxy object without `getOwnPropertyNames` trap.
770 // We simply ignore it here, as we don't want to break the
771 // autocompletion. Fixes the bug
772 // https://github.com/nodejs/node/issues/2119
775 // works for non-objects
779 if (typeof obj === 'object' || typeof obj === 'function') {
780 p = Object.getPrototypeOf(obj);
782 p = obj.constructor ? obj.constructor.prototype : null;
785 memberGroups.push(filteredOwnPropertyNames(p));
786 p = Object.getPrototypeOf(p);
787 // Circular refs possible? Let's guard against that.
794 //console.log("completion error walking prototype chain:" + e);
798 if (memberGroups.length) {
799 for (i = 0; i < memberGroups.length; i++) {
800 completionGroups.push(memberGroups[i].map(function(member) {
801 return expr + '.' + member;
805 filter = expr + '.' + filter;
809 completionGroupsLoaded();
813 completionGroupsLoaded();
816 completionGroupsLoaded();
819 // Will be called when all completionGroups are in place
820 // Useful for async autocompletion
821 function completionGroupsLoaded(err) {
824 // Filter, sort (within each group), uniq and merge the completion groups.
825 if (completionGroups.length && filter) {
826 var newCompletionGroups = [];
827 for (i = 0; i < completionGroups.length; i++) {
828 group = completionGroups[i].filter(function(elem) {
829 return elem.indexOf(filter) == 0;
832 newCompletionGroups.push(group);
835 completionGroups = newCompletionGroups;
838 if (completionGroups.length) {
839 var uniq = {}; // unique completions across all groups
841 // Completion group 0 is the "closest"
842 // (least far up the inheritance chain)
843 // so we put its completions last: to be closest in the REPL.
844 for (i = completionGroups.length - 1; i >= 0; i--) {
845 group = completionGroups[i];
847 for (var j = 0; j < group.length; j++) {
849 if (!hasOwnProperty(uniq, c)) {
854 completions.push(''); // separator btwn groups
856 while (completions.length && completions[completions.length - 1] === '') {
861 callback(null, [completions || [], completeOn]);
867 * Used to parse and execute the Node REPL commands.
869 * @param {keyword} keyword The command entered to check.
870 * @return {Boolean} If true it means don't continue parsing the command.
872 REPLServer.prototype.parseREPLKeyword = function(keyword, rest) {
873 var cmd = this.commands[keyword];
875 cmd.action.call(this, rest);
882 REPLServer.prototype.defineCommand = function(keyword, cmd) {
883 if (typeof cmd === 'function') {
885 } else if (typeof cmd.action !== 'function') {
886 throw new Error('bad argument, action must be a function');
888 this.commands[keyword] = cmd;
891 REPLServer.prototype.memory = function memory(cmd) {
894 self.lines = self.lines || [];
895 self.lines.level = self.lines.level || [];
897 // save the line so I can do magic later
899 // TODO should I tab the level?
900 self.lines.push(new Array(self.lines.level.length).join(' ') + cmd);
902 // I don't want to not change the format too much...
906 // I need to know "depth."
907 // Because I can not tell the difference between a } that
908 // closes an object literal and a } that closes a function
910 // going down is { and ( e.g. function() {
911 // going up is } and )
912 var dw = cmd.match(/{|\(/g);
913 var up = cmd.match(/}|\)/g);
914 up = up ? up.length : 0;
915 dw = dw ? dw.length : 0;
922 // push the line#, depth count, and if the line is a function.
923 // Since JS only has functional scope I only need to remove
924 // "function() {" lines, clearly this will not work for
926 // {" but nothing should break, only tab completion for local
927 // scope will not work for this function.
928 self.lines.level.push({
929 line: self.lines.length - 1,
931 isFunction: /\s*function\s*/.test(cmd)
933 } else if (depth < 0) {
935 var curr = self.lines.level.pop();
937 var tmp = curr.depth + depth;
939 //more to go, recurse
942 } else if (tmp > 0) {
943 //remove and push back
945 self.lines.level.push(curr);
952 // it is possible to determine a syntax error at this point.
953 // if the REPL still has a bufferedCommand and
954 // self.lines.level.length === 0
955 // TODO? keep a log of level so that any syntax breaking lines can
956 // be cleared on .break and in the case of a syntax error?
957 // TODO? if a log was kept, then I could clear the bufferedComand and
958 // eval these lines and throw the syntax error
960 self.lines.level = [];
964 function addStandardGlobals(completionGroups, filter) {
965 // Global object properties
966 // (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
967 completionGroups.push(['NaN', 'Infinity', 'undefined',
968 'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
969 'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
970 'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
971 'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
972 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
974 // Common keywords. Exclude for completion on the empty string, b/c
975 // they just get in the way.
977 completionGroups.push(['break', 'case', 'catch', 'const',
978 'continue', 'debugger', 'default', 'delete', 'do', 'else',
979 'export', 'false', 'finally', 'for', 'function', 'if',
980 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return',
981 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'undefined',
982 'var', 'void', 'while', 'with', 'yield']);
986 function defineDefaultCommands(repl) {
987 repl.defineCommand('break', {
988 help: 'Sometimes you get stuck, this gets you out',
990 this.lineParser.reset();
991 this.bufferedCommand = '';
992 this.displayPrompt();
997 if (repl.useGlobal) {
998 clearMessage = 'Alias for .break';
1000 clearMessage = 'Break, and also clear the local context';
1002 repl.defineCommand('clear', {
1004 action: function() {
1005 this.lineParser.reset();
1006 this.bufferedCommand = '';
1007 if (!this.useGlobal) {
1008 this.outputStream.write('Clearing context...\n');
1009 this.resetContext();
1011 this.displayPrompt();
1015 repl.defineCommand('exit', {
1016 help: 'Exit the repl',
1017 action: function() {
1022 repl.defineCommand('help', {
1023 help: 'Show repl options',
1024 action: function() {
1026 Object.keys(this.commands).sort().forEach(function(name) {
1027 var cmd = self.commands[name];
1028 self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
1030 this.displayPrompt();
1034 repl.defineCommand('save', {
1035 help: 'Save all evaluated commands in this REPL session to a file',
1036 action: function(file) {
1038 fs.writeFileSync(file, this.lines.join('\n') + '\n');
1039 this.outputStream.write('Session saved to:' + file + '\n');
1041 this.outputStream.write('Failed to save:' + file + '\n');
1043 this.displayPrompt();
1047 repl.defineCommand('load', {
1048 help: 'Load JS from a file into the REPL session',
1049 action: function(file) {
1051 var stats = fs.statSync(file);
1052 if (stats && stats.isFile()) {
1054 var data = fs.readFileSync(file, 'utf8');
1055 var lines = data.split('\n');
1056 this.displayPrompt();
1057 lines.forEach(function(line) {
1059 self.write(line + '\n');
1063 this.outputStream.write('Failed to load:' + file +
1064 ' is not a valid file\n');
1067 this.outputStream.write('Failed to load:' + file + '\n');
1069 this.displayPrompt();
1074 function regexpEscape(s) {
1075 return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
1080 * Converts commands that use var and function <name>() to use the
1081 * local exports.context when evaled. This provides a local context
1084 * @param {String} cmd The cmd to convert.
1085 * @return {String} The converted command.
1087 REPLServer.prototype.convertToContext = function(cmd) {
1088 const scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m;
1089 const scopeFunc = /^\s*function\s*([_\w\$]+)/;
1090 var self = this, matches;
1092 // Replaces: var foo = "bar"; with: self.context.foo = bar;
1093 matches = scopeVar.exec(cmd);
1094 if (matches && matches.length === 3) {
1095 return 'self.context.' + matches[1] + matches[2];
1098 // Replaces: function foo() {}; with: foo = function foo() {};
1099 matches = scopeFunc.exec(self.bufferedCommand);
1100 if (matches && matches.length === 2) {
1101 return matches[1] + ' = ' + self.bufferedCommand;
1108 // If the error is that we've unexpectedly ended the input,
1109 // then let the user try to recover by adding more input.
1110 function isRecoverableError(e, self) {
1111 if (e && e.name === 'SyntaxError') {
1112 var message = e.message;
1113 if (message === 'Unterminated template literal' ||
1114 message === 'Missing } in template expression') {
1115 self._inTemplateLiteral = true;
1118 return /^(Unexpected end of input|Unexpected token)/.test(message);
1123 function Recoverable(err) {
1126 inherits(Recoverable, SyntaxError);