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