UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

2,031 lines 62.3 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/repl.js

import * as __hoisted_internal_url__ from "nstdlib/lib/internal/url";
import * as __hoisted_internal_modules_esm_loader__ from "nstdlib/lib/internal/modules/esm/loader";
import * as __hoisted_internal_repl_await__ from "nstdlib/lib/internal/repl/await";
import { BuiltinModule } from "nstdlib/lib/internal/bootstrap/realm";
import {
  makeRequireFunction,
  addBuiltinLibsToObject,
} from "nstdlib/lib/internal/modules/helpers";
import {
  isIdentifierStart,
  isIdentifierChar,
  parse as acornParse,
} from "nstdlib/stub/internal/deps/acorn/acorn/dist/acorn";
import * as acornWalk from "nstdlib/stub/internal/deps/acorn/acorn-walk/dist/walk";
import {
  decorateErrorStack,
  isError,
  deprecate,
  SideEffectFreeRegExpPrototypeSymbolReplace,
  SideEffectFreeRegExpPrototypeSymbolSplit,
} from "nstdlib/lib/internal/util";
import { inspect } from "nstdlib/lib/internal/util/inspect";
import * as vm from "nstdlib/lib/vm";
import * as path from "nstdlib/lib/path";
import * as fs from "nstdlib/lib/fs";
import { Interface } from "nstdlib/lib/readline";
import { commonPrefix } from "nstdlib/lib/internal/readline/utils";
import { Console } from "nstdlib/lib/console";
import { shouldColorize } from "nstdlib/lib/internal/util/colors";
import { Module as CJSModule } from "nstdlib/lib/internal/modules/cjs/loader";
import * as domain from "nstdlib/lib/domain";
import {
  ErrorPrepareStackTrace,
  codes as __codes__,
  isErrorStackTraceLimitWritable,
  overrideStackTrace,
} from "nstdlib/lib/internal/errors";
import { sendInspectorCommand } from "nstdlib/lib/internal/util/inspector";
import { getOptionValue } from "nstdlib/lib/internal/options";
import {
  validateFunction,
  validateObject,
} from "nstdlib/lib/internal/validators";
import {
  REPL_MODE_SLOPPY,
  REPL_MODE_STRICT,
  isRecoverableError,
  kStandaloneREPL,
  setupPreview,
  setupReverseSearch,
} from "nstdlib/lib/internal/repl/utils";
import {
  constants as __constants__,
  getOwnNonIndexProperties,
} from "nstdlib/stub/binding/util";
import {
  startSigintWatchdog,
  stopSigintWatchdog,
} from "nstdlib/stub/binding/contextify";
import * as history from "nstdlib/lib/internal/repl/history";
import { extensionFormatMap } from "nstdlib/lib/internal/modules/esm/formats";
import { makeContextifyScript } from "nstdlib/lib/internal/vm";
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

/* A REPL library that you can include in your own code to get a runtime
 * interface to your program.
 *
 *   const repl = require("repl");
 *   // start repl on stdin
 *   repl.start("prompt> ");
 *
 *   // listen for unix socket connections and start repl on them
 *   net.createServer(function(socket) {
 *     repl.start("node via Unix socket> ", socket);
 *   }).listen("/tmp/node-repl-sock");
 *
 *   // listen for TCP socket connections and start repl on them
 *   net.createServer(function(socket) {
 *     repl.start("node via TCP socket> ", socket);
 *   }).listen(5001);
 *
 *   // expose foo to repl context
 *   repl.start("node > ").context.foo = "stdin is fun";
 */

const { runInThisContext, runInContext } = vm.Script.prototype;

let _builtinLibs = Array.prototype.filter.call(
  CJSModule.builtinModules,
  (e) => !String.prototype.startsWith.call(e, "_"),
);
const nodeSchemeBuiltinLibs = Array.prototype.map.call(
  _builtinLibs,
  (lib) => `node:${lib}`,
);
Array.prototype.forEach.call(BuiltinModule.getSchemeOnlyModuleNames(), (lib) =>
  Array.prototype.push.call(nodeSchemeBuiltinLibs, `node:${lib}`),
);

const {
  ERR_CANNOT_WATCH_SIGINT,
  ERR_INVALID_REPL_EVAL_CONFIG,
  ERR_INVALID_REPL_INPUT,
  ERR_MISSING_ARGS,
  ERR_SCRIPT_EXECUTION_INTERRUPTED,
} = __codes__;
const experimentalREPLAwait = getOptionValue("--experimental-repl-await");
const pendingDeprecation = getOptionValue("--pending-deprecation");

const { ALL_PROPERTIES, SKIP_SYMBOLS } = __constants__;

let nextREPLResourceNumber = 1;
// This prevents v8 code cache from getting confused and using a different
// cache from a resource of the same name
function getREPLResourceName() {
  return `REPL${nextREPLResourceNumber++}`;
}

// Lazy-loaded.
let processTopLevelAwait;

const globalBuiltins = new Set(
  vm.runInNewContext("Object.getOwnPropertyNames(globalThis)"),
);

const parentModule = module;
const domainSet = new WeakSet();

const kBufferedCommandSymbol = Symbol("bufferedCommand");
const kContextId = Symbol("contextId");
const kLoadingSymbol = Symbol("loading");

let addedNewListener = false;

try {
  // Hack for require.resolve("./relative") to work properly.
  module.filename = path.resolve("repl");
} catch {
  // path.resolve('repl') fails when the current working directory has been
  // deleted.  Fall back to the directory name of the (absolute) executable
  // path.  It's not really correct but what are the alternatives?
  const dirname = path.dirname(process.execPath);
  module.filename = path.resolve(dirname, "repl");
}

// Hack for repl require to work properly with node_modules folders
module.paths = CJSModule._nodeModulePaths(module.filename);

// This is the default "writer" value, if none is passed in the REPL options,
// and it can be overridden by custom print functions, such as `probe` or
// `eyes.js`.
const writer = (obj) => inspect(obj, writer.options);
writer.options = { ...inspect.defaultOptions, showProxy: true };

// Converts static import statement to dynamic import statement
const toDynamicImport = (codeLine) => {
  let dynamicImportStatement = "";
  const ast = acornParse(codeLine, {
    __proto__: null,
    sourceType: "module",
    ecmaVersion: "latest",
  });
  acornWalk.ancestor(ast, {
    ImportDeclaration(node) {
      const awaitDynamicImport = `await import(${JSONStringify(node.source.value)});`;
      if (node.specifiers.length === 0) {
        dynamicImportStatement += awaitDynamicImport;
      } else if (
        node.specifiers.length === 1 &&
        node.specifiers[0].type === "ImportNamespaceSpecifier"
      ) {
        dynamicImportStatement += `const ${node.specifiers[0].local.name} = ${awaitDynamicImport}`;
      } else {
        const importNames = Array.prototype.join.call(
          Array.prototype.map.call(node.specifiers, ({ local, imported }) =>
            local.name === imported?.name
              ? local.name
              : `${imported?.name ?? "default"}: ${local.name}`,
          ),
          ", ",
        );
        dynamicImportStatement += `const { ${importNames} } = ${awaitDynamicImport}`;
      }
    },
  });
  return dynamicImportStatement;
};

function REPLServer(
  prompt,
  stream,
  eval_,
  useGlobal,
  ignoreUndefined,
  replMode,
) {
  if (!(this instanceof REPLServer)) {
    return new REPLServer(
      prompt,
      stream,
      eval_,
      useGlobal,
      ignoreUndefined,
      replMode,
    );
  }

  let options;
  if (prompt !== null && typeof prompt === "object") {
    // An options object was given.
    options = { ...prompt };
    stream = options.stream || options.socket;
    eval_ = options.eval;
    useGlobal = options.useGlobal;
    ignoreUndefined = options.ignoreUndefined;
    prompt = options.prompt;
    replMode = options.replMode;
  } else {
    options = {};
  }

  if (!options.input && !options.output) {
    // Legacy API, passing a 'stream'/'socket' option.
    if (!stream) {
      // Use stdin and stdout as the default streams if none were given.
      stream = process;
    }
    // We're given a duplex readable/writable Stream, like a `net.Socket`
    // or a custom object with 2 streams, or the `process` object.
    options.input = stream.stdin || stream;
    options.output = stream.stdout || stream;
  }

  if (options.terminal === undefined) {
    options.terminal = options.output.isTTY;
  }
  options.terminal = !!options.terminal;

  if (options.terminal && options.useColors === undefined) {
    // If possible, check if stdout supports colors or not.
    options.useColors = shouldColorize(options.output);
  }

  // TODO(devsnek): Add a test case for custom eval functions.
  const preview =
    options.terminal &&
    (options.preview !== undefined ? !!options.preview : !eval_);

  Object.defineProperty(this, "inputStream", {
    __proto__: null,
    get: pendingDeprecation
      ? deprecate(
          () => this.input,
          "repl.inputStream and repl.outputStream are deprecated. " +
            "Use repl.input and repl.output instead",
          "DEP0141",
        )
      : () => this.input,
    set: pendingDeprecation
      ? deprecate(
          (val) => (this.input = val),
          "repl.inputStream and repl.outputStream are deprecated. " +
            "Use repl.input and repl.output instead",
          "DEP0141",
        )
      : (val) => (this.input = val),
    enumerable: false,
    configurable: true,
  });
  Object.defineProperty(this, "outputStream", {
    __proto__: null,
    get: pendingDeprecation
      ? deprecate(
          () => this.output,
          "repl.inputStream and repl.outputStream are deprecated. " +
            "Use repl.input and repl.output instead",
          "DEP0141",
        )
      : () => this.output,
    set: pendingDeprecation
      ? deprecate(
          (val) => (this.output = val),
          "repl.inputStream and repl.outputStream are deprecated. " +
            "Use repl.input and repl.output instead",
          "DEP0141",
        )
      : (val) => (this.output = val),
    enumerable: false,
    configurable: true,
  });

  this.allowBlockingCompletions = !!options.allowBlockingCompletions;
  this.useColors = !!options.useColors;
  this._domain = options.domain || domain.create();
  this.useGlobal = !!useGlobal;
  this.ignoreUndefined = !!ignoreUndefined;
  this.replMode = replMode || module.exports.REPL_MODE_SLOPPY;
  this.underscoreAssigned = false;
  this.last = undefined;
  this.underscoreErrAssigned = false;
  this.lastError = undefined;
  this.breakEvalOnSigint = !!options.breakEvalOnSigint;
  this.editorMode = false;
  // Context id for use with the inspector protocol.
  this[kContextId] = undefined;

  if (this.breakEvalOnSigint && eval_) {
    // Allowing this would not reflect user expectations.
    // breakEvalOnSigint affects only the behavior of the default eval().
    throw new ERR_INVALID_REPL_EVAL_CONFIG();
  }

  if (options[kStandaloneREPL]) {
    // It is possible to introspect the running REPL accessing this variable
    // from inside the REPL. This is useful for anyone working on the REPL.
    module.exports.repl = this;
  } else if (!addedNewListener) {
    // Add this listener only once and use a WeakSet that contains the REPLs
    // domains. Otherwise we'd have to add a single listener to each REPL
    // instance and that could trigger the `MaxListenersExceededWarning`.
    process.prependListener("newListener", (event, listener) => {
      if (
        event === "uncaughtException" &&
        process.domain &&
        listener.name !== "domainUncaughtExceptionClear" &&
        domainSet.has(process.domain)
      ) {
        // Throw an error so that the event will not be added and the current
        // domain takes over. That way the user is notified about the error
        // and the current code evaluation is stopped, just as any other code
        // that contains an error.
        throw new ERR_INVALID_REPL_INPUT(
          "Listeners for `uncaughtException` cannot be used in the REPL",
        );
      }
    });
    addedNewListener = true;
  }

  domainSet.add(this._domain);

  const savedRegExMatches = ["", "", "", "", "", "", "", "", "", ""];
  const sep = "\u0000\u0000\u0000";
  const regExMatcher = new RegExp(
    `^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
      `${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
      `${sep}(.*)$`,
  );

  eval_ = eval_ || defaultEval;

  const self = this;

  // Pause taking in new input, and store the keys in a buffer.
  const pausedBuffer = [];
  let paused = false;
  function pause() {
    paused = true;
  }

  function unpause() {
    if (!paused) return;
    paused = false;
    let entry;
    const tmpCompletionEnabled = self.isCompletionEnabled;
    while ((entry = Array.prototype.shift.call(pausedBuffer)) !== undefined) {
      const { 0: type, 1: payload, 2: isCompletionEnabled } = entry;
      switch (type) {
        case "key": {
          const { 0: d, 1: key } = payload;
          self.isCompletionEnabled = isCompletionEnabled;
          self._ttyWrite(d, key);
          break;
        }
        case "close":
          self.emit("exit");
          break;
      }
      if (paused) {
        break;
      }
    }
    self.isCompletionEnabled = tmpCompletionEnabled;
  }

  function defaultEval(code, context, file, cb) {
    let result, script, wrappedErr;
    let err = null;
    let wrappedCmd = false;
    let awaitPromise = false;
    const input = code;

    // It's confusing for `{ a : 1 }` to be interpreted as a block
    // statement rather than an object literal.  So, we first try
    // to wrap it in parentheses, so that it will be interpreted as
    // an expression.  Note that if the above condition changes,
    // lib/internal/repl/utils.js needs to be changed to match.
    if (
      RegExp.prototype.exec.call(/^\s*{/, code) !== null &&
      RegExp.prototype.exec.call(/;\s*$/, code) === null
    ) {
      code = `(${String.prototype.trim.call(code)})\n`;
      wrappedCmd = true;
    }

    const hostDefinedOptionId = Symbol(`eval:${file}`);
    let parentURL;
    try {
      const { pathToFileURL } = __hoisted_internal_url__;
      // Adding `/repl` prevents dynamic imports from loading relative
      // to the parent of `process.cwd()`.
      parentURL = pathToFileURL(path.join(process.cwd(), "repl")).href;
    } catch {
      // Continue regardless of error.
    }
    async function importModuleDynamically(specifier, _, importAttributes) {
      const cascadedLoader =
        __hoisted_internal_modules_esm_loader__.getOrInitializeCascadedLoader();
      return cascadedLoader.import(specifier, parentURL, importAttributes);
    }
    // `experimentalREPLAwait` is set to true by default.
    // Shall be false in case `--no-experimental-repl-await` flag is used.
    if (
      experimentalREPLAwait &&
      String.prototype.includes.call(code, "await")
    ) {
      if (processTopLevelAwait === undefined) {
        ({ processTopLevelAwait } = __hoisted_internal_repl_await__);
      }

      try {
        const potentialWrappedCode = processTopLevelAwait(code);
        if (potentialWrappedCode !== null) {
          code = potentialWrappedCode;
          wrappedCmd = true;
          awaitPromise = true;
        }
      } catch (e) {
        let recoverableError = false;
        if (e.name === "SyntaxError") {
          // Remove all "await"s and attempt running the script
          // in order to detect if error is truly non recoverable
          const fallbackCode = SideEffectFreeRegExpPrototypeSymbolReplace(
            /\bawait\b/g,
            code,
            "",
          );
          try {
            makeContextifyScript(
              fallbackCode, // code
              file, // filename,
              0, // lineOffset
              0, // columnOffset,
              undefined, // cachedData
              false, // produceCachedData
              undefined, // parsingContext
              hostDefinedOptionId, // hostDefinedOptionId
              importModuleDynamically, // importModuleDynamically
            );
          } catch (fallbackError) {
            if (isRecoverableError(fallbackError, fallbackCode)) {
              recoverableError = true;
              err = new Recoverable(e);
            }
          }
        }
        if (!recoverableError) {
          decorateErrorStack(e);
          err = e;
        }
      }
    }

    // First, create the Script object to check the syntax
    if (code === "\n") return cb(null);

    if (err === null) {
      while (true) {
        try {
          if (
            self.replMode === module.exports.REPL_MODE_STRICT &&
            RegExp.prototype.exec.call(/^\s*$/, code) === null
          ) {
            // "void 0" keeps the repl from returning "use strict" as the result
            // value for statements and declarations that don't return a value.
            code = `'use strict'; void 0;\n${code}`;
          }
          script = makeContextifyScript(
            code, // code
            file, // filename,
            0, // lineOffset
            0, // columnOffset,
            undefined, // cachedData
            false, // produceCachedData
            undefined, // parsingContext
            hostDefinedOptionId, // hostDefinedOptionId
            importModuleDynamically, // importModuleDynamically
          );
        } catch (e) {
          {
            /* debug */
          }
          if (wrappedCmd) {
            // Unwrap and try again
            wrappedCmd = false;
            awaitPromise = false;
            code = input;
            wrappedErr = e;
            continue;
          }
          // Preserve original error for wrapped command
          const error = wrappedErr || e;
          if (isRecoverableError(error, code)) err = new Recoverable(error);
          else err = error;
        }
        break;
      }
    }

    // This will set the values from `savedRegExMatches` to corresponding
    // predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
    RegExp.prototype.exec.call(
      regExMatcher,
      Array.prototype.join.call(savedRegExMatches, sep),
    );

    let finished = false;
    function finishExecution(err, result) {
      if (finished) return;
      finished = true;

      // After executing the current expression, store the values of RegExp
      // predefined properties back in `savedRegExMatches`
      for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
        savedRegExMatches[idx] = RegExp[`$${idx}`];
      }

      cb(err, result);
    }

    if (!err) {
      // Unset raw mode during evaluation so that Ctrl+C raises a signal.
      let previouslyInRawMode;
      if (self.breakEvalOnSigint) {
        // Start the SIGINT watchdog before entering raw mode so that a very
        // quick Ctrl+C doesn't lead to aborting the process completely.
        if (!startSigintWatchdog()) throw new ERR_CANNOT_WATCH_SIGINT();
        previouslyInRawMode = self._setRawMode(false);
      }

      try {
        try {
          const scriptOptions = {
            displayErrors: false,
            breakOnSigint: self.breakEvalOnSigint,
          };

          if (self.useGlobal) {
            result = ReflectApply(runInThisContext, script, [scriptOptions]);
          } else {
            result = ReflectApply(runInContext, script, [
              context,
              scriptOptions,
            ]);
          }
        } finally {
          if (self.breakEvalOnSigint) {
            // Reset terminal mode to its previous value.
            self._setRawMode(previouslyInRawMode);

            // Returns true if there were pending SIGINTs *after* the script
            // has terminated without being interrupted itself.
            if (stopSigintWatchdog()) {
              self.emit("SIGINT");
            }
          }
        }
      } catch (e) {
        err = e;

        if (process.domain) {
          {
            /* debug */
          }
          process.domain.emit("error", err);
          process.domain.exit();
          return;
        }
      }

      if (awaitPromise && !err) {
        let sigintListener;
        pause();
        let promise = result;
        if (self.breakEvalOnSigint) {
          const interrupt = new Promise((resolve, reject) => {
            sigintListener = () => {
              const tmp = MainContextError.stackTraceLimit;
              if (isErrorStackTraceLimitWritable())
                MainContextError.stackTraceLimit = 0;
              const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED();
              if (isErrorStackTraceLimitWritable())
                MainContextError.stackTraceLimit = tmp;
              reject(err);
            };
            prioritizedSigintQueue.add(sigintListener);
          });
          promise = Promise.race([promise, interrupt]);
        }

        (async () => {
          try {
            const result = (await promise)?.value;
            finishExecution(null, result);
          } catch (err) {
            if (err && process.domain) {
              {
                /* debug */
              }
              process.domain.emit("error", err);
              process.domain.exit();
              return;
            }
            finishExecution(err);
          } finally {
            // Remove prioritized SIGINT listener if it was not called.
            prioritizedSigintQueue.delete(sigintListener);
            unpause();
          }
        })();
      }
    }

    if (!awaitPromise || err) {
      finishExecution(err, result);
    }
  }

  self.eval = self._domain.bind(eval_);

  self._domain.on("error", function debugDomainError(e) {
    {
      /* debug */
    }
    let errStack = "";

    if (typeof e === "object" && e !== null) {
      overrideStackTrace.set(e, (error, stackFrames) => {
        let frames;
        if (typeof stackFrames === "object") {
          // Search from the bottom of the call stack to
          // find the first frame with a null function name
          const idx = Array.prototype.findLastIndex.call(
            stackFrames,
            (frame) => frame.getFunctionName() === null,
          );
          // If found, get rid of it and everything below it
          frames = Array.prototype.slice.call(stackFrames, 0, idx);
        } else {
          frames = stackFrames;
        }
        // FIXME(devsnek): this is inconsistent with the checks
        // that the real prepareStackTrace dispatch uses in
        // lib/internal/errors.js.
        if (typeof MainContextError.prepareStackTrace === "function") {
          return MainContextError.prepareStackTrace(error, frames);
        }
        return Error.prepareStackTrace(error, frames);
      });
      decorateErrorStack(e);

      if (e.domainThrown) {
        delete e.domain;
        delete e.domainThrown;
      }

      if (isError(e)) {
        if (e.stack) {
          if (e.name === "SyntaxError") {
            // Remove stack trace.
            e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
              /^\s+at\s.*\n?/gm,
              SideEffectFreeRegExpPrototypeSymbolReplace(
                /^REPL\d+:\d+\r?\n/,
                e.stack,
                "",
              ),
              "",
            );
            const importErrorStr =
              "Cannot use import statement outside a " + "module";
            if (String.prototype.includes.call(e.message, importErrorStr)) {
              e.message =
                "Cannot use import statement inside the Node.js " +
                "REPL, alternatively use dynamic import: " +
                toDynamicImport(Array.prototype.at.call(self.lines, -1));
              e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
                /SyntaxError:.*\n/,
                e.stack,
                `SyntaxError: ${e.message}\n`,
              );
            }
          } else if (self.replMode === module.exports.REPL_MODE_STRICT) {
            e.stack = SideEffectFreeRegExpPrototypeSymbolReplace(
              /(\s+at\s+REPL\d+:)(\d+)/,
              e.stack,
              (_, pre, line) => pre + (line - 1),
            );
          }
        }
        errStack = self.writer(e);

        // Remove one line error braces to keep the old style in place.
        if (errStack[0] === "[" && errStack[errStack.length - 1] === "]") {
          errStack = String.prototype.slice.call(errStack, 1, -1);
        }
      }
    }

    if (!self.underscoreErrAssigned) {
      self.lastError = e;
    }

    if (
      options[kStandaloneREPL] &&
      process.listenerCount("uncaughtException") !== 0
    ) {
      process.nextTick(() => {
        process.emit("uncaughtException", e);
        self.clearBufferedCommand();
        self.lines.level = [];
        self.displayPrompt();
      });
    } else {
      if (errStack === "") {
        errStack = self.writer(e);
      }
      const lines = SideEffectFreeRegExpPrototypeSymbolSplit(
        /(?<=\n)/,
        errStack,
      );
      let matched = false;

      errStack = "";
      Array.prototype.forEach.call(lines, (line) => {
        if (
          !matched &&
          RegExp.prototype.exec.call(/^\[?([A-Z][a-z0-9_]*)*Error/, line) !==
            null
        ) {
          errStack +=
            writer.options.breakLength >= line.length
              ? `Uncaught ${line}`
              : `Uncaught:\n${line}`;
          matched = true;
        } else {
          errStack += line;
        }
      });
      if (!matched) {
        const ln = lines.length === 1 ? " " : ":\n";
        errStack = `Uncaught${ln}${errStack}`;
      }
      // Normalize line endings.
      errStack += String.prototype.endsWith.call(errStack, "\n") ? "" : "\n";
      self.output.write(errStack);
      self.clearBufferedCommand();
      self.lines.level = [];
      self.displayPrompt();
    }
  });

  self.clearBufferedCommand();

  function completer(text, cb) {
    ReflectApply(complete, self, [
      text,
      self.editorMode ? self.completeOnEditorMode(cb) : cb,
    ]);
  }

  ReflectApply(Interface, this, [
    {
      input: options.input,
      output: options.output,
      completer: options.completer || completer,
      terminal: options.terminal,
      historySize: options.historySize,
      prompt,
    },
  ]);

  self.resetContext();

  this.commands = { __proto__: null };
  defineDefaultCommands(this);

  // Figure out which "writer" function to use
  self.writer = options.writer || module.exports.writer;

  if (self.writer === writer) {
    // Conditionally turn on ANSI coloring.
    writer.options.colors = self.useColors;

    if (options[kStandaloneREPL]) {
      Object.defineProperty(inspect, "replDefaults", {
        __proto__: null,
        get() {
          return writer.options;
        },
        set(options) {
          validateObject(options, "options");
          return Object.assign(writer.options, options);
        },
        enumerable: true,
        configurable: true,
      });
    }
  }

  function _parseREPLKeyword(keyword, rest) {
    const cmd = this.commands[keyword];
    if (cmd) {
      ReflectApply(cmd.action, this, [rest]);
      return true;
    }
    return false;
  }

  self.on("close", function emitExit() {
    if (paused) {
      Array.prototype.push.call(pausedBuffer, ["close"]);
      return;
    }
    self.emit("exit");
  });

  let sawSIGINT = false;
  let sawCtrlD = false;
  const prioritizedSigintQueue = new Set();
  self.on("SIGINT", function onSigInt() {
    if (prioritizedSigintQueue.size > 0) {
      for (const task of prioritizedSigintQueue) {
        task();
      }
      return;
    }

    const empty = self.line.length === 0;
    self.clearLine();
    _turnOffEditorMode(self);

    const cmd = self[kBufferedCommandSymbol];
    if (!(cmd && cmd.length > 0) && empty) {
      if (sawSIGINT) {
        self.close();
        sawSIGINT = false;
        return;
      }
      self.output.write(
        "(To exit, press Ctrl+C again or Ctrl+D or type .exit)\n",
      );
      sawSIGINT = true;
    } else {
      sawSIGINT = false;
    }

    self.clearBufferedCommand();
    self.lines.level = [];
    self.displayPrompt();
  });

  self.on("line", function onLine(cmd) {
    {
      /* debug */
    }
    cmd = cmd || "";
    sawSIGINT = false;

    if (self.editorMode) {
      self[kBufferedCommandSymbol] += cmd + "\n";

      // code alignment
      const matches =
        self._sawKeyPress && !self[kLoadingSymbol]
          ? RegExp.prototype.exec.call(/^\s+/, cmd)
          : null;
      if (matches) {
        const prefix = matches[0];
        self.write(prefix);
        self.line = prefix;
        self.cursor = prefix.length;
      }
      ReflectApply(_memory, self, [cmd]);
      return;
    }

    // Check REPL keywords and empty lines against a trimmed line input.
    const trimmedCmd = String.prototype.trim.call(cmd);

    // Check to see if a REPL keyword was used. If it returns true,
    // display next prompt and return.
    if (trimmedCmd) {
      if (
        String.prototype.charAt.call(trimmedCmd, 0) === "." &&
        String.prototype.charAt.call(trimmedCmd, 1) !== "." &&
        Number.isNaN(Number.parseFloat(trimmedCmd))
      ) {
        const matches = RegExp.prototype.exec.call(
          /^\.([^\s]+)\s*(.*)$/,
          trimmedCmd,
        );
        const keyword = matches && matches[1];
        const rest = matches && matches[2];
        if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) {
          return;
        }
        if (!self[kBufferedCommandSymbol]) {
          self.output.write("Invalid REPL keyword\n");
          finish(null);
          return;
        }
      }
    }

    const evalCmd = self[kBufferedCommandSymbol] + cmd + "\n";

    {
      /* debug */
    }
    self.eval(evalCmd, self.context, getREPLResourceName(), finish);

    function finish(e, ret) {
      {
        /* debug */
      }
      ReflectApply(_memory, self, [cmd]);

      if (
        e &&
        !self[kBufferedCommandSymbol] &&
        String.prototype.startsWith.call(
          String.prototype.trim.call(cmd),
          "npm ",
        )
      ) {
        self.output.write(
          "npm should be run outside of the " +
            "Node.js REPL, in your normal shell.\n" +
            "(Press Ctrl+D to exit.)\n",
        );
        self.displayPrompt();
        return;
      }

      // If error was SyntaxError and not JSON.parse error
      if (e) {
        if (e instanceof Recoverable && !sawCtrlD) {
          // Start buffering data like that:
          // {
          // ...  x: 1
          // ... }
          self[kBufferedCommandSymbol] += cmd + "\n";
          self.displayPrompt();
          return;
        }
        self._domain.emit("error", e.err || e);
      }

      // Clear buffer if no SyntaxErrors
      self.clearBufferedCommand();
      sawCtrlD = false;

      // If we got any output - print it (if no error)
      if (
        !e &&
        // When an invalid REPL command is used, error message is printed
        // immediately. We don't have to print anything else. So, only when
        // the second argument to this function is there, print it.
        arguments.length === 2 &&
        (!self.ignoreUndefined || ret !== undefined)
      ) {
        if (!self.underscoreAssigned) {
          self.last = ret;
        }
        self.output.write(self.writer(ret) + "\n");
      }

      // Display prompt again (unless we already did by emitting the 'error'
      // event on the domain instance).
      if (!e) {
        self.displayPrompt();
      }
    }
  });

  self.on("SIGCONT", function onSigCont() {
    if (self.editorMode) {
      self.output.write(`${self._initialPrompt}.editor\n`);
      self.output.write(
        "// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n",
      );
      self.output.write(`${self[kBufferedCommandSymbol]}\n`);
      self.prompt(true);
    } else {
      self.displayPrompt(true);
    }
  });

  const { reverseSearch } = setupReverseSearch(this);

  const { clearPreview, showPreview } = setupPreview(
    this,
    kContextId,
    kBufferedCommandSymbol,
    preview,
  );

  // Wrap readline tty to enable editor mode and pausing.
  const ttyWrite = Function.prototype.bind.call(self._ttyWrite, self);
  self._ttyWrite = (d, key) => {
    key = key || {};
    if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === "c")) {
      Array.prototype.push.call(pausedBuffer, [
        "key",
        [d, key],
        self.isCompletionEnabled,
      ]);
      return;
    }
    if (!self.editorMode || !self.terminal) {
      // Before exiting, make sure to clear the line.
      if (
        key.ctrl &&
        key.name === "d" &&
        self.cursor === 0 &&
        self.line.length === 0
      ) {
        self.clearLine();
      }
      clearPreview(key);
      if (!reverseSearch(d, key)) {
        ttyWrite(d, key);
        const showCompletionPreview = key.name !== "escape";
        showPreview(showCompletionPreview);
      }
      return;
    }

    // Editor mode
    if (key.ctrl && !key.shift) {
      switch (key.name) {
        // TODO(BridgeAR): There should not be a special mode necessary for full
        // multiline support.
        case "d": // End editor mode
          _turnOffEditorMode(self);
          sawCtrlD = true;
          ttyWrite(d, { name: "return" });
          break;
        case "n": // Override next history item
        case "p": // Override previous history item
          break;
        default:
          ttyWrite(d, key);
      }
    } else {
      switch (key.name) {
        case "up": // Override previous history item
        case "down": // Override next history item
          break;
        case "tab":
          // Prevent double tab behavior
          self._previousKey = null;
          ttyWrite(d, key);
          break;
        default:
          ttyWrite(d, key);
      }
    }
  };

  self.displayPrompt();
}
Object.setPrototypeOf(REPLServer.prototype, Interface.prototype);
Object.setPrototypeOf(REPLServer, Interface);

// Prompt is a string to print on each line for the prompt,
// source is a stream to use for I/O, defaulting to stdin/stdout.
function start(prompt, source, eval_, useGlobal, ignoreUndefined, replMode) {
  return new REPLServer(
    prompt,
    source,
    eval_,
    useGlobal,
    ignoreUndefined,
    replMode,
  );
}

REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) {
  history(this, historyFile, cb);
};

REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() {
  this[kBufferedCommandSymbol] = "";
};

REPLServer.prototype.close = function close() {
  if (this.terminal && this._flushing && !this._closingOnFlush) {
    this._closingOnFlush = true;
    this.once("flushHistory", () =>
      ReflectApply(Interface.prototype.close, this, []),
    );

    return;
  }
  process.nextTick(() => ReflectApply(Interface.prototype.close, this, []));
};

REPLServer.prototype.createContext = function () {
  let context;
  if (this.useGlobal) {
    context = globalThis;
  } else {
    sendInspectorCommand(
      (session) => {
        session.post("Runtime.enable");
        session.once("Runtime.executionContextCreated", ({ params }) => {
          this[kContextId] = params.context.id;
        });
        context = vm.createContext();
        session.post("Runtime.disable");
      },
      () => {
        context = vm.createContext();
      },
    );
    Array.prototype.forEach.call(
      Object.getOwnPropertyNames(globalThis),
      (name) => {
        // Only set properties that do not already exist as a global builtin.
        if (!globalBuiltins.has(name)) {
          Object.defineProperty(context, name, {
            __proto__: null,
            ...Object.getOwnPropertyDescriptor(globalThis, name),
          });
        }
      },
    );
    context.global = context;
    const _console = new Console(this.output);
    Object.defineProperty(context, "console", {
      __proto__: null,
      configurable: true,
      writable: true,
      value: _console,
    });
  }

  const replModule = new CJSModule("<repl>");
  replModule.paths = CJSModule._resolveLookupPaths("<repl>", parentModule);

  Object.defineProperty(context, "module", {
    __proto__: null,
    configurable: true,
    writable: true,
    value: replModule,
  });
  Object.defineProperty(context, "require", {
    __proto__: null,
    configurable: true,
    writable: true,
    value: makeRequireFunction(replModule),
  });

  addBuiltinLibsToObject(context, "<REPL>");

  return context;
};

REPLServer.prototype.resetContext = function () {
  this.context = this.createContext();
  this.underscoreAssigned = false;
  this.underscoreErrAssigned = false;
  // TODO(BridgeAR): Deprecate the lines.
  this.lines = [];
  this.lines.level = [];

  Object.defineProperty(this.context, "_", {
    __proto__: null,
    configurable: true,
    get: () => this.last,
    set: (value) => {
      this.last = value;
      if (!this.underscoreAssigned) {
        this.underscoreAssigned = true;
        this.output.write("Expression assignment to _ now disabled.\n");
      }
    },
  });

  Object.defineProperty(this.context, "_error", {
    __proto__: null,
    configurable: true,
    get: () => this.lastError,
    set: (value) => {
      this.lastError = value;
      if (!this.underscoreErrAssigned) {
        this.underscoreErrAssigned = true;
        this.output.write("Expression assignment to _error now disabled.\n");
      }
    },
  });

  // Allow REPL extensions to extend the new context
  this.emit("reset", this.context);
};

REPLServer.prototype.displayPrompt = function (preserveCursor) {
  let prompt = this._initialPrompt;
  if (this[kBufferedCommandSymbol].length) {
    prompt = "...";
    const len = this.lines.level.length ? this.lines.level.length - 1 : 0;
    const levelInd = String.prototype.repeat.call("..", len);
    prompt += levelInd + " ";
  }

  // Do not overwrite `_initialPrompt` here
  ReflectApply(Interface.prototype.setPrompt, this, [prompt]);
  this.prompt(preserveCursor);
};

// When invoked as an API method, overwrite _initialPrompt
REPLServer.prototype.setPrompt = function setPrompt(prompt) {
  this._initialPrompt = prompt;
  ReflectApply(Interface.prototype.setPrompt, this, [prompt]);
};

const importRE =
  /\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
const requireRE =
  /\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
const simpleExpressionRE =
  /(?:[\w$'"`[{(](?:\w|\$|['"`\]})])*\??\.)*[a-zA-Z_$](?:\w|\$)*\??\.?$/;
const versionedFileNamesRe = /-\d+\.\d+/;

function isIdentifier(str) {
  if (str === "") {
    return false;
  }
  const first = String.prototype.codePointAt.call(str, 0);
  if (!isIdentifierStart(first)) {
    return false;
  }
  const firstLen = first > 0xffff ? 2 : 1;
  for (let i = firstLen; i < str.length; i += 1) {
    const cp = String.prototype.codePointAt.call(str, i);
    if (!isIdentifierChar(cp)) {
      return false;
    }
    if (cp > 0xffff) {
      i += 1;
    }
  }
  return true;
}

function isNotLegacyObjectPrototypeMethod(str) {
  return (
    isIdentifier(str) &&
    str !== "__defineGetter__" &&
    str !== "__defineSetter__" &&
    str !== "__lookupGetter__" &&
    str !== "__lookupSetter__"
  );
}

function filteredOwnPropertyNames(obj) {
  if (!obj) return [];
  // `Object.prototype` is the only non-contrived object that fulfills
  // `Object.getPrototypeOf(X) === null &&
  //  Object.getPrototypeOf(Object.getPrototypeOf(X.constructor)) === X`.
  let isObjectPrototype = false;
  if (Object.getPrototypeOf(obj) === null) {
    const ctorDescriptor = Object.getOwnPropertyDescriptor(obj, "constructor");
    if (ctorDescriptor && ctorDescriptor.value) {
      const ctorProto = Object.getPrototypeOf(ctorDescriptor.value);
      isObjectPrototype = ctorProto && Object.getPrototypeOf(ctorProto) === obj;
    }
  }
  const filter = ALL_PROPERTIES | SKIP_SYMBOLS;
  return Array.prototype.filter.call(
    getOwnNonIndexProperties(obj, filter),
    isObjectPrototype ? isNotLegacyObjectPrototypeMethod : isIdentifier,
  );
}

function getGlobalLexicalScopeNames(contextId) {
  return sendInspectorCommand(
    (session) => {
      let names = [];
      session.post(
        "Runtime.globalLexicalScopeNames",
        {
          executionContextId: contextId,
        },
        (error, result) => {
          if (!error) names = result.names;
        },
      );
      return names;
    },
    () => [],
  );
}

REPLServer.prototype.complete = function () {
  ReflectApply(this.completer, this, arguments);
};

function gracefulReaddir(...args) {
  try {
    return ReflectApply(fs.readdirSync, null, args);
  } catch {
    // Continue regardless of error.
  }
}

function completeFSFunctions(match) {
  let baseName = "";
  let filePath = match[1];
  let fileList = gracefulReaddir(filePath, { withFileTypes: true });

  if (!fileList) {
    baseName = path.basename(filePath);
    filePath = path.dirname(filePath);
    fileList = gracefulReaddir(filePath, { withFileTypes: true }) || [];
  }

  const completions = Array.prototype.map.call(
    Array.prototype.filter.call(fileList, (dirent) =>
      String.prototype.startsWith.call(dirent.name, baseName),
    ),
    (d) => d.name,
  );

  return [[completions], baseName];
}

// Provide a list of completions for the given leading text. This is
// given to the readline interface for handling tab completion.
//
// Example:
//  complete('let foo = util.')
//    -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
//        'util.' ]
//
// Warning: This eval's code like "foo.bar.baz", so it will run property
// getter code.
function complete(line, callback) {
  // List of completion lists, one for each inheritance "level"
  let completionGroups = [];
  let completeOn, group;

  // Ignore right whitespace. It could change the outcome.
  line = String.prototype.trimStart.call(line);

  let filter = "";

  let match;
  // REPL commands (e.g. ".break").
  if ((match = RegExp.prototype.exec.call(/^\s*\.(\w*)$/, line)) !== null) {
    Array.prototype.push.call(completionGroups, Object.keys(this.commands));
    completeOn = match[1];
    if (completeOn.length) {
      filter = completeOn;
    }
  } else if ((match = RegExp.prototype.exec.call(requireRE, line)) !== null) {
    // require('...<Tab>')
    completeOn = match[1];
    filter = completeOn;
    if (this.allowBlockingCompletions) {
      const subdir = match[2] || "";
      const extensions = Object.keys(this.context.require.extensions);
      const indexes = Array.prototype.map.call(
        extensions,
        (extension) => `index${extension}`,
      );
      Array.prototype.push.call(indexes, "package.json", "index");

      group = [];
      let paths = [];

      if (completeOn === ".") {
        group = ["./", "../"];
      } else if (completeOn === "..") {
        group = ["../"];
      } else if (RegExp.prototype.exec.call(/^\.\.?\//, completeOn) !== null) {
        paths = [process.cwd()];
      } else {
        paths = [];
        Array.prototype.push.apply(paths, module.paths);
        Array.prototype.push.apply(paths, CJSModule.globalPaths);
      }

      Array.prototype.forEach.call(paths, (dir) => {
        dir = path.resolve(dir, subdir);
        const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
        Array.prototype.forEach.call(dirents, (dirent) => {
          if (
            RegExp.prototype.exec.call(versionedFileNamesRe, dirent.name) !==
              null ||
            dirent.name === ".npm"
          ) {
            // Exclude versioned names that 'npm' installs.
            return;
          }
          const extension = path.extname(dirent.name);
          const base = String.prototype.slice.call(
            dirent.name,
            0,
            -extension.length,
          );
          if (!dirent.isDirectory()) {
            if (
              String.prototype.includes.call(extensions, extension) &&
              (!subdir || base !== "index")
            ) {
              Array.prototype.push.call(group, `${subdir}${base}`);
            }
            return;
          }
          Array.prototype.push.call(group, `${subdir}${dirent.name}/`);
          const absolute = path.resolve(dir, dirent.name);
          if (
            Array.prototype.some.call(
              gracefulReaddir(absolute) || [],
              (subfile) => Array.prototype.includes.call(indexes, subfile),
            )
          ) {
            Array.prototype.push.call(group, `${subdir}${dirent.name}`);
          }
        });
      });
      if (group.length) {
        Array.prototype.push.call(completionGroups, group);
      }
    }

    Array.prototype.push.call(
      completionGroups,
      _builtinLibs,
      nodeSchemeBuiltinLibs,
    );
  } else if ((match = RegExp.prototype.exec.call(importRE, line)) !== null) {
    // import('...<Tab>')
    completeOn = match[1];
    filter = completeOn;
    if (this.allowBlockingCompletions) {
      const subdir = match[2] || "";
      // File extensions that can be imported:
      const extensions = Object.keys(extensionFormatMap);

      // Only used when loading bare module specifiers from `node_modules`:
      const indexes = Array.prototype.map.call(
        extensions,
        (ext) => `index${ext}`,
      );
      Array.prototype.push.call(indexes, "package.json");

      group = [];
      let paths = [];
      if (completeOn === ".") {
        group = ["./", "../"];
      } else if (completeOn === "..") {
        group = ["../"];
      } else if (RegExp.prototype.exec.call(/^\.\.?\//, completeOn) !== null) {
        paths = [process.cwd()];
      } else {
        paths = Array.prototype.slice.call(module.paths);
      }

      Array.prototype.forEach.call(paths, (dir) => {
        dir = path.resolve(dir, subdir);
        const isInNodeModules = path.basename(dir) === "node_modules";
        const dirents = gracefulReaddir(dir, { withFileTypes: true }) || [];
        Array.prototype.forEach.call(dirents, (dirent) => {
          const { name } = dirent;
          if (
            RegExp.prototype.exec.call(versionedFileNamesRe, name) !== null ||
            name === ".npm"
          ) {
            // Exclude versioned names that 'npm' installs.
            return;
          }

          if (!dirent.isDirectory()) {
            const extension = path.extname(name);
            if (String.prototype.includes.call(extensions, extension)) {
              Array.prototype.push.call(group, `${subdir}${name}`);
            }
            return;
          }

          Array.prototype.push.call(group, `${subdir}${name}/`);
          if (!subdir && isInNodeModules) {
            const absolute = path.resolve(dir, name);
            const subfiles = gracefulReaddir(absolute) || [];
            if (
              Array.prototype.some.call(subfiles, (subfile) => {
                return Array.prototype.includes.call(indexes, subfile);
              })
            ) {
              Array.prototype.push.call(group, `${subdir}${name}`);
            }
          }
        });
      });

      if (group.length) {
        Array.prototype.push.call(completionGroups, group);
      }
    }

    Array.prototype.push.call(
      completionGroups,
      _builtinLibs,
      nodeSchemeBuiltinLibs,
    );
  } else if (
    (match = RegExp.prototype.exec.call(fsAutoCompleteRE, line)) !== null &&
    this.allowBlockingCompletions
  ) {
    ({ 0: completionGroups, 1: completeOn } = completeFSFunctions(match));
    // Handle variable member lookup.
    // We support simple chained expressions like the following (no function
    // calls, etc.). That is for simplicity and also because we *eval* that
    // leading expression so for safety (see WARNING above) don't want to
    // eval function calls.
    //
    //   foo.bar<|>     # completions for 'foo' with filter 'bar'
    //   spam.eggs.<|>  # completions for 'spam.eggs' with filter ''
    //   foo<|>         # all scope vars with filter 'foo'
    //   foo.<|>        # completions for 'foo' with filter ''
  } else if (
    line.length === 0 ||
    RegExp.prototype.exec.call(/\w|\.|\$/, line[line.length - 1]) !== null
  ) {
    const { 0: match } = RegExp.prototype.exec.call(
      simpleExpressionRE,
      line,
    ) || [""];
    if (line.length !== 0 && !match) {
      completionGroupsLoaded();
      return;
    }
    let expr = "";
    completeOn = match;
    if (String.prototype.endsWith.call(line, ".")) {
      expr = String.prototype.slice.call(match, 0, -1);
    } else if (line.length !== 0) {
      const bits = String.prototype.split.call(match, ".");
      filter = Array.prototype.pop.call(bits);
      expr = Array.prototype.join.call(bits, ".");
    }

    // Resolve expr and get its completions.
    if (!expr) {
      // Get global vars synchronously
      Array.prototype.push.call(
        completionGroups,
        getGlobalLexicalScopeNames(this[kContextId]),
      );
      let contextProto = this.context;
      while ((contextProto = Object.getPrototypeOf(contextProto)) !== null) {
        Array.prototype.push.call(
          completionGroups,
          filteredOwnPropertyNames(contextProto),
        );
      }
      const contextOwnNames = filteredOwnPropertyNames(this.context);
      if (!this.useGlobal) {
        // When the context is not `global`, builtins are not own
        // properties of it.
        // `globalBuiltins` is a `SafeSet`, not an Array-like.
        Array.prototype.push.call(contextOwnNames, ...globalBuiltins);
      }
      Array.prototype.push.call(completionGroups, contextOwnNames);
      if (filter !== "") addCommonWords(completionGroups);
      completionGroupsLoaded();
      return;
    }

    let chaining = ".";
    if (String.prototype.endsWith.call(expr, "?")) {
      expr = String.prototype.slice.call(expr, 0, -1);
      chaining = "?.";
    }

    const memberGroups = [];
    const evalExpr = `try { ${expr} } catch {}`;
    this.eval(evalExpr, this.context, getREPLResourceName(), (e, obj) => {
      try {
        let p;
        if (
          (typeof obj === "object" && obj !== null) ||
          typeof obj === "function"
        ) {
          memberGroups.push(filteredOwnPropertyNames(obj));
          p = Object.getPrototypeOf(obj);
        } else {
          p = obj.constructor ? obj.constructor.prototype : null;
        }
        // Circular refs possible? Let's guard against that.
        let sentinel = 5;
        while (p !== null && sentinel-- !== 0) {
          memberGroups.push(filteredOwnPropertyNames(p));
          p = Object.getPrototypeOf(p);
        }
      } catch {
        // Maybe a Proxy object without `getOwnPropertyNames` trap.
        // We simply ignore it here, as we don't want to break the
        // autocompletion. Fixes the bug
        // https://github.com/nodejs/node/issues/2119
      }

      if (memberGroups.length) {
        expr += chaining;
        Array.prototype.forEach.call(memberGroups, (group) => {
          Array.prototype.push.call(
            completionGroups,
            Array.prototype.map.call(group, (member) => `${expr}${member}`),
          );
        });
        if (filter) {
          filter = `${expr}${filter}`;
        }
      }

      completionGroupsLoaded();
    });
    return;
  }

  return completionGroupsLoaded();

  // Will be called when all completionGroups are in place
  // Useful for async autocompletion
  function completionGroupsLoaded() {
    // Filter, sort (within each group), uniq and merge the completion groups.
    if (completionGroups.length && filter) {
      const newCompletionGroups = [];
      const lowerCaseFilter = String.prototype.toLocaleLowerCase.call(filter);
      Array.prototype.forEach.call(completionGroups, (group) => {
        const filteredGroup = Array.prototype.filter.call(group, (str) => {
          // Filter is always case-insensitive following chromium autocomplete
          // behavior.
          return String.prototype.startsWith.call(
            String.prototype.toLocaleLowerCase.call(str),
            lowerCaseFilter,
          );
        });
        if (filteredGroup.length) {
          Array.prototype.push.call(newCompletionGroups, filteredGroup);
        }
      });
      completionGroups = newCompletionGroups;
    }

    const completions = [];
    // Unique completions across all groups.
    const uniqueSet = new Set();
    uniqueSet.add("");
    // Completion group 0 is the "closest" (least far up the inheritance
    // chain) so we put its completions last: to be closest in the REPL.
    Array.prototype.forEach.call(completionGroups, (group) => {
      Array.prototype.sort.call(group, (a, b) => (b > a ? 1 : -1));
      const setSize = uniqueSet.size;
      Array.prototype.forEach.call(group, (entry) => {
        if (!uniqueSet.has(entry)) {
          Array.prototype.unshift.call(completions, entry);
          uniqueSet.add(entry);
        }
      });
      // Add a separator between groups.
      if (uniqueSet.size !== setSize) {
        Array.prototype.unshift.call(completions, "");
      }
    });

    // Remove obsolete group entry, if present.
    if (completions[0] === "") {
      Array.prototype.shift.call(completions);
    }

    callback(null, [completions, completeOn]);
  }
}

REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
  if (err) return callback(err);

  const { 0: completions, 1: completeOn = "" } = results;
  let result = Array.prototype.filter.call(completions, Boolean);

  if (completeOn && result.length !== 0) {
    result = [commonPrefix(result)];
  }

  callback(null, [result, completeOn]);
};

REPLServer.prototype.defineCommand = function (keyword, cmd) {
  if (typeof cmd === "function") {
    cmd = { action: cmd };
  } else {
    validateFunction(cmd.action, "cmd.action");
  }
  this.commands[keyword] = cmd;
};

// TODO(BridgeAR): This should be replaced with acorn to build an AST. The
// language became more complex and using a simple approach like this is not
// sufficient anymore.
function _memory(cmd) {
  const self = this;
  self.lines = self.lines || [];
  self.lines.level = self.lines.level || [];

  // Save the line so I can do magic later
  if (cmd) {
    const len = self.lines.level.length ? self.lines.level.length - 1 : 0;
    Array.prototype.push.call(
      self.lines,
      String.prototype.repeat.call("  ", len) + cmd,
    );
  } else {
    // I don't want to not change the format too much...
    Array.prototype.push.call(self.lines, "");
  }

  if (!cmd) {
    self.lines.level = [];
    return;
  }

  // I need to know "depth."
  // Because I can not tell the difference between a } that
  // closes an object literal and a } that closes a function
  const countMatches = (regex, str) => {
    let count = 0;
    while (RegExp.prototype.exec.call(regex, str) !== null) count++;
    return count;
  };

  // Going down is { and (   e.g. function() {
  // going up is } and )
  const dw = countMatches(/[{(]/g, cmd);
  const up = countMatches(/[})]/g, cmd);
  let depth = dw.length - up.length;

  if (depth) {
    (function workIt() {
      if (depth > 0) {
        // Going... down.
        // Push the line#, depth count, and if the line is a function.
        // Since JS only has functional scope I only need to remove
        // "function() {" lines, clearly this will not work for
        // "function()
        // {" but nothing should break, only tab completion for local
        // scope will not work for this function.
        Array.prototype.push.call(self.lines.level, {
          line: self.lines.length - 1,
          depth: depth,
        });
      } else if (depth < 0) {
        // Going... up.
        const curr = Array.prototype.pop.call(self.lines.level);
        if (curr) {
          const tmp = curr.depth + depth;
          if (tmp < 0) {
            // More to go, recurse
            depth += curr.depth;
            workIt();
          } else if (tmp > 0) {
            // Remove and push back
            curr.depth += depth;
            Array.prototype.push.call(self.lines.level, curr);
          }
        }
      }
    })();
  }
}

function addCommonWords(completionGroups) {
  // Only words which do not yet exist as global property should be added to
  // this list.
  Array.prototype.push.call(completionGroups, [
    "async",
    "await",
    "break",
    "case",
    "catch",
    "const",
    "continue",
    "debugger",
    "default",
    "delete",
    "do",
    "else",
    "export",
    "false",
    "finally",
    "for",
    "function",
    "if",
    "import",
    "in",
    "instanceof",
    "let",
    "new",
    "null",
    "return",
    "switch",
    "this",
    "throw",
    "true",
    "try",
    "typeof",
    "var",
    "void",
    "while",
    "with",
    "yield",
  ]);
}

function _turnOnEditorMode(repl) {
  repl.editorMode = true;
  ReflectApply(Interface.prototype.setPrompt, repl, [""]);
}

function _turnOffEditorMode(repl) {
  repl.editorMode = false;
  repl.setPrompt(repl._initialPrompt);
}

function defineDefaultCommands(repl) {
  repl.defineCommand("break", {
    help: "Sometimes you get stuck, this gets you out",
    action: function () {
      this.clearBufferedCommand();
      this.displayPrompt();
    },
  });

  let clearMessage;
  if (repl.useGlobal) {
    clearMessage = "Alias for .break";
  } else {
    clearMessage = "Break, and also clear the local context";
  }
  repl.defineCommand("clear", {
    help: clearMessage,
    action: function () {
      this.clearBufferedCommand();
      if (!this.useGlobal) {
        this.output.write("Clearing context...\n");
        this.resetContext();
      }
      this.displayPrompt();
    },
  });

  repl.defineCommand("exit", {
    help: "Exit the REPL",
    action: function () {
      this.close();
    },
  });

  repl.defineCommand("help", {
    help: "Print this help message",
    action: function () {
      const names = Array.prototype.sort.call(Object.keys(this.commands));
      const longestNameLength = MathMaxApply(
        Array.prototype.map.call(names, (name) => name.length),
      );
      Array.prototype.forEach.call(names, (name) => {
        const cmd = this.commands[name];
        const spaces = String.prototype.repeat.call(
          " ",
          longestNameLength - name.length + 3,
        );
        const line = `.${name}${cmd.help ? spaces + cmd.help : ""}\n`;
        this.output.write(line);
      });
      this.output.write(
        "\nPress Ctrl+C to abort current expression, " +
          "Ctrl+D to exit the REPL\n",
      );
      this.displayPrompt();
    },
  });

  repl.defineCommand("save", {
    help: "Save all evaluated commands in this REPL session to a file",
    action: function (file) {
      try {
        if (file === "") {
          throw new ERR_MISSING_ARGS("file");
        }
        fs.writeFileSync(file, Array.prototype.join.call(this.lines, "\n"));
        this.output.write(`Session saved to: ${file}\n`);
      } catch (error) {
        if (error instanceof ERR_MISSING_ARGS) {
          this.output.write(`${error.message}\n`);
        } else {
          this.output.write(`Failed to save: ${file}\n`);
        }
      }
      this.displayPrompt();
    },
  });

  repl.defineCommand("load", {
    help: "Load JS from a file into the REPL session",
    action: function (file) {
      try {
        if (file === "") {
          throw new ERR_MISSING_ARGS("file");
        }
        const stats = fs.statSync(file);
        if (stats && stats.isFile()) {
          _turnOnEditorMode(this);
          this[kLoadingSymbol] = true;
          const data = fs.readFileSync(file, "utf8");
          this.write(data);
          this[kLoadingSymbol] = false;
          _turnOffEditorMode(this);
          this.write("\n");
        } else {
          this.output.write(`Failed to load: ${file} is not a valid file\n`);
        }
      } catch (error) {
        if (error instanceof ERR_MISSING_ARGS) {
          this.output.write(`${error.message}\n`);
        } else {
          this.output.write(`Failed to load: ${file}\n`);
        }
      }
      this.displayPrompt();
    },
  });
  if (repl.terminal) {
    repl.defineCommand("editor", {
      help: "Enter editor mode",
      action() {
        _turnOnEditorMode(this);
        this.output.write(
          "// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)\n",
        );
      },
    });
  }
}

function Recoverable(err) {
  this.err = err;
}
Object.setPrototypeOf(Recoverable.prototype, SyntaxErrorPrototype);
Object.setPrototypeOf(Recoverable, SyntaxError);

export { start };
export { writer };
export { REPLServer };
export { REPL_MODE_SLOPPY };
export { REPL_MODE_STRICT };
export { Recoverable };

Object.defineProperty(module.exports, "builtinModules", {
  __proto__: null,
  get: () => _builtinLibs,
  set: (val) => (_builtinLibs = val),
  enumerable: true,
  configurable: true,
});

Object.defineProperty(module.exports, "_builtinLibs", {
  __proto__: null,
  get: pendingDeprecation
    ? deprecate(
        () => _builtinLibs,
        "repl._builtinLibs is deprecated. Check module.builtinModules instead",
        "DEP0142",
      )
    : () => _builtinLibs,
  set: pendingDeprecation
    ? deprecate(
        (val) => (_builtinLibs = val),
        "repl._builtinLibs is deprecated. Check module.builtinModules instead",
        "DEP0142",
      )
    : (val) => (_builtinLibs = val),
  enumerable: false,
  configurable: true,
});