• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22/* A repl library that you can include in your own code to get a runtime
23 * interface to your program.
24 *
25 *   const repl = require("repl");
26 *   // start repl on stdin
27 *   repl.start("prompt> ");
28 *
29 *   // listen for unix socket connections and start repl on them
30 *   net.createServer(function(socket) {
31 *     repl.start("node via Unix socket> ", socket);
32 *   }).listen("/tmp/node-repl-sock");
33 *
34 *   // listen for TCP socket connections and start repl on them
35 *   net.createServer(function(socket) {
36 *     repl.start("node via TCP socket> ", socket);
37 *   }).listen(5001);
38 *
39 *   // expose foo to repl context
40 *   repl.start("node > ").context.foo = "stdin is fun";
41 */
42
43'use strict';
44
45const {
46  Error,
47  MathMax,
48  NumberIsNaN,
49  ObjectAssign,
50  ObjectCreate,
51  ObjectDefineProperty,
52  ObjectGetOwnPropertyDescriptor,
53  ObjectGetOwnPropertyNames,
54  ObjectGetPrototypeOf,
55  ObjectKeys,
56  ObjectSetPrototypeOf,
57  Promise,
58  PromiseRace,
59  RegExp,
60  Set,
61  StringPrototypeIncludes,
62  Symbol,
63  WeakSet,
64} = primordials;
65
66const {
67  builtinLibs,
68  makeRequireFunction,
69  addBuiltinLibsToObject
70} = require('internal/modules/cjs/helpers');
71const {
72  isIdentifierStart,
73  isIdentifierChar
74} = require('internal/deps/acorn/acorn/dist/acorn');
75const {
76  decorateErrorStack,
77  isError,
78  deprecate
79} = require('internal/util');
80const { inspect } = require('internal/util/inspect');
81const vm = require('vm');
82const path = require('path');
83const fs = require('fs');
84const { Interface } = require('readline');
85const {
86  commonPrefix
87} = require('internal/readline/utils');
88const { Console } = require('console');
89const cjsLoader = require('internal/modules/cjs/loader');
90const { Module: CJSModule } = cjsLoader;
91const domain = require('domain');
92let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
93  debug = fn;
94});
95const {
96  codes: {
97    ERR_CANNOT_WATCH_SIGINT,
98    ERR_INVALID_ARG_TYPE,
99    ERR_INVALID_REPL_EVAL_CONFIG,
100    ERR_INVALID_REPL_INPUT,
101    ERR_SCRIPT_EXECUTION_INTERRUPTED,
102  },
103  overrideStackTrace,
104} = require('internal/errors');
105const { sendInspectorCommand } = require('internal/util/inspector');
106const experimentalREPLAwait = require('internal/options').getOptionValue(
107  '--experimental-repl-await'
108);
109const {
110  isRecoverableError,
111  kStandaloneREPL,
112  setupPreview,
113  setupReverseSearch,
114} = require('internal/repl/utils');
115const {
116  getOwnNonIndexProperties,
117  propertyFilter: {
118    ALL_PROPERTIES,
119    SKIP_SYMBOLS
120  }
121} = internalBinding('util');
122const {
123  startSigintWatchdog,
124  stopSigintWatchdog
125} = internalBinding('contextify');
126
127const history = require('internal/repl/history');
128let nextREPLResourceNumber = 1;
129// This prevents v8 code cache from getting confused and using a different
130// cache from a resource of the same name
131function getREPLResourceName() {
132  return `REPL${nextREPLResourceNumber++}`;
133}
134
135// Lazy-loaded.
136let processTopLevelAwait;
137
138const globalBuiltins =
139  new Set(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)'));
140
141const parentModule = module;
142const domainSet = new WeakSet();
143
144const kBufferedCommandSymbol = Symbol('bufferedCommand');
145const kContextId = Symbol('contextId');
146
147let addedNewListener = false;
148
149try {
150  // Hack for require.resolve("./relative") to work properly.
151  module.filename = path.resolve('repl');
152} catch {
153  // path.resolve('repl') fails when the current working directory has been
154  // deleted.  Fall back to the directory name of the (absolute) executable
155  // path.  It's not really correct but what are the alternatives?
156  const dirname = path.dirname(process.execPath);
157  module.filename = path.resolve(dirname, 'repl');
158}
159
160// Hack for repl require to work properly with node_modules folders
161module.paths = CJSModule._nodeModulePaths(module.filename);
162
163// This is the default "writer" value, if none is passed in the REPL options,
164// and it can be overridden by custom print functions, such as `probe` or
165// `eyes.js`.
166const writer = exports.writer = (obj) => inspect(obj, writer.options);
167writer.options = { ...inspect.defaultOptions, showProxy: true };
168
169exports._builtinLibs = builtinLibs;
170
171function REPLServer(prompt,
172                    stream,
173                    eval_,
174                    useGlobal,
175                    ignoreUndefined,
176                    replMode) {
177  if (!(this instanceof REPLServer)) {
178    return new REPLServer(prompt,
179                          stream,
180                          eval_,
181                          useGlobal,
182                          ignoreUndefined,
183                          replMode);
184  }
185
186  let options;
187  if (prompt !== null && typeof prompt === 'object') {
188    // An options object was given.
189    options = { ...prompt };
190    stream = options.stream || options.socket;
191    eval_ = options.eval;
192    useGlobal = options.useGlobal;
193    ignoreUndefined = options.ignoreUndefined;
194    prompt = options.prompt;
195    replMode = options.replMode;
196  } else {
197    options = {};
198  }
199
200  if (!options.input && !options.output) {
201    // Legacy API, passing a 'stream'/'socket' option.
202    if (!stream) {
203      // Use stdin and stdout as the default streams if none were given.
204      stream = process;
205    }
206    // We're given a duplex readable/writable Stream, like a `net.Socket`
207    // or a custom object with 2 streams, or the `process` object.
208    options.input = stream.stdin || stream;
209    options.output = stream.stdout || stream;
210  }
211
212  if (options.terminal === undefined) {
213    options.terminal = options.output.isTTY;
214  }
215  options.terminal = !!options.terminal;
216
217  if (options.terminal && options.useColors === undefined) {
218    // If possible, check if stdout supports colors or not.
219    if (options.output.hasColors) {
220      options.useColors = options.output.hasColors();
221    } else if (process.env.NODE_DISABLE_COLORS === undefined) {
222      options.useColors = true;
223    }
224  }
225
226  // TODO(devsnek): Add a test case for custom eval functions.
227  const preview = options.terminal &&
228    (options.preview !== undefined ? !!options.preview : !eval_);
229
230  this.inputStream = options.input;
231  this.outputStream = options.output;
232  this.useColors = !!options.useColors;
233  this._domain = options.domain || domain.create();
234  this.useGlobal = !!useGlobal;
235  this.ignoreUndefined = !!ignoreUndefined;
236  this.replMode = replMode || exports.REPL_MODE_SLOPPY;
237  this.underscoreAssigned = false;
238  this.last = undefined;
239  this.underscoreErrAssigned = false;
240  this.lastError = undefined;
241  this.breakEvalOnSigint = !!options.breakEvalOnSigint;
242  this.editorMode = false;
243  // Context id for use with the inspector protocol.
244  this[kContextId] = undefined;
245
246  if (this.breakEvalOnSigint && eval_) {
247    // Allowing this would not reflect user expectations.
248    // breakEvalOnSigint affects only the behavior of the default eval().
249    throw new ERR_INVALID_REPL_EVAL_CONFIG();
250  }
251
252  // Add this listener only once and use a WeakSet that contains the REPLs
253  // domains. Otherwise we'd have to add a single listener to each REPL instance
254  // and that could trigger the `MaxListenersExceededWarning`.
255  if (!options[kStandaloneREPL] && !addedNewListener) {
256    process.prependListener('newListener', (event, listener) => {
257      if (event === 'uncaughtException' &&
258          process.domain &&
259          listener.name !== 'domainUncaughtExceptionClear' &&
260          domainSet.has(process.domain)) {
261        // Throw an error so that the event will not be added and the current
262        // domain takes over. That way the user is notified about the error
263        // and the current code evaluation is stopped, just as any other code
264        // that contains an error.
265        throw new ERR_INVALID_REPL_INPUT(
266          'Listeners for `uncaughtException` cannot be used in the REPL');
267      }
268    });
269    addedNewListener = true;
270  }
271
272  domainSet.add(this._domain);
273
274  let rli = this;
275  ObjectDefineProperty(this, 'rli', {
276    get: deprecate(() => rli,
277                   'REPLServer.rli is deprecated', 'DEP0124'),
278    set: deprecate((val) => rli = val,
279                   'REPLServer.rli is deprecated', 'DEP0124'),
280    enumerable: true,
281    configurable: true
282  });
283
284  const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
285  const sep = '\u0000\u0000\u0000';
286  const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
287                                  `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
288                                  `${sep}(.*)$`);
289
290  eval_ = eval_ || defaultEval;
291
292  const self = this;
293
294  // Pause taking in new input, and store the keys in a buffer.
295  const pausedBuffer = [];
296  let paused = false;
297  function pause() {
298    paused = true;
299  }
300
301  function unpause() {
302    if (!paused) return;
303    paused = false;
304    let entry;
305    const tmpCompletionEnabled = self.isCompletionEnabled;
306    while (entry = pausedBuffer.shift()) {
307      const [type, payload, isCompletionEnabled] = entry;
308      switch (type) {
309        case 'key': {
310          const [d, key] = payload;
311          self.isCompletionEnabled = isCompletionEnabled;
312          self._ttyWrite(d, key);
313          break;
314        }
315        case 'close':
316          self.emit('exit');
317          break;
318      }
319      if (paused) {
320        break;
321      }
322    }
323    self.isCompletionEnabled = tmpCompletionEnabled;
324  }
325
326  function defaultEval(code, context, file, cb) {
327    const asyncESM = require('internal/process/esm_loader');
328
329    let result, script, wrappedErr;
330    let err = null;
331    let wrappedCmd = false;
332    let awaitPromise = false;
333    const input = code;
334
335    // It's confusing for `{ a : 1 }` to be interpreted as a block
336    // statement rather than an object literal.  So, we first try
337    // to wrap it in parentheses, so that it will be interpreted as
338    // an expression.  Note that if the above condition changes,
339    // lib/internal/repl/utils.js needs to be changed to match.
340    if (/^\s*{/.test(code) && !/;\s*$/.test(code)) {
341      code = `(${code.trim()})\n`;
342      wrappedCmd = true;
343    }
344
345    if (experimentalREPLAwait && code.includes('await')) {
346      if (processTopLevelAwait === undefined) {
347        ({ processTopLevelAwait } = require('internal/repl/await'));
348      }
349
350      const potentialWrappedCode = processTopLevelAwait(code);
351      if (potentialWrappedCode !== null) {
352        code = potentialWrappedCode;
353        wrappedCmd = true;
354        awaitPromise = true;
355      }
356    }
357
358    // First, create the Script object to check the syntax
359    if (code === '\n')
360      return cb(null);
361
362    let parentURL;
363    try {
364      const { pathToFileURL } = require('url');
365      // Adding `/repl` prevents dynamic imports from loading relative
366      // to the parent of `process.cwd()`.
367      parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
368    } catch {
369    }
370    while (true) {
371      try {
372        if (!/^\s*$/.test(code) &&
373            self.replMode === exports.REPL_MODE_STRICT) {
374          // "void 0" keeps the repl from returning "use strict" as the result
375          // value for statements and declarations that don't return a value.
376          code = `'use strict'; void 0;\n${code}`;
377        }
378        script = vm.createScript(code, {
379          filename: file,
380          displayErrors: true,
381          importModuleDynamically: async (specifier) => {
382            return asyncESM.ESMLoader.import(specifier, parentURL);
383          }
384        });
385      } catch (e) {
386        debug('parse error %j', code, e);
387        if (wrappedCmd) {
388          // Unwrap and try again
389          wrappedCmd = false;
390          awaitPromise = false;
391          code = input;
392          wrappedErr = e;
393          continue;
394        }
395        // Preserve original error for wrapped command
396        const error = wrappedErr || e;
397        if (isRecoverableError(error, code))
398          err = new Recoverable(error);
399        else
400          err = error;
401      }
402      break;
403    }
404
405    // This will set the values from `savedRegExMatches` to corresponding
406    // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
407    regExMatcher.test(savedRegExMatches.join(sep));
408
409    let finished = false;
410    function finishExecution(err, result) {
411      if (finished) return;
412      finished = true;
413
414      // After executing the current expression, store the values of RegExp
415      // predefined properties back in `savedRegExMatches`
416      for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
417        savedRegExMatches[idx] = RegExp[`$${idx}`];
418      }
419
420      cb(err, result);
421    }
422
423    if (!err) {
424      // Unset raw mode during evaluation so that Ctrl+C raises a signal.
425      let previouslyInRawMode;
426      if (self.breakEvalOnSigint) {
427        // Start the SIGINT watchdog before entering raw mode so that a very
428        // quick Ctrl+C doesn't lead to aborting the process completely.
429        if (!startSigintWatchdog())
430          throw new ERR_CANNOT_WATCH_SIGINT();
431        previouslyInRawMode = self._setRawMode(false);
432      }
433
434      try {
435        try {
436          const scriptOptions = {
437            displayErrors: false,
438            breakOnSigint: self.breakEvalOnSigint
439          };
440
441          if (self.useGlobal) {
442            result = script.runInThisContext(scriptOptions);
443          } else {
444            result = script.runInContext(context, scriptOptions);
445          }
446        } finally {
447          if (self.breakEvalOnSigint) {
448            // Reset terminal mode to its previous value.
449            self._setRawMode(previouslyInRawMode);
450
451            // Returns true if there were pending SIGINTs *after* the script
452            // has terminated without being interrupted itself.
453            if (stopSigintWatchdog()) {
454              self.emit('SIGINT');
455            }
456          }
457        }
458      } catch (e) {
459        err = e;
460
461        if (process.domain) {
462          debug('not recoverable, send to domain');
463          process.domain.emit('error', err);
464          process.domain.exit();
465          return;
466        }
467      }
468
469      if (awaitPromise && !err) {
470        let sigintListener;
471        pause();
472        let promise = result;
473        if (self.breakEvalOnSigint) {
474          const interrupt = new Promise((resolve, reject) => {
475            sigintListener = () => {
476              const tmp = Error.stackTraceLimit;
477              Error.stackTraceLimit = 0;
478              const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED();
479              Error.stackTraceLimit = tmp;
480              reject(err);
481            };
482            prioritizedSigintQueue.add(sigintListener);
483          });
484          promise = PromiseRace([promise, interrupt]);
485        }
486
487        promise.then((result) => {
488          finishExecution(null, result);
489        }, (err) => {
490          if (err && process.domain) {
491            debug('not recoverable, send to domain');
492            process.domain.emit('error', err);
493            process.domain.exit();
494            return;
495          }
496          finishExecution(err);
497        }).finally(() => {
498          // Remove prioritized SIGINT listener if it was not called.
499          prioritizedSigintQueue.delete(sigintListener);
500          unpause();
501        });
502      }
503    }
504
505    if (!awaitPromise || err) {
506      finishExecution(err, result);
507    }
508  }
509
510  self.eval = self._domain.bind(eval_);
511
512  self._domain.on('error', function debugDomainError(e) {
513    debug('domain error');
514    let errStack = '';
515
516    if (typeof e === 'object' && e !== null) {
517      overrideStackTrace.set(e, (error, stackFrames) => {
518        let frames;
519        if (typeof stackFrames === 'object') {
520          // Search from the bottom of the call stack to
521          // find the first frame with a null function name
522          const idx = stackFrames
523            .reverse()
524            .findIndex((frame) => frame.getFunctionName() === null);
525          // If found, get rid of it and everything below it
526          frames = stackFrames.splice(idx + 1);
527        } else {
528          frames = stackFrames;
529        }
530        // FIXME(devsnek): this is inconsistent with the checks
531        // that the real prepareStackTrace dispatch uses in
532        // lib/internal/errors.js.
533        if (typeof Error.prepareStackTrace === 'function') {
534          return Error.prepareStackTrace(error, frames);
535        }
536        frames.push(error);
537        return frames.reverse().join('\n    at ');
538      });
539      decorateErrorStack(e);
540
541      if (e.domainThrown) {
542        delete e.domain;
543        delete e.domainThrown;
544      }
545
546      if (isError(e)) {
547        if (e.stack) {
548          if (e.name === 'SyntaxError') {
549            // Remove stack trace.
550            e.stack = e.stack
551              .replace(/^REPL\d+:\d+\r?\n/, '')
552              .replace(/^\s+at\s.*\n?/gm, '');
553            const importErrorStr = 'Cannot use import statement outside a ' +
554              'module';
555            if (StringPrototypeIncludes(e.message, importErrorStr)) {
556              e.message = 'Cannot use import statement inside the Node.js ' +
557                'repl, alternatively use dynamic import';
558              e.stack = e.stack.replace(/SyntaxError:.*\n/,
559                                        `SyntaxError: ${e.message}\n`);
560            }
561          } else if (self.replMode === exports.REPL_MODE_STRICT) {
562            e.stack = e.stack.replace(/(\s+at\s+REPL\d+:)(\d+)/,
563                                      (_, pre, line) => pre + (line - 1));
564          }
565        }
566        errStack = self.writer(e);
567
568        // Remove one line error braces to keep the old style in place.
569        if (errStack[errStack.length - 1] === ']') {
570          errStack = errStack.slice(1, -1);
571        }
572      }
573    }
574
575    if (!self.underscoreErrAssigned) {
576      self.lastError = e;
577    }
578
579    if (options[kStandaloneREPL] &&
580        process.listenerCount('uncaughtException') !== 0) {
581      process.nextTick(() => {
582        process.emit('uncaughtException', e);
583        self.clearBufferedCommand();
584        self.lines.level = [];
585        self.displayPrompt();
586      });
587    } else {
588      if (errStack === '') {
589        errStack = self.writer(e);
590      }
591      const lines = errStack.split(/(?<=\n)/);
592      let matched = false;
593
594      errStack = '';
595      for (const line of lines) {
596        if (!matched && /^\[?([A-Z][a-z0-9_]*)*Error/.test(line)) {
597          errStack += writer.options.breakLength >= line.length ?
598            `Uncaught ${line}` :
599            `Uncaught:\n${line}`;
600          matched = true;
601        } else {
602          errStack += line;
603        }
604      }
605      if (!matched) {
606        const ln = lines.length === 1 ? ' ' : ':\n';
607        errStack = `Uncaught${ln}${errStack}`;
608      }
609      // Normalize line endings.
610      errStack += errStack.endsWith('\n') ? '' : '\n';
611      self.outputStream.write(errStack);
612      self.clearBufferedCommand();
613      self.lines.level = [];
614      self.displayPrompt();
615    }
616  });
617
618  self.resetContext();
619  self.lines.level = [];
620
621  self.clearBufferedCommand();
622  ObjectDefineProperty(this, 'bufferedCommand', {
623    get: deprecate(() => self[kBufferedCommandSymbol],
624                   'REPLServer.bufferedCommand is deprecated',
625                   'DEP0074'),
626    set: deprecate((val) => self[kBufferedCommandSymbol] = val,
627                   'REPLServer.bufferedCommand is deprecated',
628                   'DEP0074'),
629    enumerable: true
630  });
631
632  // Figure out which "complete" function to use.
633  self.completer = (typeof options.completer === 'function') ?
634    options.completer : completer;
635
636  function completer(text, cb) {
637    complete.call(self, text, self.editorMode ?
638      self.completeOnEditorMode(cb) : cb);
639  }
640
641  Interface.call(this, {
642    input: self.inputStream,
643    output: self.outputStream,
644    completer: self.completer,
645    terminal: options.terminal,
646    historySize: options.historySize,
647    prompt
648  });
649
650  this.commands = ObjectCreate(null);
651  defineDefaultCommands(this);
652
653  // Figure out which "writer" function to use
654  self.writer = options.writer || exports.writer;
655
656  if (self.writer === writer) {
657    // Conditionally turn on ANSI coloring.
658    writer.options.colors = self.useColors;
659
660    if (options[kStandaloneREPL]) {
661      ObjectDefineProperty(inspect, 'replDefaults', {
662        get() {
663          return writer.options;
664        },
665        set(options) {
666          if (options === null || typeof options !== 'object') {
667            throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
668          }
669          return ObjectAssign(writer.options, options);
670        },
671        enumerable: true,
672        configurable: true
673      });
674    }
675  }
676
677  function _parseREPLKeyword(keyword, rest) {
678    const cmd = this.commands[keyword];
679    if (cmd) {
680      cmd.action.call(this, rest);
681      return true;
682    }
683    return false;
684  }
685
686  self.parseREPLKeyword = deprecate(
687    _parseREPLKeyword,
688    'REPLServer.parseREPLKeyword() is deprecated',
689    'DEP0075');
690
691  self.on('close', function emitExit() {
692    if (paused) {
693      pausedBuffer.push(['close']);
694      return;
695    }
696    self.emit('exit');
697  });
698
699  let sawSIGINT = false;
700  let sawCtrlD = false;
701  const prioritizedSigintQueue = new Set();
702  self.on('SIGINT', function onSigInt() {
703    if (prioritizedSigintQueue.size > 0) {
704      for (const task of prioritizedSigintQueue) {
705        task();
706      }
707      return;
708    }
709
710    const empty = self.line.length === 0;
711    self.clearLine();
712    _turnOffEditorMode(self);
713
714    const cmd = self[kBufferedCommandSymbol];
715    if (!(cmd && cmd.length > 0) && empty) {
716      if (sawSIGINT) {
717        self.close();
718        sawSIGINT = false;
719        return;
720      }
721      self.output.write('(To exit, press ^C again or ^D or type .exit)\n');
722      sawSIGINT = true;
723    } else {
724      sawSIGINT = false;
725    }
726
727    self.clearBufferedCommand();
728    self.lines.level = [];
729    self.displayPrompt();
730  });
731
732  self.on('line', function onLine(cmd) {
733    debug('line %j', cmd);
734    cmd = cmd || '';
735    sawSIGINT = false;
736
737    if (self.editorMode) {
738      self[kBufferedCommandSymbol] += cmd + '\n';
739
740      // code alignment
741      const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null;
742      if (matches) {
743        const prefix = matches[0];
744        self.write(prefix);
745        self.line = prefix;
746        self.cursor = prefix.length;
747      }
748      _memory.call(self, cmd);
749      return;
750    }
751
752    // Check REPL keywords and empty lines against a trimmed line input.
753    const trimmedCmd = cmd.trim();
754
755    // Check to see if a REPL keyword was used. If it returns true,
756    // display next prompt and return.
757    if (trimmedCmd) {
758      if (trimmedCmd.charAt(0) === '.' && trimmedCmd.charAt(1) !== '.' &&
759          NumberIsNaN(parseFloat(trimmedCmd))) {
760        const matches = trimmedCmd.match(/^\.([^\s]+)\s*(.*)$/);
761        const keyword = matches && matches[1];
762        const rest = matches && matches[2];
763        if (_parseREPLKeyword.call(self, keyword, rest) === true) {
764          return;
765        }
766        if (!self[kBufferedCommandSymbol]) {
767          self.outputStream.write('Invalid REPL keyword\n');
768          finish(null);
769          return;
770        }
771      }
772    }
773
774    const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n';
775
776    debug('eval %j', evalCmd);
777    self.eval(evalCmd, self.context, getREPLResourceName(), finish);
778
779    function finish(e, ret) {
780      debug('finish', e, ret);
781      _memory.call(self, cmd);
782
783      if (e && !self[kBufferedCommandSymbol] && cmd.trim().startsWith('npm ')) {
784        self.outputStream.write('npm should be run outside of the ' +
785                                'node repl, in your normal shell.\n' +
786                                '(Press Control-D to exit.)\n');
787        self.displayPrompt();
788        return;
789      }
790
791      // If error was SyntaxError and not JSON.parse error
792      if (e) {
793        if (e instanceof Recoverable && !sawCtrlD) {
794          // Start buffering data like that:
795          // {
796          // ...  x: 1
797          // ... }
798          self[kBufferedCommandSymbol] += cmd + '\n';
799          self.displayPrompt();
800          return;
801        }
802        self._domain.emit('error', e.err || e);
803      }
804
805      // Clear buffer if no SyntaxErrors
806      self.clearBufferedCommand();
807      sawCtrlD = false;
808
809      // If we got any output - print it (if no error)
810      if (!e &&
811          // When an invalid REPL command is used, error message is printed
812          // immediately. We don't have to print anything else. So, only when
813          // the second argument to this function is there, print it.
814          arguments.length === 2 &&
815          (!self.ignoreUndefined || ret !== undefined)) {
816        if (!self.underscoreAssigned) {
817          self.last = ret;
818        }
819        self.outputStream.write(self.writer(ret) + '\n');
820      }
821
822      // Display prompt again
823      self.displayPrompt();
824    }
825  });
826
827  self.on('SIGCONT', function onSigCont() {
828    if (self.editorMode) {
829      self.outputStream.write(`${self._initialPrompt}.editor\n`);
830      self.outputStream.write(
831        '// Entering editor mode (^D to finish, ^C to cancel)\n');
832      self.outputStream.write(`${self[kBufferedCommandSymbol]}\n`);
833      self.prompt(true);
834    } else {
835      self.displayPrompt(true);
836    }
837  });
838
839  const { reverseSearch } = setupReverseSearch(this);
840
841  const {
842    clearPreview,
843    showPreview
844  } = setupPreview(
845    this,
846    kContextId,
847    kBufferedCommandSymbol,
848    preview
849  );
850
851  // Wrap readline tty to enable editor mode and pausing.
852  const ttyWrite = self._ttyWrite.bind(self);
853  self._ttyWrite = (d, key) => {
854    key = key || {};
855    if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
856      pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]);
857      return;
858    }
859    if (!self.editorMode || !self.terminal) {
860      // Before exiting, make sure to clear the line.
861      if (key.ctrl && key.name === 'd' &&
862          self.cursor === 0 && self.line.length === 0) {
863        self.clearLine();
864      }
865      clearPreview();
866      if (!reverseSearch(d, key)) {
867        ttyWrite(d, key);
868        showPreview();
869      }
870      return;
871    }
872
873    // Editor mode
874    if (key.ctrl && !key.shift) {
875      switch (key.name) {
876        // TODO(BridgeAR): There should not be a special mode necessary for full
877        // multiline support.
878        case 'd': // End editor mode
879          _turnOffEditorMode(self);
880          sawCtrlD = true;
881          ttyWrite(d, { name: 'return' });
882          break;
883        case 'n': // Override next history item
884        case 'p': // Override previous history item
885          break;
886        default:
887          ttyWrite(d, key);
888      }
889    } else {
890      switch (key.name) {
891        case 'up':   // Override previous history item
892        case 'down': // Override next history item
893          break;
894        case 'tab':
895          // Prevent double tab behavior
896          self._previousKey = null;
897          ttyWrite(d, key);
898          break;
899        default:
900          ttyWrite(d, key);
901      }
902    }
903  };
904
905  self.displayPrompt();
906}
907ObjectSetPrototypeOf(REPLServer.prototype, Interface.prototype);
908ObjectSetPrototypeOf(REPLServer, Interface);
909
910exports.REPLServer = REPLServer;
911
912exports.REPL_MODE_SLOPPY = Symbol('repl-sloppy');
913exports.REPL_MODE_STRICT = Symbol('repl-strict');
914
915// Prompt is a string to print on each line for the prompt,
916// source is a stream to use for I/O, defaulting to stdin/stdout.
917exports.start = function(prompt,
918                         source,
919                         eval_,
920                         useGlobal,
921                         ignoreUndefined,
922                         replMode) {
923  const repl = new REPLServer(prompt,
924                              source,
925                              eval_,
926                              useGlobal,
927                              ignoreUndefined,
928                              replMode);
929  if (!exports.repl) exports.repl = repl;
930  return repl;
931};
932
933REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) {
934  history(this, historyFile, cb);
935};
936
937REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() {
938  this[kBufferedCommandSymbol] = '';
939};
940
941REPLServer.prototype.close = function close() {
942  if (this.terminal && this._flushing && !this._closingOnFlush) {
943    this._closingOnFlush = true;
944    this.once('flushHistory', () =>
945      Interface.prototype.close.call(this)
946    );
947
948    return;
949  }
950  process.nextTick(() =>
951    Interface.prototype.close.call(this)
952  );
953};
954
955REPLServer.prototype.createContext = function() {
956  let context;
957  if (this.useGlobal) {
958    context = global;
959  } else {
960    sendInspectorCommand((session) => {
961      session.post('Runtime.enable');
962      session.once('Runtime.executionContextCreated', ({ params }) => {
963        this[kContextId] = params.context.id;
964      });
965      context = vm.createContext();
966      session.post('Runtime.disable');
967    }, () => {
968      context = vm.createContext();
969    });
970    for (const name of ObjectGetOwnPropertyNames(global)) {
971      // Only set properties that do not already exist as a global builtin.
972      if (!globalBuiltins.has(name)) {
973        ObjectDefineProperty(context, name,
974                             ObjectGetOwnPropertyDescriptor(global, name));
975      }
976    }
977    context.global = context;
978    const _console = new Console(this.outputStream);
979    ObjectDefineProperty(context, 'console', {
980      configurable: true,
981      writable: true,
982      value: _console
983    });
984  }
985
986  const module = new CJSModule('<repl>');
987  module.paths = CJSModule._resolveLookupPaths('<repl>', parentModule);
988
989  ObjectDefineProperty(context, 'module', {
990    configurable: true,
991    writable: true,
992    value: module
993  });
994  ObjectDefineProperty(context, 'require', {
995    configurable: true,
996    writable: true,
997    value: makeRequireFunction(module)
998  });
999
1000  addBuiltinLibsToObject(context);
1001
1002  return context;
1003};
1004
1005REPLServer.prototype.resetContext = function() {
1006  this.context = this.createContext();
1007  this.underscoreAssigned = false;
1008  this.underscoreErrAssigned = false;
1009  this.lines = [];
1010  this.lines.level = [];
1011
1012  ObjectDefineProperty(this.context, '_', {
1013    configurable: true,
1014    get: () => this.last,
1015    set: (value) => {
1016      this.last = value;
1017      if (!this.underscoreAssigned) {
1018        this.underscoreAssigned = true;
1019        this.outputStream.write('Expression assignment to _ now disabled.\n');
1020      }
1021    }
1022  });
1023
1024  ObjectDefineProperty(this.context, '_error', {
1025    configurable: true,
1026    get: () => this.lastError,
1027    set: (value) => {
1028      this.lastError = value;
1029      if (!this.underscoreErrAssigned) {
1030        this.underscoreErrAssigned = true;
1031        this.outputStream.write(
1032          'Expression assignment to _error now disabled.\n');
1033      }
1034    }
1035  });
1036
1037  // Allow REPL extensions to extend the new context
1038  this.emit('reset', this.context);
1039};
1040
1041REPLServer.prototype.displayPrompt = function(preserveCursor) {
1042  let prompt = this._initialPrompt;
1043  if (this[kBufferedCommandSymbol].length) {
1044    prompt = '...';
1045    const len = this.lines.level.length ? this.lines.level.length - 1 : 0;
1046    const levelInd = '..'.repeat(len);
1047    prompt += levelInd + ' ';
1048  }
1049
1050  // Do not overwrite `_initialPrompt` here
1051  Interface.prototype.setPrompt.call(this, prompt);
1052  this.prompt(preserveCursor);
1053};
1054
1055// When invoked as an API method, overwrite _initialPrompt
1056REPLServer.prototype.setPrompt = function setPrompt(prompt) {
1057  this._initialPrompt = prompt;
1058  Interface.prototype.setPrompt.call(this, prompt);
1059};
1060
1061REPLServer.prototype.turnOffEditorMode = deprecate(
1062  function() { _turnOffEditorMode(this); },
1063  'REPLServer.turnOffEditorMode() is deprecated',
1064  'DEP0078');
1065
1066const requireRE = /\brequire\s*\(['"](([\w@./-]+\/)?(?:[\w@./-]*))/;
1067const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
1068const simpleExpressionRE =
1069    /(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/;
1070
1071function isIdentifier(str) {
1072  if (str === '') {
1073    return false;
1074  }
1075  const first = str.codePointAt(0);
1076  if (!isIdentifierStart(first)) {
1077    return false;
1078  }
1079  const firstLen = first > 0xffff ? 2 : 1;
1080  for (let i = firstLen; i < str.length; i += 1) {
1081    const cp = str.codePointAt(i);
1082    if (!isIdentifierChar(cp)) {
1083      return false;
1084    }
1085    if (cp > 0xffff) {
1086      i += 1;
1087    }
1088  }
1089  return true;
1090}
1091
1092function filteredOwnPropertyNames(obj) {
1093  if (!obj) return [];
1094  const filter = ALL_PROPERTIES | SKIP_SYMBOLS;
1095  return getOwnNonIndexProperties(obj, filter).filter(isIdentifier);
1096}
1097
1098function getGlobalLexicalScopeNames(contextId) {
1099  return sendInspectorCommand((session) => {
1100    let names = [];
1101    session.post('Runtime.globalLexicalScopeNames', {
1102      executionContextId: contextId
1103    }, (error, result) => {
1104      if (!error) names = result.names;
1105    });
1106    return names;
1107  }, () => []);
1108}
1109
1110REPLServer.prototype.complete = function() {
1111  this.completer.apply(this, arguments);
1112};
1113
1114// TODO: Native module names should be auto-resolved.
1115// That improves the auto completion.
1116
1117// Provide a list of completions for the given leading text. This is
1118// given to the readline interface for handling tab completion.
1119//
1120// Example:
1121//  complete('let foo = util.')
1122//    -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
1123//        'util.' ]
1124//
1125// Warning: This eval's code like "foo.bar.baz", so it will run property
1126// getter code.
1127function complete(line, callback) {
1128  // List of completion lists, one for each inheritance "level"
1129  let completionGroups = [];
1130  let completeOn, group;
1131
1132  // Ignore right whitespace. It could change the outcome.
1133  line = line.trimLeft();
1134
1135  // REPL commands (e.g. ".break").
1136  let filter;
1137  let match = line.match(/^\s*\.(\w*)$/);
1138  if (match) {
1139    completionGroups.push(ObjectKeys(this.commands));
1140    completeOn = match[1];
1141    if (match[1].length) {
1142      filter = match[1];
1143    }
1144
1145    completionGroupsLoaded();
1146  } else if (match = line.match(requireRE)) {
1147    // require('...<Tab>')
1148    const exts = ObjectKeys(this.context.require.extensions);
1149    const indexRe = new RegExp('^index(?:' + exts.map(regexpEscape).join('|') +
1150                             ')$');
1151    const versionedFileNamesRe = /-\d+\.\d+/;
1152
1153    completeOn = match[1];
1154    const subdir = match[2] || '';
1155    filter = match[1];
1156    let dir, files, subfiles, isDirectory;
1157    group = [];
1158    let paths = [];
1159
1160    if (completeOn === '.') {
1161      group = ['./', '../'];
1162    } else if (completeOn === '..') {
1163      group = ['../'];
1164    } else if (/^\.\.?\//.test(completeOn)) {
1165      paths = [process.cwd()];
1166    } else {
1167      paths = module.paths.concat(CJSModule.globalPaths);
1168    }
1169
1170    for (let i = 0; i < paths.length; i++) {
1171      dir = path.resolve(paths[i], subdir);
1172      try {
1173        files = fs.readdirSync(dir);
1174      } catch {
1175        continue;
1176      }
1177      for (let f = 0; f < files.length; f++) {
1178        const name = files[f];
1179        const ext = path.extname(name);
1180        const base = name.slice(0, -ext.length);
1181        if (versionedFileNamesRe.test(base) || name === '.npm') {
1182          // Exclude versioned names that 'npm' installs.
1183          continue;
1184        }
1185        const abs = path.resolve(dir, name);
1186        try {
1187          isDirectory = fs.statSync(abs).isDirectory();
1188        } catch {
1189          continue;
1190        }
1191        if (isDirectory) {
1192          group.push(subdir + name + '/');
1193          try {
1194            subfiles = fs.readdirSync(abs);
1195          } catch {
1196            continue;
1197          }
1198          for (let s = 0; s < subfiles.length; s++) {
1199            if (indexRe.test(subfiles[s])) {
1200              group.push(subdir + name);
1201            }
1202          }
1203        } else if (exts.includes(ext) && (!subdir || base !== 'index')) {
1204          group.push(subdir + base);
1205        }
1206      }
1207    }
1208    if (group.length) {
1209      completionGroups.push(group);
1210    }
1211
1212    if (!subdir) {
1213      completionGroups.push(exports._builtinLibs);
1214    }
1215
1216    completionGroupsLoaded();
1217  } else if (match = line.match(fsAutoCompleteRE)) {
1218
1219    let filePath = match[1];
1220    let fileList;
1221    filter = '';
1222
1223    try {
1224      fileList = fs.readdirSync(filePath, { withFileTypes: true });
1225      completionGroups.push(fileList.map((dirent) => dirent.name));
1226      completeOn = '';
1227    } catch {
1228      try {
1229        const baseName = path.basename(filePath);
1230        filePath = path.dirname(filePath);
1231        fileList = fs.readdirSync(filePath, { withFileTypes: true });
1232        const filteredValue = fileList.filter((d) =>
1233          d.name.startsWith(baseName))
1234          .map((d) => d.name);
1235        completionGroups.push(filteredValue);
1236        completeOn = baseName;
1237      } catch {}
1238    }
1239
1240    completionGroupsLoaded();
1241  // Handle variable member lookup.
1242  // We support simple chained expressions like the following (no function
1243  // calls, etc.). That is for simplicity and also because we *eval* that
1244  // leading expression so for safety (see WARNING above) don't want to
1245  // eval function calls.
1246  //
1247  //   foo.bar<|>     # completions for 'foo' with filter 'bar'
1248  //   spam.eggs.<|>  # completions for 'spam.eggs' with filter ''
1249  //   foo<|>         # all scope vars with filter 'foo'
1250  //   foo.<|>        # completions for 'foo' with filter ''
1251  } else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) {
1252    match = simpleExpressionRE.exec(line);
1253    if (line.length !== 0 && !match) {
1254      completionGroupsLoaded();
1255      return;
1256    }
1257    let expr;
1258    completeOn = (match ? match[0] : '');
1259    if (line.length === 0) {
1260      filter = '';
1261      expr = '';
1262    } else if (line[line.length - 1] === '.') {
1263      filter = '';
1264      expr = match[0].slice(0, match[0].length - 1);
1265    } else {
1266      const bits = match[0].split('.');
1267      filter = bits.pop();
1268      expr = bits.join('.');
1269    }
1270
1271    // Resolve expr and get its completions.
1272    const memberGroups = [];
1273    if (!expr) {
1274      // Get global vars synchronously
1275      completionGroups.push(getGlobalLexicalScopeNames(this[kContextId]));
1276      let contextProto = this.context;
1277      while (contextProto = ObjectGetPrototypeOf(contextProto)) {
1278        completionGroups.push(filteredOwnPropertyNames(contextProto));
1279      }
1280      const contextOwnNames = filteredOwnPropertyNames(this.context);
1281      if (!this.useGlobal) {
1282        // When the context is not `global`, builtins are not own
1283        // properties of it.
1284        contextOwnNames.push(...globalBuiltins);
1285      }
1286      completionGroups.push(contextOwnNames);
1287      if (filter !== '') addCommonWords(completionGroups);
1288      completionGroupsLoaded();
1289      return;
1290    }
1291
1292    const evalExpr = `try { ${expr} } catch {}`;
1293    this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => {
1294      if (obj != null) {
1295        if (typeof obj === 'object' || typeof obj === 'function') {
1296          try {
1297            memberGroups.push(filteredOwnPropertyNames(obj));
1298          } catch {
1299            // Probably a Proxy object without `getOwnPropertyNames` trap.
1300            // We simply ignore it here, as we don't want to break the
1301            // autocompletion. Fixes the bug
1302            // https://github.com/nodejs/node/issues/2119
1303          }
1304        }
1305        // Works for non-objects
1306        try {
1307          let p;
1308          if (typeof obj === 'object' || typeof obj === 'function') {
1309            p = ObjectGetPrototypeOf(obj);
1310          } else {
1311            p = obj.constructor ? obj.constructor.prototype : null;
1312          }
1313          // Circular refs possible? Let's guard against that.
1314          let sentinel = 5;
1315          while (p !== null && sentinel-- !== 0) {
1316            memberGroups.push(filteredOwnPropertyNames(p));
1317            p = ObjectGetPrototypeOf(p);
1318          }
1319        } catch {}
1320      }
1321
1322      if (memberGroups.length) {
1323        for (let i = 0; i < memberGroups.length; i++) {
1324          completionGroups.push(
1325            memberGroups[i].map((member) => `${expr}.${member}`));
1326        }
1327        if (filter) {
1328          filter = `${expr}.${filter}`;
1329        }
1330      }
1331
1332      completionGroupsLoaded();
1333    });
1334  } else {
1335    completionGroupsLoaded();
1336  }
1337
1338  // Will be called when all completionGroups are in place
1339  // Useful for async autocompletion
1340  function completionGroupsLoaded() {
1341    // Filter, sort (within each group), uniq and merge the completion groups.
1342    if (completionGroups.length && filter) {
1343      const newCompletionGroups = [];
1344      for (let i = 0; i < completionGroups.length; i++) {
1345        group = completionGroups[i]
1346          .filter((elem) => elem.indexOf(filter) === 0);
1347        if (group.length) {
1348          newCompletionGroups.push(group);
1349        }
1350      }
1351      completionGroups = newCompletionGroups;
1352    }
1353
1354    const completions = [];
1355    // Unique completions across all groups.
1356    const uniqueSet = new Set(['']);
1357    // Completion group 0 is the "closest" (least far up the inheritance
1358    // chain) so we put its completions last: to be closest in the REPL.
1359    for (const group of completionGroups) {
1360      group.sort((a, b) => (b > a ? 1 : -1));
1361      const setSize = uniqueSet.size;
1362      for (const entry of group) {
1363        if (!uniqueSet.has(entry)) {
1364          completions.unshift(entry);
1365          uniqueSet.add(entry);
1366        }
1367      }
1368      // Add a separator between groups.
1369      if (uniqueSet.size !== setSize) {
1370        completions.unshift('');
1371      }
1372    }
1373
1374    // Remove obsolete group entry, if present.
1375    if (completions[0] === '') {
1376      completions.shift();
1377    }
1378
1379    callback(null, [completions, completeOn]);
1380  }
1381}
1382
1383REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
1384  if (err) return callback(err);
1385
1386  const [completions, completeOn = ''] = results;
1387  let result = completions.filter((v) => v);
1388
1389  if (completeOn && result.length !== 0) {
1390    result = [commonPrefix(result)];
1391  }
1392
1393  callback(null, [result, completeOn]);
1394};
1395
1396REPLServer.prototype.defineCommand = function(keyword, cmd) {
1397  if (typeof cmd === 'function') {
1398    cmd = { action: cmd };
1399  } else if (typeof cmd.action !== 'function') {
1400    throw new ERR_INVALID_ARG_TYPE('cmd.action', 'Function', cmd.action);
1401  }
1402  this.commands[keyword] = cmd;
1403};
1404
1405REPLServer.prototype.memory = deprecate(
1406  _memory,
1407  'REPLServer.memory() is deprecated',
1408  'DEP0082');
1409
1410// TODO(BridgeAR): This should be replaced with acorn to build an AST. The
1411// language became more complex and using a simple approach like this is not
1412// sufficient anymore.
1413function _memory(cmd) {
1414  const self = this;
1415  self.lines = self.lines || [];
1416  self.lines.level = self.lines.level || [];
1417
1418  // Save the line so I can do magic later
1419  if (cmd) {
1420    const len = self.lines.level.length ? self.lines.level.length - 1 : 0;
1421    self.lines.push('  '.repeat(len) + cmd);
1422  } else {
1423    // I don't want to not change the format too much...
1424    self.lines.push('');
1425  }
1426
1427  if (!cmd) {
1428    self.lines.level = [];
1429    return;
1430  }
1431
1432  // I need to know "depth."
1433  // Because I can not tell the difference between a } that
1434  // closes an object literal and a } that closes a function
1435
1436  // Going down is { and (   e.g. function() {
1437  // going up is } and )
1438  let dw = cmd.match(/[{(]/g);
1439  let up = cmd.match(/[})]/g);
1440  up = up ? up.length : 0;
1441  dw = dw ? dw.length : 0;
1442  let depth = dw - up;
1443
1444  if (depth) {
1445    (function workIt() {
1446      if (depth > 0) {
1447        // Going... down.
1448        // Push the line#, depth count, and if the line is a function.
1449        // Since JS only has functional scope I only need to remove
1450        // "function() {" lines, clearly this will not work for
1451        // "function()
1452        // {" but nothing should break, only tab completion for local
1453        // scope will not work for this function.
1454        self.lines.level.push({
1455          line: self.lines.length - 1,
1456          depth: depth
1457        });
1458      } else if (depth < 0) {
1459        // Going... up.
1460        const curr = self.lines.level.pop();
1461        if (curr) {
1462          const tmp = curr.depth + depth;
1463          if (tmp < 0) {
1464            // More to go, recurse
1465            depth += curr.depth;
1466            workIt();
1467          } else if (tmp > 0) {
1468            // Remove and push back
1469            curr.depth += depth;
1470            self.lines.level.push(curr);
1471          }
1472        }
1473      }
1474    }());
1475  }
1476}
1477
1478function addCommonWords(completionGroups) {
1479  // Only words which do not yet exist as global property should be added to
1480  // this list.
1481  completionGroups.push([
1482    'async', 'await', 'break', 'case', 'catch', 'const', 'continue',
1483    'debugger', 'default', 'delete', 'do', 'else', 'export', 'false',
1484    'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let',
1485    'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try',
1486    'typeof', 'var', 'void', 'while', 'with', 'yield'
1487  ]);
1488}
1489
1490function _turnOnEditorMode(repl) {
1491  repl.editorMode = true;
1492  Interface.prototype.setPrompt.call(repl, '');
1493}
1494
1495function _turnOffEditorMode(repl) {
1496  repl.editorMode = false;
1497  repl.setPrompt(repl._initialPrompt);
1498}
1499
1500function defineDefaultCommands(repl) {
1501  repl.defineCommand('break', {
1502    help: 'Sometimes you get stuck, this gets you out',
1503    action: function() {
1504      this.clearBufferedCommand();
1505      this.displayPrompt();
1506    }
1507  });
1508
1509  let clearMessage;
1510  if (repl.useGlobal) {
1511    clearMessage = 'Alias for .break';
1512  } else {
1513    clearMessage = 'Break, and also clear the local context';
1514  }
1515  repl.defineCommand('clear', {
1516    help: clearMessage,
1517    action: function() {
1518      this.clearBufferedCommand();
1519      if (!this.useGlobal) {
1520        this.outputStream.write('Clearing context...\n');
1521        this.resetContext();
1522      }
1523      this.displayPrompt();
1524    }
1525  });
1526
1527  repl.defineCommand('exit', {
1528    help: 'Exit the repl',
1529    action: function() {
1530      this.close();
1531    }
1532  });
1533
1534  repl.defineCommand('help', {
1535    help: 'Print this help message',
1536    action: function() {
1537      const names = ObjectKeys(this.commands).sort();
1538      const longestNameLength = names.reduce(
1539        (max, name) => MathMax(max, name.length),
1540        0
1541      );
1542      for (let n = 0; n < names.length; n++) {
1543        const name = names[n];
1544        const cmd = this.commands[name];
1545        const spaces = ' '.repeat(longestNameLength - name.length + 3);
1546        const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`;
1547        this.outputStream.write(line);
1548      }
1549      this.outputStream.write('\nPress ^C to abort current expression, ' +
1550        '^D to exit the repl\n');
1551      this.displayPrompt();
1552    }
1553  });
1554
1555  repl.defineCommand('save', {
1556    help: 'Save all evaluated commands in this REPL session to a file',
1557    action: function(file) {
1558      try {
1559        fs.writeFileSync(file, this.lines.join('\n'));
1560        this.outputStream.write(`Session saved to: ${file}\n`);
1561      } catch {
1562        this.outputStream.write(`Failed to save: ${file}\n`);
1563      }
1564      this.displayPrompt();
1565    }
1566  });
1567
1568  repl.defineCommand('load', {
1569    help: 'Load JS from a file into the REPL session',
1570    action: function(file) {
1571      try {
1572        const stats = fs.statSync(file);
1573        if (stats && stats.isFile()) {
1574          _turnOnEditorMode(this);
1575          const data = fs.readFileSync(file, 'utf8');
1576          this.write(data);
1577          _turnOffEditorMode(this);
1578          this.write('\n');
1579        } else {
1580          this.outputStream.write(
1581            `Failed to load: ${file} is not a valid file\n`
1582          );
1583        }
1584      } catch {
1585        this.outputStream.write(`Failed to load: ${file}\n`);
1586      }
1587      this.displayPrompt();
1588    }
1589  });
1590  if (repl.terminal) {
1591    repl.defineCommand('editor', {
1592      help: 'Enter editor mode',
1593      action() {
1594        _turnOnEditorMode(this);
1595        this.outputStream.write(
1596          '// Entering editor mode (^D to finish, ^C to cancel)\n');
1597      }
1598    });
1599  }
1600}
1601
1602function regexpEscape(s) {
1603  return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
1604}
1605
1606function Recoverable(err) {
1607  this.err = err;
1608}
1609ObjectSetPrototypeOf(Recoverable.prototype, SyntaxError.prototype);
1610ObjectSetPrototypeOf(Recoverable, SyntaxError);
1611exports.Recoverable = Recoverable;
1612