2017-01-03 22:16:48 +01:00
|
|
|
// 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.
|
|
|
|
|
2010-12-02 03:07:20 +01:00
|
|
|
/* A repl library that you can include in your own code to get a runtime
|
|
|
|
* interface to your program.
|
|
|
|
*
|
2019-09-16 13:09:26 +02:00
|
|
|
* const repl = require("repl");
|
2010-12-02 03:07:20 +01:00
|
|
|
* // start repl on stdin
|
|
|
|
* repl.start("prompt> ");
|
|
|
|
*
|
|
|
|
* // listen for unix socket connections and start repl on them
|
2012-02-19 00:01:35 +01:00
|
|
|
* net.createServer(function(socket) {
|
2010-12-02 03:07:20 +01:00
|
|
|
* repl.start("node via Unix socket> ", socket);
|
|
|
|
* }).listen("/tmp/node-repl-sock");
|
|
|
|
*
|
|
|
|
* // listen for TCP socket connections and start repl on them
|
2012-02-19 00:01:35 +01:00
|
|
|
* net.createServer(function(socket) {
|
2010-12-02 03:07:20 +01:00
|
|
|
* repl.start("node via TCP socket> ", socket);
|
|
|
|
* }).listen(5001);
|
|
|
|
*
|
|
|
|
* // expose foo to repl context
|
|
|
|
* repl.start("node > ").context.foo = "stdin is fun";
|
|
|
|
*/
|
2009-09-24 00:56:24 +02:00
|
|
|
|
2014-11-22 16:59:48 +01:00
|
|
|
'use strict';
|
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
const {
|
2020-01-02 19:19:02 +01:00
|
|
|
Error,
|
2019-11-22 18:04:46 +01:00
|
|
|
MathMax,
|
2019-11-27 19:59:29 +01:00
|
|
|
NumberIsNaN,
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectAssign,
|
|
|
|
ObjectCreate,
|
|
|
|
ObjectDefineProperty,
|
|
|
|
ObjectGetOwnPropertyDescriptor,
|
|
|
|
ObjectGetOwnPropertyNames,
|
|
|
|
ObjectGetPrototypeOf,
|
|
|
|
ObjectKeys,
|
|
|
|
ObjectSetPrototypeOf,
|
2019-12-13 16:46:35 +01:00
|
|
|
Promise,
|
|
|
|
PromiseRace,
|
2020-01-06 03:48:14 +01:00
|
|
|
RegExp,
|
2020-01-02 14:27:54 +01:00
|
|
|
Set,
|
2019-11-30 16:55:29 +01:00
|
|
|
Symbol,
|
2020-01-02 14:48:55 +01:00
|
|
|
WeakSet,
|
2019-11-22 18:04:46 +01:00
|
|
|
} = primordials;
|
2019-03-31 13:30:12 +02:00
|
|
|
|
2018-03-06 19:30:18 +01:00
|
|
|
const {
|
|
|
|
makeRequireFunction,
|
|
|
|
addBuiltinLibsToObject
|
|
|
|
} = require('internal/modules/cjs/helpers');
|
2018-06-27 06:57:57 +02:00
|
|
|
const {
|
|
|
|
isIdentifierStart,
|
|
|
|
isIdentifierChar
|
2019-01-31 08:36:48 +01:00
|
|
|
} = require('internal/deps/acorn/acorn/dist/acorn');
|
2019-03-21 10:24:13 +01:00
|
|
|
const {
|
|
|
|
decorateErrorStack,
|
|
|
|
isError,
|
|
|
|
deprecate
|
|
|
|
} = require('internal/util');
|
|
|
|
const { inspect } = require('internal/util/inspect');
|
2015-01-21 17:36:59 +01:00
|
|
|
const vm = require('vm');
|
|
|
|
const path = require('path');
|
|
|
|
const fs = require('fs');
|
2017-10-07 16:50:42 +02:00
|
|
|
const { Interface } = require('readline');
|
2019-12-11 19:21:40 +01:00
|
|
|
const {
|
|
|
|
commonPrefix
|
|
|
|
} = require('internal/readline/utils');
|
2017-10-07 16:50:42 +02:00
|
|
|
const { Console } = require('console');
|
2019-05-19 05:48:46 +02:00
|
|
|
const CJSModule = require('internal/modules/cjs/loader').Module;
|
2020-05-07 17:09:51 +02:00
|
|
|
const builtinModules = [...CJSModule.builtinModules]
|
|
|
|
.filter((e) => !e.startsWith('_'));
|
2015-01-21 17:36:59 +01:00
|
|
|
const domain = require('domain');
|
2019-03-21 10:24:13 +01:00
|
|
|
const debug = require('internal/util/debuglog').debuglog('repl');
|
2018-02-27 14:55:32 +01:00
|
|
|
const {
|
2019-09-16 22:32:15 +02:00
|
|
|
codes: {
|
|
|
|
ERR_CANNOT_WATCH_SIGINT,
|
|
|
|
ERR_INVALID_ARG_TYPE,
|
|
|
|
ERR_INVALID_REPL_EVAL_CONFIG,
|
|
|
|
ERR_INVALID_REPL_INPUT,
|
|
|
|
ERR_SCRIPT_EXECUTION_INTERRUPTED,
|
|
|
|
},
|
|
|
|
overrideStackTrace,
|
|
|
|
} = require('internal/errors');
|
2017-10-29 18:19:24 +01:00
|
|
|
const { sendInspectorCommand } = require('internal/util/inspector');
|
2018-11-04 21:52:50 +01:00
|
|
|
const experimentalREPLAwait = require('internal/options').getOptionValue(
|
2018-10-16 06:13:18 +02:00
|
|
|
'--experimental-repl-await'
|
|
|
|
);
|
2019-01-19 15:34:00 +01:00
|
|
|
const {
|
2020-05-04 03:49:04 +02:00
|
|
|
REPL_MODE_SLOPPY,
|
|
|
|
REPL_MODE_STRICT,
|
2019-01-19 15:34:00 +01:00
|
|
|
isRecoverableError,
|
2019-12-05 14:41:49 +01:00
|
|
|
kStandaloneREPL,
|
|
|
|
setupPreview,
|
2019-12-16 10:22:48 +01:00
|
|
|
setupReverseSearch,
|
2019-01-19 15:34:00 +01:00
|
|
|
} = require('internal/repl/utils');
|
2018-08-20 03:26:27 +02:00
|
|
|
const {
|
|
|
|
getOwnNonIndexProperties,
|
|
|
|
propertyFilter: {
|
|
|
|
ALL_PROPERTIES,
|
|
|
|
SKIP_SYMBOLS
|
2019-04-18 04:58:58 +02:00
|
|
|
}
|
|
|
|
} = internalBinding('util');
|
|
|
|
const {
|
2018-09-03 17:28:38 +02:00
|
|
|
startSigintWatchdog,
|
|
|
|
stopSigintWatchdog
|
2019-04-18 04:58:58 +02:00
|
|
|
} = internalBinding('contextify');
|
|
|
|
|
2019-02-01 18:49:16 +01:00
|
|
|
const history = require('internal/repl/history');
|
2018-03-26 05:13:54 +02:00
|
|
|
|
|
|
|
// Lazy-loaded.
|
|
|
|
let processTopLevelAwait;
|
2010-05-10 02:02:02 +02:00
|
|
|
|
2019-12-10 15:37:32 +01:00
|
|
|
const globalBuiltins =
|
|
|
|
new Set(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)'));
|
|
|
|
|
2015-12-09 21:55:29 +01:00
|
|
|
const parentModule = module;
|
2018-05-17 16:25:36 +02:00
|
|
|
const domainSet = new WeakSet();
|
2015-10-14 06:39:16 +02:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
const kBufferedCommandSymbol = Symbol('bufferedCommand');
|
2017-10-29 18:19:24 +01:00
|
|
|
const kContextId = Symbol('contextId');
|
2016-06-22 19:07:59 +02:00
|
|
|
|
2018-05-17 16:25:36 +02:00
|
|
|
let addedNewListener = false;
|
|
|
|
|
2015-05-04 10:40:40 +02:00
|
|
|
try {
|
2017-12-30 03:20:51 +01:00
|
|
|
// Hack for require.resolve("./relative") to work properly.
|
2015-05-04 10:40:40 +02:00
|
|
|
module.filename = path.resolve('repl');
|
2018-11-04 16:07:28 +01:00
|
|
|
} catch {
|
2015-05-04 10:40:40 +02:00
|
|
|
// 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');
|
|
|
|
}
|
|
|
|
|
2017-12-30 03:20:51 +01:00
|
|
|
// Hack for repl require to work properly with node_modules folders
|
2018-03-06 19:30:18 +01:00
|
|
|
module.paths = CJSModule._nodeModulePaths(module.filename);
|
2015-05-04 10:40:40 +02:00
|
|
|
|
2018-10-21 11:32:03 +02:00
|
|
|
// 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`.
|
2019-03-21 10:24:13 +01:00
|
|
|
const writer = exports.writer = (obj) => inspect(obj, writer.options);
|
|
|
|
writer.options = { ...inspect.defaultOptions, showProxy: true };
|
2010-04-12 01:13:32 +02:00
|
|
|
|
2020-05-07 17:09:51 +02:00
|
|
|
exports._builtinLibs = builtinModules;
|
2011-12-25 05:39:57 +01:00
|
|
|
|
2015-04-23 09:35:53 +02:00
|
|
|
function REPLServer(prompt,
|
|
|
|
stream,
|
|
|
|
eval_,
|
|
|
|
useGlobal,
|
|
|
|
ignoreUndefined,
|
|
|
|
replMode) {
|
2012-03-27 00:21:25 +02:00
|
|
|
if (!(this instanceof REPLServer)) {
|
2015-04-23 09:35:53 +02:00
|
|
|
return new REPLServer(prompt,
|
|
|
|
stream,
|
|
|
|
eval_,
|
|
|
|
useGlobal,
|
|
|
|
ignoreUndefined,
|
|
|
|
replMode);
|
2012-03-27 00:21:25 +02:00
|
|
|
}
|
|
|
|
|
2019-03-08 12:21:35 +01:00
|
|
|
let options;
|
2015-01-29 02:05:53 +01:00
|
|
|
if (prompt !== null && typeof prompt === 'object') {
|
2019-03-08 12:21:35 +01:00
|
|
|
// An options object was given.
|
|
|
|
options = { ...prompt };
|
2012-03-27 00:21:25 +02:00
|
|
|
stream = options.stream || options.socket;
|
2012-07-04 23:14:27 +02:00
|
|
|
eval_ = options.eval;
|
2012-03-27 00:21:25 +02:00
|
|
|
useGlobal = options.useGlobal;
|
|
|
|
ignoreUndefined = options.ignoreUndefined;
|
|
|
|
prompt = options.prompt;
|
2015-04-23 09:35:53 +02:00
|
|
|
replMode = options.replMode;
|
2012-03-27 00:21:25 +02:00
|
|
|
} else {
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
|
2019-03-08 12:21:35 +01:00
|
|
|
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.
|
|
|
|
if (options.output.hasColors) {
|
|
|
|
options.useColors = options.output.hasColors();
|
|
|
|
} else if (process.env.NODE_DISABLE_COLORS === undefined) {
|
|
|
|
options.useColors = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-08 11:54:51 +01:00
|
|
|
// TODO(devsnek): Add a test case for custom eval functions.
|
2019-12-05 14:41:49 +01:00
|
|
|
const preview = options.terminal &&
|
2020-01-08 11:54:51 +01:00
|
|
|
(options.preview !== undefined ? !!options.preview : !eval_);
|
2019-12-05 14:41:49 +01:00
|
|
|
|
2019-03-08 12:21:35 +01:00
|
|
|
this.inputStream = options.input;
|
|
|
|
this.outputStream = options.output;
|
|
|
|
this.useColors = !!options.useColors;
|
|
|
|
this._domain = options.domain || domain.create();
|
|
|
|
this.useGlobal = !!useGlobal;
|
|
|
|
this.ignoreUndefined = !!ignoreUndefined;
|
|
|
|
this.replMode = replMode || 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_) {
|
2016-05-08 03:30:23 +02:00
|
|
|
// Allowing this would not reflect user expectations.
|
2017-06-17 15:11:45 +02:00
|
|
|
// breakEvalOnSigint affects only the behavior of the default eval().
|
2018-02-27 14:55:32 +01:00
|
|
|
throw new ERR_INVALID_REPL_EVAL_CONFIG();
|
2016-05-08 03:30:23 +02:00
|
|
|
}
|
|
|
|
|
2019-12-15 17:43:46 +01:00
|
|
|
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.
|
|
|
|
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`.
|
2018-05-17 16:25:36 +02:00
|
|
|
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);
|
|
|
|
|
2019-03-08 12:21:35 +01:00
|
|
|
let rli = this;
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(this, 'rli', {
|
2019-03-21 10:24:13 +01:00
|
|
|
get: deprecate(() => rli,
|
|
|
|
'REPLServer.rli is deprecated', 'DEP0124'),
|
|
|
|
set: deprecate((val) => rli = val,
|
|
|
|
'REPLServer.rli is deprecated', 'DEP0124'),
|
2019-01-21 01:08:22 +01:00
|
|
|
enumerable: true,
|
|
|
|
configurable: true
|
|
|
|
});
|
2014-02-15 15:21:26 +01:00
|
|
|
|
2015-07-08 23:53:48 +02:00
|
|
|
const savedRegExMatches = ['', '', '', '', '', '', '', '', '', ''];
|
2018-02-11 19:17:03 +01:00
|
|
|
const sep = '\u0000\u0000\u0000';
|
|
|
|
const regExMatcher = new RegExp(`^${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
|
|
|
|
`${sep}(.*)${sep}(.*)${sep}(.*)${sep}(.*)` +
|
|
|
|
`${sep}(.*)$`);
|
|
|
|
|
|
|
|
eval_ = eval_ || defaultEval;
|
|
|
|
|
2019-03-08 12:21:35 +01:00
|
|
|
const self = this;
|
|
|
|
|
2018-02-11 19:17:03 +01:00
|
|
|
// Pause taking in new input, and store the keys in a buffer.
|
|
|
|
const pausedBuffer = [];
|
|
|
|
let paused = false;
|
|
|
|
function pause() {
|
|
|
|
paused = true;
|
|
|
|
}
|
2019-11-28 07:56:21 +01:00
|
|
|
|
2018-02-11 19:17:03 +01:00
|
|
|
function unpause() {
|
|
|
|
if (!paused) return;
|
|
|
|
paused = false;
|
|
|
|
let entry;
|
2020-01-11 13:36:21 +01:00
|
|
|
const tmpCompletionEnabled = self.isCompletionEnabled;
|
2018-02-11 19:17:03 +01:00
|
|
|
while (entry = pausedBuffer.shift()) {
|
2020-01-11 13:36:21 +01:00
|
|
|
const [type, payload, isCompletionEnabled] = entry;
|
2018-02-11 19:17:03 +01:00
|
|
|
switch (type) {
|
|
|
|
case 'key': {
|
|
|
|
const [d, key] = payload;
|
2020-01-11 13:36:21 +01:00
|
|
|
self.isCompletionEnabled = isCompletionEnabled;
|
2018-02-11 19:17:03 +01:00
|
|
|
self._ttyWrite(d, key);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'close':
|
|
|
|
self.emit('exit');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (paused) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-01-11 13:36:21 +01:00
|
|
|
self.isCompletionEnabled = tmpCompletionEnabled;
|
2018-02-11 19:17:03 +01:00
|
|
|
}
|
|
|
|
|
2015-11-14 01:40:08 +01:00
|
|
|
function defaultEval(code, context, file, cb) {
|
2019-10-11 23:57:13 +02:00
|
|
|
const asyncESM = require('internal/process/esm_loader');
|
2019-09-04 18:19:14 +02:00
|
|
|
|
2019-01-27 01:18:47 +01:00
|
|
|
let result, script, wrappedErr;
|
|
|
|
let err = null;
|
|
|
|
let wrappedCmd = false;
|
|
|
|
let awaitPromise = false;
|
|
|
|
const input = code;
|
2015-11-14 01:40:08 +01:00
|
|
|
|
2020-02-25 03:50:13 +01:00
|
|
|
// 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 (/^\s*{/.test(code) && !/;\s*$/.test(code)) {
|
2015-11-14 01:40:08 +01:00
|
|
|
code = `(${code.trim()})\n`;
|
|
|
|
wrappedCmd = true;
|
2016-11-22 23:16:25 +01:00
|
|
|
}
|
|
|
|
|
2018-03-26 05:13:54 +02:00
|
|
|
if (experimentalREPLAwait && code.includes('await')) {
|
|
|
|
if (processTopLevelAwait === undefined) {
|
|
|
|
({ processTopLevelAwait } = require('internal/repl/await'));
|
|
|
|
}
|
|
|
|
|
2017-09-23 09:10:47 +02:00
|
|
|
const potentialWrappedCode = processTopLevelAwait(code);
|
|
|
|
if (potentialWrappedCode !== null) {
|
|
|
|
code = potentialWrappedCode;
|
|
|
|
wrappedCmd = true;
|
|
|
|
awaitPromise = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-30 03:20:51 +01:00
|
|
|
// First, create the Script object to check the syntax
|
2016-08-17 16:47:13 +02:00
|
|
|
if (code === '\n')
|
2016-04-06 02:17:33 +02:00
|
|
|
return cb(null);
|
|
|
|
|
2019-11-23 04:06:43 +01:00
|
|
|
let parentURL;
|
2019-09-04 18:19:14 +02:00
|
|
|
try {
|
|
|
|
const { pathToFileURL } = require('url');
|
2019-11-23 04:06:43 +01:00
|
|
|
// Adding `/repl` prevents dynamic imports from loading relative
|
|
|
|
// to the parent of `process.cwd()`.
|
|
|
|
parentURL = pathToFileURL(path.join(process.cwd(), 'repl')).href;
|
2019-09-04 18:19:14 +02:00
|
|
|
} catch {
|
|
|
|
}
|
2015-04-23 09:35:53 +02:00
|
|
|
while (true) {
|
|
|
|
try {
|
2020-05-04 03:49:04 +02:00
|
|
|
if (self.replMode === exports.REPL_MODE_STRICT && !/^\s*$/.test(code)) {
|
2017-02-28 02:57:44 +01:00
|
|
|
// "void 0" keeps the repl from returning "use strict" as the result
|
|
|
|
// value for statements and declarations that don't return a value.
|
2016-08-17 16:47:13 +02:00
|
|
|
code = `'use strict'; void 0;\n${code}`;
|
2015-04-23 09:35:53 +02:00
|
|
|
}
|
2015-11-14 01:40:08 +01:00
|
|
|
script = vm.createScript(code, {
|
2015-04-23 09:35:53 +02:00
|
|
|
filename: file,
|
2019-09-04 18:19:14 +02:00
|
|
|
displayErrors: true,
|
2019-10-11 23:57:13 +02:00
|
|
|
importModuleDynamically: async (specifier) => {
|
2019-11-23 04:06:43 +01:00
|
|
|
return asyncESM.ESMLoader.import(specifier, parentURL);
|
2019-10-11 23:57:13 +02:00
|
|
|
}
|
2015-04-23 09:35:53 +02:00
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
debug('parse error %j', code, e);
|
2015-11-14 01:40:08 +01:00
|
|
|
if (wrappedCmd) {
|
2017-12-30 03:20:51 +01:00
|
|
|
// Unwrap and try again
|
2017-09-23 09:10:47 +02:00
|
|
|
wrappedCmd = false;
|
|
|
|
awaitPromise = false;
|
2015-11-14 01:40:08 +01:00
|
|
|
code = input;
|
2017-02-28 02:57:44 +01:00
|
|
|
wrappedErr = e;
|
2015-04-23 09:35:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
2017-12-30 03:20:51 +01:00
|
|
|
// Preserve original error for wrapped command
|
2016-03-07 06:01:33 +01:00
|
|
|
const error = wrappedErr || e;
|
2015-11-14 01:40:08 +01:00
|
|
|
if (isRecoverableError(error, code))
|
2016-03-07 06:01:33 +01:00
|
|
|
err = new Recoverable(error);
|
2015-04-23 09:35:53 +02:00
|
|
|
else
|
2016-03-07 06:01:33 +01:00
|
|
|
err = error;
|
2015-04-23 09:35:53 +02:00
|
|
|
}
|
|
|
|
break;
|
2011-09-07 09:39:49 +02:00
|
|
|
}
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
|
2015-07-08 23:53:48 +02:00
|
|
|
// This will set the values from `savedRegExMatches` to corresponding
|
|
|
|
// predefined RegExp properties `RegExp.$1`, `RegExp.$2` ... `RegExp.$9`
|
|
|
|
regExMatcher.test(savedRegExMatches.join(sep));
|
|
|
|
|
2017-09-23 09:10:47 +02:00
|
|
|
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`
|
2019-09-16 13:09:26 +02:00
|
|
|
for (let idx = 1; idx < savedRegExMatches.length; idx += 1) {
|
2017-09-23 09:10:47 +02:00
|
|
|
savedRegExMatches[idx] = RegExp[`$${idx}`];
|
|
|
|
}
|
|
|
|
|
|
|
|
cb(err, result);
|
|
|
|
}
|
|
|
|
|
2013-08-28 03:53:39 +02:00
|
|
|
if (!err) {
|
2016-05-08 03:30:23 +02:00
|
|
|
// Unset raw mode during evaluation so that Ctrl+C raises a signal.
|
|
|
|
let previouslyInRawMode;
|
2016-09-18 11:16:45 +02:00
|
|
|
if (self.breakEvalOnSigint) {
|
2016-05-08 03:30:23 +02:00
|
|
|
// Start the SIGINT watchdog before entering raw mode so that a very
|
2017-02-06 20:44:17 +01:00
|
|
|
// quick Ctrl+C doesn't lead to aborting the process completely.
|
2018-09-03 17:28:38 +02:00
|
|
|
if (!startSigintWatchdog())
|
2018-02-27 14:55:32 +01:00
|
|
|
throw new ERR_CANNOT_WATCH_SIGINT();
|
2016-05-08 03:30:23 +02:00
|
|
|
previouslyInRawMode = self._setRawMode(false);
|
|
|
|
}
|
|
|
|
|
2013-08-28 03:53:39 +02:00
|
|
|
try {
|
2016-05-08 03:30:23 +02:00
|
|
|
try {
|
|
|
|
const scriptOptions = {
|
2017-05-18 23:53:20 +02:00
|
|
|
displayErrors: false,
|
2016-05-08 03:30:23 +02:00
|
|
|
breakOnSigint: self.breakEvalOnSigint
|
|
|
|
};
|
|
|
|
|
|
|
|
if (self.useGlobal) {
|
|
|
|
result = script.runInThisContext(scriptOptions);
|
|
|
|
} else {
|
2018-05-26 04:26:34 +02:00
|
|
|
result = script.runInContext(context, scriptOptions);
|
2016-05-08 03:30:23 +02:00
|
|
|
}
|
|
|
|
} finally {
|
2016-09-18 11:16:45 +02:00
|
|
|
if (self.breakEvalOnSigint) {
|
2016-05-08 03:30:23 +02:00
|
|
|
// 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.
|
2018-09-03 17:28:38 +02:00
|
|
|
if (stopSigintWatchdog()) {
|
2016-05-08 03:30:23 +02:00
|
|
|
self.emit('SIGINT');
|
|
|
|
}
|
|
|
|
}
|
2013-08-28 03:53:39 +02:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
err = e;
|
2018-05-26 04:26:34 +02:00
|
|
|
|
2017-10-28 18:19:53 +02:00
|
|
|
if (process.domain) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
debug('not recoverable, send to domain');
|
|
|
|
process.domain.emit('error', err);
|
|
|
|
process.domain.exit();
|
|
|
|
return;
|
|
|
|
}
|
2013-08-28 03:53:39 +02:00
|
|
|
}
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
|
2017-09-23 09:10:47 +02:00
|
|
|
if (awaitPromise && !err) {
|
|
|
|
let sigintListener;
|
2018-02-11 19:17:03 +01:00
|
|
|
pause();
|
2017-09-23 09:10:47 +02:00
|
|
|
let promise = result;
|
|
|
|
if (self.breakEvalOnSigint) {
|
|
|
|
const interrupt = new Promise((resolve, reject) => {
|
|
|
|
sigintListener = () => {
|
2018-08-21 16:32:54 +02:00
|
|
|
const tmp = Error.stackTraceLimit;
|
|
|
|
Error.stackTraceLimit = 0;
|
|
|
|
const err = new ERR_SCRIPT_EXECUTION_INTERRUPTED();
|
|
|
|
Error.stackTraceLimit = tmp;
|
|
|
|
reject(err);
|
2017-09-23 09:10:47 +02:00
|
|
|
};
|
|
|
|
prioritizedSigintQueue.add(sigintListener);
|
|
|
|
});
|
2019-12-13 16:46:35 +01:00
|
|
|
promise = PromiseRace([promise, interrupt]);
|
2017-09-23 09:10:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
promise.then((result) => {
|
2019-01-27 01:18:47 +01:00
|
|
|
finishExecution(null, result);
|
2017-09-23 09:10:47 +02:00
|
|
|
}, (err) => {
|
|
|
|
if (err && process.domain) {
|
|
|
|
debug('not recoverable, send to domain');
|
|
|
|
process.domain.emit('error', err);
|
|
|
|
process.domain.exit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
finishExecution(err);
|
2018-10-30 09:33:00 +01:00
|
|
|
}).finally(() => {
|
|
|
|
// Remove prioritized SIGINT listener if it was not called.
|
|
|
|
prioritizedSigintQueue.delete(sigintListener);
|
|
|
|
unpause();
|
2017-09-23 09:10:47 +02:00
|
|
|
});
|
|
|
|
}
|
2015-07-08 23:53:48 +02:00
|
|
|
}
|
|
|
|
|
2017-09-23 09:10:47 +02:00
|
|
|
if (!awaitPromise || err) {
|
|
|
|
finishExecution(err, result);
|
|
|
|
}
|
2013-03-14 22:16:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
self.eval = self._domain.bind(eval_);
|
|
|
|
|
2016-10-29 17:04:54 +02:00
|
|
|
self._domain.on('error', function debugDomainError(e) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
debug('domain error');
|
2018-08-21 03:22:18 +02:00
|
|
|
let errStack = '';
|
|
|
|
|
|
|
|
if (typeof e === 'object' && e !== null) {
|
2019-09-16 22:32:15 +02:00
|
|
|
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 = stackFrames
|
|
|
|
.reverse()
|
|
|
|
.findIndex((frame) => frame.getFunctionName() === null);
|
|
|
|
// If found, get rid of it and everything below it
|
|
|
|
frames = stackFrames.splice(idx + 1);
|
|
|
|
} else {
|
|
|
|
frames = stackFrames;
|
|
|
|
}
|
|
|
|
// FIXME(devsnek): this is inconsistent with the checks
|
|
|
|
// that the real prepareStackTrace dispatch uses in
|
|
|
|
// lib/internal/errors.js.
|
|
|
|
if (typeof Error.prepareStackTrace === 'function') {
|
|
|
|
return Error.prepareStackTrace(error, frames);
|
|
|
|
}
|
|
|
|
frames.push(error);
|
|
|
|
return frames.reverse().join('\n at ');
|
|
|
|
});
|
2019-03-21 10:24:13 +01:00
|
|
|
decorateErrorStack(e);
|
2018-08-21 03:22:18 +02:00
|
|
|
|
|
|
|
if (e.domainThrown) {
|
|
|
|
delete e.domain;
|
|
|
|
delete e.domainThrown;
|
|
|
|
}
|
|
|
|
|
2019-03-21 10:24:13 +01:00
|
|
|
if (isError(e)) {
|
2018-08-21 03:22:18 +02:00
|
|
|
if (e.stack) {
|
|
|
|
if (e.name === 'SyntaxError') {
|
|
|
|
// Remove stack trace.
|
|
|
|
e.stack = e.stack
|
|
|
|
.replace(/^repl:\d+\r?\n/, '')
|
|
|
|
.replace(/^\s+at\s.*\n?/gm, '');
|
|
|
|
} else if (self.replMode === exports.REPL_MODE_STRICT) {
|
|
|
|
e.stack = e.stack.replace(/(\s+at\s+repl:)(\d+)/,
|
|
|
|
(_, pre, line) => pre + (line - 1));
|
|
|
|
}
|
|
|
|
}
|
2019-02-28 11:03:37 +01:00
|
|
|
errStack = self.writer(e);
|
2018-08-21 03:22:18 +02:00
|
|
|
|
|
|
|
// Remove one line error braces to keep the old style in place.
|
|
|
|
if (errStack[errStack.length - 1] === ']') {
|
|
|
|
errStack = errStack.slice(1, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!self.underscoreErrAssigned) {
|
|
|
|
self.lastError = e;
|
2017-07-16 17:12:57 +02:00
|
|
|
}
|
2018-08-21 03:22:18 +02:00
|
|
|
|
2018-05-17 16:25:36 +02:00
|
|
|
if (options[kStandaloneREPL] &&
|
|
|
|
process.listenerCount('uncaughtException') !== 0) {
|
|
|
|
process.nextTick(() => {
|
|
|
|
process.emit('uncaughtException', e);
|
2019-12-11 19:26:47 +01:00
|
|
|
self.clearBufferedCommand();
|
|
|
|
self.lines.level = [];
|
|
|
|
self.displayPrompt();
|
2018-05-17 16:25:36 +02:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (errStack === '') {
|
2019-09-23 19:38:12 +02:00
|
|
|
errStack = self.writer(e);
|
|
|
|
}
|
|
|
|
const lines = errStack.split(/(?<=\n)/);
|
|
|
|
let matched = false;
|
|
|
|
|
|
|
|
errStack = '';
|
|
|
|
for (const line of lines) {
|
|
|
|
if (!matched && /^\[?([A-Z][a-z0-9_]*)*Error/.test(line)) {
|
|
|
|
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}`;
|
2018-05-17 16:25:36 +02:00
|
|
|
}
|
2019-09-23 19:38:12 +02:00
|
|
|
// Normalize line endings.
|
|
|
|
errStack += errStack.endsWith('\n') ? '' : '\n';
|
2019-12-11 19:26:47 +01:00
|
|
|
self.outputStream.write(errStack);
|
|
|
|
self.clearBufferedCommand();
|
|
|
|
self.lines.level = [];
|
|
|
|
self.displayPrompt();
|
2018-05-17 16:25:36 +02:00
|
|
|
}
|
2013-03-14 22:16:13 +01:00
|
|
|
});
|
2011-09-07 09:39:49 +02:00
|
|
|
|
2012-08-24 22:18:19 +02:00
|
|
|
self.resetContext();
|
2013-08-28 03:53:39 +02:00
|
|
|
self.lines.level = [];
|
2012-08-24 22:18:19 +02:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
self.clearBufferedCommand();
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(this, 'bufferedCommand', {
|
2019-03-21 10:24:13 +01:00
|
|
|
get: deprecate(() => self[kBufferedCommandSymbol],
|
|
|
|
'REPLServer.bufferedCommand is deprecated',
|
|
|
|
'DEP0074'),
|
|
|
|
set: deprecate((val) => self[kBufferedCommandSymbol] = val,
|
|
|
|
'REPLServer.bufferedCommand is deprecated',
|
|
|
|
'DEP0074'),
|
2017-06-14 21:52:15 +02:00
|
|
|
enumerable: true
|
|
|
|
});
|
|
|
|
|
2016-07-04 00:29:34 +02:00
|
|
|
// Figure out which "complete" function to use.
|
2016-12-10 11:08:56 +01:00
|
|
|
self.completer = (typeof options.completer === 'function') ?
|
|
|
|
options.completer : completer;
|
2016-06-12 10:07:33 +02:00
|
|
|
|
|
|
|
function completer(text, cb) {
|
2016-12-10 11:08:56 +01:00
|
|
|
complete.call(self, text, self.editorMode ?
|
|
|
|
self.completeOnEditorMode(cb) : cb);
|
2016-06-12 10:07:33 +02:00
|
|
|
}
|
2011-01-24 19:55:30 +01:00
|
|
|
|
2015-10-07 08:05:53 +02:00
|
|
|
Interface.call(this, {
|
2015-04-23 09:35:53 +02:00
|
|
|
input: self.inputStream,
|
|
|
|
output: self.outputStream,
|
2016-07-04 00:29:34 +02:00
|
|
|
completer: self.completer,
|
2015-04-23 09:35:53 +02:00
|
|
|
terminal: options.terminal,
|
2016-06-03 03:31:23 +02:00
|
|
|
historySize: options.historySize,
|
|
|
|
prompt
|
2015-04-23 09:35:53 +02:00
|
|
|
});
|
2014-02-15 15:21:26 +01:00
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
this.commands = ObjectCreate(null);
|
2010-10-13 16:51:53 +02:00
|
|
|
defineDefaultCommands(this);
|
|
|
|
|
2017-12-30 03:20:51 +01:00
|
|
|
// Figure out which "writer" function to use
|
2012-03-28 02:39:14 +02:00
|
|
|
self.writer = options.writer || exports.writer;
|
|
|
|
|
2019-01-19 15:34:00 +01:00
|
|
|
if (self.writer === writer) {
|
|
|
|
// Conditionally turn on ANSI coloring.
|
|
|
|
writer.options.colors = self.useColors;
|
|
|
|
|
|
|
|
if (options[kStandaloneREPL]) {
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(inspect, 'replDefaults', {
|
2019-01-19 15:34:00 +01:00
|
|
|
get() {
|
|
|
|
return writer.options;
|
|
|
|
},
|
|
|
|
set(options) {
|
|
|
|
if (options === null || typeof options !== 'object') {
|
|
|
|
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
|
|
|
|
}
|
2019-11-22 18:04:46 +01:00
|
|
|
return ObjectAssign(writer.options, options);
|
2019-01-19 15:34:00 +01:00
|
|
|
},
|
|
|
|
enumerable: true,
|
|
|
|
configurable: true
|
|
|
|
});
|
|
|
|
}
|
2010-09-04 06:25:35 +02:00
|
|
|
}
|
2010-09-17 06:07:22 +02:00
|
|
|
|
2017-07-13 20:17:33 +02:00
|
|
|
function _parseREPLKeyword(keyword, rest) {
|
2019-03-26 05:21:27 +01:00
|
|
|
const cmd = this.commands[keyword];
|
2017-07-13 20:17:33 +02:00
|
|
|
if (cmd) {
|
|
|
|
cmd.action.call(this, rest);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-21 10:24:13 +01:00
|
|
|
self.parseREPLKeyword = deprecate(
|
2017-07-13 20:17:33 +02:00
|
|
|
_parseREPLKeyword,
|
|
|
|
'REPLServer.parseREPLKeyword() is deprecated',
|
|
|
|
'DEP0075');
|
|
|
|
|
2016-10-29 17:04:54 +02:00
|
|
|
self.on('close', function emitExit() {
|
2017-09-23 09:09:09 +02:00
|
|
|
if (paused) {
|
|
|
|
pausedBuffer.push(['close']);
|
|
|
|
return;
|
|
|
|
}
|
2012-03-27 21:41:42 +02:00
|
|
|
self.emit('exit');
|
|
|
|
});
|
|
|
|
|
2019-09-16 13:09:26 +02:00
|
|
|
let sawSIGINT = false;
|
|
|
|
let sawCtrlD = false;
|
2017-09-23 09:10:47 +02:00
|
|
|
const prioritizedSigintQueue = new Set();
|
2016-10-29 17:04:54 +02:00
|
|
|
self.on('SIGINT', function onSigInt() {
|
2017-09-23 09:10:47 +02:00
|
|
|
if (prioritizedSigintQueue.size > 0) {
|
|
|
|
for (const task of prioritizedSigintQueue) {
|
|
|
|
task();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-03-26 05:21:27 +01:00
|
|
|
const empty = self.line.length === 0;
|
2014-02-15 15:21:26 +01:00
|
|
|
self.clearLine();
|
2017-09-01 18:52:18 +02:00
|
|
|
_turnOffEditorMode(self);
|
2012-03-13 01:29:21 +01:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
const cmd = self[kBufferedCommandSymbol];
|
|
|
|
if (!(cmd && cmd.length > 0) && empty) {
|
2012-03-13 02:05:16 +01:00
|
|
|
if (sawSIGINT) {
|
2014-02-15 15:21:26 +01:00
|
|
|
self.close();
|
2012-03-13 02:05:16 +01:00
|
|
|
sawSIGINT = false;
|
|
|
|
return;
|
|
|
|
}
|
2019-02-21 15:32:08 +01:00
|
|
|
self.output.write('(To exit, press ^C again or ^D or type .exit)\n');
|
2012-03-13 02:05:16 +01:00
|
|
|
sawSIGINT = true;
|
|
|
|
} else {
|
2012-03-13 01:28:31 +01:00
|
|
|
sawSIGINT = false;
|
2010-09-17 06:07:22 +02:00
|
|
|
}
|
2011-09-20 12:04:35 +02:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
self.clearBufferedCommand();
|
2013-08-28 03:53:39 +02:00
|
|
|
self.lines.level = [];
|
2011-04-21 21:17:21 +02:00
|
|
|
self.displayPrompt();
|
2010-09-17 06:07:22 +02:00
|
|
|
});
|
|
|
|
|
2016-10-29 17:04:54 +02:00
|
|
|
self.on('line', function onLine(cmd) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
debug('line %j', cmd);
|
2015-11-14 01:40:08 +01:00
|
|
|
cmd = cmd || '';
|
2011-04-21 21:17:21 +02:00
|
|
|
sawSIGINT = false;
|
2015-01-19 06:42:05 +01:00
|
|
|
|
2016-06-12 10:07:33 +02:00
|
|
|
if (self.editorMode) {
|
2017-06-14 21:52:15 +02:00
|
|
|
self[kBufferedCommandSymbol] += cmd + '\n';
|
2016-08-23 20:35:59 +02:00
|
|
|
|
|
|
|
// code alignment
|
|
|
|
const matches = self._sawKeyPress ? cmd.match(/^\s+/) : null;
|
|
|
|
if (matches) {
|
|
|
|
const prefix = matches[0];
|
2016-10-20 16:57:40 +02:00
|
|
|
self.write(prefix);
|
2016-08-23 20:35:59 +02:00
|
|
|
self.line = prefix;
|
|
|
|
self.cursor = prefix.length;
|
|
|
|
}
|
2017-10-16 23:24:17 +02:00
|
|
|
_memory.call(self, cmd);
|
2016-06-12 10:07:33 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-14 01:40:08 +01:00
|
|
|
// Check REPL keywords and empty lines against a trimmed line input.
|
|
|
|
const trimmedCmd = cmd.trim();
|
2010-05-31 20:50:35 +02:00
|
|
|
|
|
|
|
// Check to see if a REPL keyword was used. If it returns true,
|
|
|
|
// display next prompt and return.
|
2015-11-14 01:40:08 +01:00
|
|
|
if (trimmedCmd) {
|
2017-07-23 18:17:07 +02:00
|
|
|
if (trimmedCmd.charAt(0) === '.' && trimmedCmd.charAt(1) !== '.' &&
|
2019-11-27 19:59:29 +01:00
|
|
|
NumberIsNaN(parseFloat(trimmedCmd))) {
|
2015-11-14 01:40:08 +01:00
|
|
|
const matches = trimmedCmd.match(/^\.([^\s]+)\s*(.*)$/);
|
|
|
|
const keyword = matches && matches[1];
|
|
|
|
const rest = matches && matches[2];
|
2017-07-13 20:17:33 +02:00
|
|
|
if (_parseREPLKeyword.call(self, keyword, rest) === true) {
|
2015-11-14 01:40:08 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-06-14 21:52:15 +02:00
|
|
|
if (!self[kBufferedCommandSymbol]) {
|
2015-11-14 01:40:08 +01:00
|
|
|
self.outputStream.write('Invalid REPL keyword\n');
|
|
|
|
finish(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-10-13 16:51:53 +02:00
|
|
|
}
|
2010-05-31 20:50:35 +02:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
const evalCmd = self[kBufferedCommandSymbol] + cmd + '\n';
|
2011-09-07 09:39:49 +02:00
|
|
|
|
2016-04-06 02:17:33 +02:00
|
|
|
debug('eval %j', evalCmd);
|
|
|
|
self.eval(evalCmd, self.context, 'repl', finish);
|
|
|
|
|
2011-09-11 09:05:00 +02:00
|
|
|
function finish(e, ret) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
debug('finish', e, ret);
|
2017-10-16 23:24:17 +02:00
|
|
|
_memory.call(self, cmd);
|
2011-11-12 02:44:39 +01:00
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
if (e && !self[kBufferedCommandSymbol] && cmd.trim().startsWith('npm ')) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
self.outputStream.write('npm should be run outside of the ' +
|
2016-08-17 16:47:13 +02:00
|
|
|
'node repl, in your normal shell.\n' +
|
|
|
|
'(Press Control-D to exit.)\n');
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
self.displayPrompt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
// If error was SyntaxError and not JSON.parse error
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
if (e) {
|
2015-11-14 01:40:08 +01:00
|
|
|
if (e instanceof Recoverable && !sawCtrlD) {
|
repl: Simplify paren wrap, continuation-detection
This simplifies the logic that was in isSyntaxError, as well as the
choice to wrap command input in parens to coerce to an expression
statement.
1. Rather than a growing blacklist of allowed-to-throw syntax errors,
just sniff for the one we really care about ("Unexpected end of input")
and let all the others pass through.
2. Wrapping {a:1} in parens makes sense, because blocks and line labels
are silly and confusing and should not be in JavaScript at all.
However, wrapping functions and other types of programs in parens is
weird and required yet *more* hacking to work around. By only wrapping
statements that start with { and end with }, we can handle the confusing
use-case, without having to then do extra work for functions and other
cases.
This also fixes the repl wart where `console.log)(` works in the repl,
but only by virtue of the fact that it's wrapped in parens first, as
well as potential side effects of double-running the commands, such as:
> x = 1
1
> eval('x++; throw new SyntaxError("e")')
... ^C
> x
3
2013-08-29 23:48:24 +02:00
|
|
|
// Start buffering data like that:
|
|
|
|
// {
|
|
|
|
// ... x: 1
|
|
|
|
// ... }
|
2017-06-14 21:52:15 +02:00
|
|
|
self[kBufferedCommandSymbol] += cmd + '\n';
|
2012-06-05 21:02:37 +02:00
|
|
|
self.displayPrompt();
|
|
|
|
return;
|
|
|
|
}
|
2020-04-08 18:58:03 +02:00
|
|
|
self._domain.emit('error', e.err || e);
|
2010-05-31 20:50:35 +02:00
|
|
|
}
|
2011-09-07 12:31:29 +02:00
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
// Clear buffer if no SyntaxErrors
|
2017-06-14 21:52:15 +02:00
|
|
|
self.clearBufferedCommand();
|
2016-06-12 10:07:33 +02:00
|
|
|
sawCtrlD = false;
|
2011-09-08 11:03:27 +02:00
|
|
|
|
|
|
|
// If we got any output - print it (if no error)
|
2015-07-12 02:53:39 +02:00
|
|
|
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)) {
|
2016-03-02 22:45:16 +01:00
|
|
|
if (!self.underscoreAssigned) {
|
|
|
|
self.last = ret;
|
|
|
|
}
|
2016-08-17 16:47:13 +02:00
|
|
|
self.outputStream.write(self.writer(ret) + '\n');
|
2011-09-07 12:31:29 +02:00
|
|
|
}
|
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
// Display prompt again
|
2011-09-07 09:39:49 +02:00
|
|
|
self.displayPrompt();
|
2015-04-28 19:46:14 +02:00
|
|
|
}
|
2010-05-31 20:50:35 +02:00
|
|
|
});
|
|
|
|
|
2016-10-29 17:04:54 +02:00
|
|
|
self.on('SIGCONT', function onSigCont() {
|
2016-06-12 10:07:33 +02:00
|
|
|
if (self.editorMode) {
|
2016-08-17 16:47:13 +02:00
|
|
|
self.outputStream.write(`${self._initialPrompt}.editor\n`);
|
2016-06-12 10:07:33 +02:00
|
|
|
self.outputStream.write(
|
2016-08-17 16:47:13 +02:00
|
|
|
'// Entering editor mode (^D to finish, ^C to cancel)\n');
|
2017-06-14 21:52:15 +02:00
|
|
|
self.outputStream.write(`${self[kBufferedCommandSymbol]}\n`);
|
2016-06-12 10:07:33 +02:00
|
|
|
self.prompt(true);
|
|
|
|
} else {
|
|
|
|
self.displayPrompt(true);
|
|
|
|
}
|
2010-04-12 01:13:32 +02:00
|
|
|
});
|
2010-05-31 20:50:35 +02:00
|
|
|
|
2019-12-16 10:22:48 +01:00
|
|
|
const { reverseSearch } = setupReverseSearch(this);
|
|
|
|
|
2019-12-05 14:41:49 +01:00
|
|
|
const {
|
|
|
|
clearPreview,
|
2019-12-11 19:33:53 +01:00
|
|
|
showPreview
|
2019-12-05 14:41:49 +01:00
|
|
|
} = setupPreview(
|
|
|
|
this,
|
|
|
|
kContextId,
|
|
|
|
kBufferedCommandSymbol,
|
|
|
|
preview
|
|
|
|
);
|
|
|
|
|
2017-09-23 09:09:09 +02:00
|
|
|
// Wrap readline tty to enable editor mode and pausing.
|
2016-06-12 10:07:33 +02:00
|
|
|
const ttyWrite = self._ttyWrite.bind(self);
|
|
|
|
self._ttyWrite = (d, key) => {
|
2016-10-20 16:47:33 +02:00
|
|
|
key = key || {};
|
2017-09-23 09:09:09 +02:00
|
|
|
if (paused && !(self.breakEvalOnSigint && key.ctrl && key.name === 'c')) {
|
2020-01-11 13:36:21 +01:00
|
|
|
pausedBuffer.push(['key', [d, key], self.isCompletionEnabled]);
|
2017-09-23 09:09:09 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-06-12 10:07:33 +02:00
|
|
|
if (!self.editorMode || !self.terminal) {
|
2019-02-21 15:32:55 +01:00
|
|
|
// Before exiting, make sure to clear the line.
|
|
|
|
if (key.ctrl && key.name === 'd' &&
|
|
|
|
self.cursor === 0 && self.line.length === 0) {
|
|
|
|
self.clearLine();
|
|
|
|
}
|
2020-05-04 03:46:23 +02:00
|
|
|
clearPreview(key);
|
2019-12-16 10:22:48 +01:00
|
|
|
if (!reverseSearch(d, key)) {
|
|
|
|
ttyWrite(d, key);
|
|
|
|
showPreview();
|
|
|
|
}
|
2016-06-12 10:07:33 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-12-30 03:20:51 +01:00
|
|
|
// Editor mode
|
2016-06-12 10:07:33 +02:00
|
|
|
if (key.ctrl && !key.shift) {
|
|
|
|
switch (key.name) {
|
2019-12-11 19:33:53 +01:00
|
|
|
// TODO(BridgeAR): There should not be a special mode necessary for full
|
|
|
|
// multiline support.
|
2016-06-12 10:07:33 +02:00
|
|
|
case 'd': // End editor mode
|
2017-09-01 18:52:18 +02:00
|
|
|
_turnOffEditorMode(self);
|
2016-06-12 10:07:33 +02:00
|
|
|
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':
|
2017-12-30 03:20:51 +01:00
|
|
|
// Prevent double tab behavior
|
2016-06-12 10:07:33 +02:00
|
|
|
self._previousKey = null;
|
|
|
|
ttyWrite(d, key);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ttyWrite(d, key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-04-12 01:13:32 +02:00
|
|
|
self.displayPrompt();
|
|
|
|
}
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectSetPrototypeOf(REPLServer.prototype, Interface.prototype);
|
|
|
|
ObjectSetPrototypeOf(REPLServer, Interface);
|
2018-12-02 15:03:01 +01:00
|
|
|
|
2010-04-12 01:13:32 +02:00
|
|
|
exports.REPLServer = REPLServer;
|
2010-03-15 23:11:40 +01:00
|
|
|
|
2020-05-04 03:49:04 +02:00
|
|
|
exports.REPL_MODE_SLOPPY = REPL_MODE_SLOPPY;
|
|
|
|
exports.REPL_MODE_STRICT = REPL_MODE_STRICT;
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2018-12-10 13:27:32 +01:00
|
|
|
// Prompt is a string to print on each line for the prompt,
|
2010-04-12 01:13:32 +02:00
|
|
|
// source is a stream to use for I/O, defaulting to stdin/stdout.
|
2015-04-23 09:35:53 +02:00
|
|
|
exports.start = function(prompt,
|
|
|
|
source,
|
|
|
|
eval_,
|
|
|
|
useGlobal,
|
|
|
|
ignoreUndefined,
|
|
|
|
replMode) {
|
2019-12-15 17:43:46 +01:00
|
|
|
return new REPLServer(
|
|
|
|
prompt, source, eval_, useGlobal, ignoreUndefined, replMode);
|
2010-04-12 01:13:32 +02:00
|
|
|
};
|
2010-03-15 23:11:40 +01:00
|
|
|
|
2019-02-01 18:49:16 +01:00
|
|
|
REPLServer.prototype.setupHistory = function setupHistory(historyFile, cb) {
|
|
|
|
history(this, historyFile, cb);
|
|
|
|
};
|
|
|
|
|
2017-06-14 21:52:15 +02:00
|
|
|
REPLServer.prototype.clearBufferedCommand = function clearBufferedCommand() {
|
|
|
|
this[kBufferedCommandSymbol] = '';
|
|
|
|
};
|
|
|
|
|
2016-10-16 00:02:43 +02:00
|
|
|
REPLServer.prototype.close = function close() {
|
2015-10-07 08:05:53 +02:00
|
|
|
if (this.terminal && this._flushing && !this._closingOnFlush) {
|
|
|
|
this._closingOnFlush = true;
|
|
|
|
this.once('flushHistory', () =>
|
|
|
|
Interface.prototype.close.call(this)
|
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
process.nextTick(() =>
|
|
|
|
Interface.prototype.close.call(this)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2011-10-20 17:54:50 +02:00
|
|
|
REPLServer.prototype.createContext = function() {
|
2019-09-16 13:09:26 +02:00
|
|
|
let context;
|
2012-10-13 01:34:36 +02:00
|
|
|
if (this.useGlobal) {
|
|
|
|
context = global;
|
2011-10-20 19:00:15 +02:00
|
|
|
} else {
|
2017-10-29 18:19:24 +01:00
|
|
|
sendInspectorCommand((session) => {
|
|
|
|
session.post('Runtime.enable');
|
2018-02-20 13:12:58 +01:00
|
|
|
session.once('Runtime.executionContextCreated', ({ params }) => {
|
2017-10-29 18:19:24 +01:00
|
|
|
this[kContextId] = params.context.id;
|
|
|
|
});
|
|
|
|
context = vm.createContext();
|
|
|
|
session.post('Runtime.disable');
|
|
|
|
}, () => {
|
|
|
|
context = vm.createContext();
|
|
|
|
});
|
2019-11-22 18:04:46 +01:00
|
|
|
for (const name of ObjectGetOwnPropertyNames(global)) {
|
2019-12-10 15:37:32 +01:00
|
|
|
// Only set properties that do not already exist as a global builtin.
|
|
|
|
if (!globalBuiltins.has(name)) {
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(context, name,
|
|
|
|
ObjectGetOwnPropertyDescriptor(global, name));
|
2019-07-05 17:24:28 +02:00
|
|
|
}
|
2019-01-25 08:26:53 +01:00
|
|
|
}
|
2012-08-24 22:18:19 +02:00
|
|
|
context.global = context;
|
2016-06-22 19:07:59 +02:00
|
|
|
const _console = new Console(this.outputStream);
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(context, 'console', {
|
2016-06-22 19:07:59 +02:00
|
|
|
configurable: true,
|
2018-05-14 14:51:50 +02:00
|
|
|
writable: true,
|
2017-12-30 03:22:01 +01:00
|
|
|
value: _console
|
2016-06-22 19:07:59 +02:00
|
|
|
});
|
2011-10-20 19:00:15 +02:00
|
|
|
}
|
2011-10-20 17:54:50 +02:00
|
|
|
|
2019-03-26 05:21:27 +01:00
|
|
|
const module = new CJSModule('<repl>');
|
2019-12-10 18:17:01 +01:00
|
|
|
module.paths = CJSModule._resolveLookupPaths('<repl>', parentModule);
|
2015-12-09 21:55:29 +01:00
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(context, 'module', {
|
2018-05-14 14:51:50 +02:00
|
|
|
configurable: true,
|
|
|
|
writable: true,
|
|
|
|
value: module
|
|
|
|
});
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(context, 'require', {
|
2018-05-14 14:51:50 +02:00
|
|
|
configurable: true,
|
|
|
|
writable: true,
|
|
|
|
value: makeRequireFunction(module)
|
|
|
|
});
|
2011-10-20 17:54:50 +02:00
|
|
|
|
2018-03-06 19:30:18 +01:00
|
|
|
addBuiltinLibsToObject(context);
|
2017-07-15 05:23:02 +02:00
|
|
|
|
|
|
|
return context;
|
|
|
|
};
|
2016-03-02 22:45:16 +01:00
|
|
|
|
2017-07-15 05:23:02 +02:00
|
|
|
REPLServer.prototype.resetContext = function() {
|
|
|
|
this.context = this.createContext();
|
2016-03-02 22:45:16 +01:00
|
|
|
this.underscoreAssigned = false;
|
2018-02-21 21:50:20 +01:00
|
|
|
this.underscoreErrAssigned = false;
|
2011-11-12 02:44:39 +01:00
|
|
|
this.lines = [];
|
|
|
|
this.lines.level = [];
|
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(this.context, '_', {
|
2016-03-02 22:45:16 +01:00
|
|
|
configurable: true,
|
2017-01-12 21:52:20 +01:00
|
|
|
get: () => this.last,
|
2016-03-02 22:45:16 +01:00
|
|
|
set: (value) => {
|
|
|
|
this.last = value;
|
|
|
|
if (!this.underscoreAssigned) {
|
|
|
|
this.underscoreAssigned = true;
|
2016-08-17 16:47:13 +02:00
|
|
|
this.outputStream.write('Expression assignment to _ now disabled.\n');
|
2016-03-02 22:45:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectDefineProperty(this.context, '_error', {
|
2018-02-21 21:50:20 +01:00
|
|
|
configurable: true,
|
|
|
|
get: () => this.lastError,
|
|
|
|
set: (value) => {
|
|
|
|
this.lastError = value;
|
|
|
|
if (!this.underscoreErrAssigned) {
|
|
|
|
this.underscoreErrAssigned = true;
|
|
|
|
this.outputStream.write(
|
|
|
|
'Expression assignment to _error now disabled.\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-03-14 21:49:14 +01:00
|
|
|
// Allow REPL extensions to extend the new context
|
|
|
|
this.emit('reset', this.context);
|
2011-03-29 20:08:39 +02:00
|
|
|
};
|
|
|
|
|
2011-12-09 10:24:15 +01:00
|
|
|
REPLServer.prototype.displayPrompt = function(preserveCursor) {
|
2019-09-16 13:09:26 +02:00
|
|
|
let prompt = this._initialPrompt;
|
2017-06-14 21:52:15 +02:00
|
|
|
if (this[kBufferedCommandSymbol].length) {
|
2012-02-19 00:01:35 +01:00
|
|
|
prompt = '...';
|
2015-11-18 13:59:43 +01:00
|
|
|
const len = this.lines.level.length ? this.lines.level.length - 1 : 0;
|
|
|
|
const levelInd = '..'.repeat(len);
|
2012-02-19 00:01:35 +01:00
|
|
|
prompt += levelInd + ' ';
|
|
|
|
}
|
2014-07-31 10:30:46 +02:00
|
|
|
|
|
|
|
// Do not overwrite `_initialPrompt` here
|
2018-11-30 17:55:48 +01:00
|
|
|
Interface.prototype.setPrompt.call(this, prompt);
|
2014-02-15 15:21:26 +01:00
|
|
|
this.prompt(preserveCursor);
|
2014-07-31 10:30:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// When invoked as an API method, overwrite _initialPrompt
|
|
|
|
REPLServer.prototype.setPrompt = function setPrompt(prompt) {
|
|
|
|
this._initialPrompt = prompt;
|
2018-11-30 17:55:48 +01:00
|
|
|
Interface.prototype.setPrompt.call(this, prompt);
|
2010-04-12 01:13:32 +02:00
|
|
|
};
|
2009-09-24 00:56:24 +02:00
|
|
|
|
2019-03-21 10:24:13 +01:00
|
|
|
REPLServer.prototype.turnOffEditorMode = deprecate(
|
2017-09-01 18:52:18 +02:00
|
|
|
function() { _turnOffEditorMode(this); },
|
|
|
|
'REPLServer.turnOffEditorMode() is deprecated',
|
2017-09-28 23:35:05 +02:00
|
|
|
'DEP0078');
|
2016-06-12 10:07:33 +02:00
|
|
|
|
2020-05-07 17:13:07 +02:00
|
|
|
const requireRE = /\brequire\s*\(\s*['"`](([\w@./-]+\/)?(?:[\w@./-]*))(?![^'"`])$/;
|
2019-09-09 18:03:10 +02:00
|
|
|
const fsAutoCompleteRE = /fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
|
2015-01-21 17:36:59 +01:00
|
|
|
const simpleExpressionRE =
|
2017-06-16 19:14:58 +02:00
|
|
|
/(?:[a-zA-Z_$](?:\w|\$)*\.)*[a-zA-Z_$](?:\w|\$)*\.?$/;
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2018-06-27 06:57:57 +02:00
|
|
|
function isIdentifier(str) {
|
|
|
|
if (str === '') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const first = str.codePointAt(0);
|
|
|
|
if (!isIdentifierStart(first)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const firstLen = first > 0xffff ? 2 : 1;
|
2019-09-16 13:09:26 +02:00
|
|
|
for (let i = firstLen; i < str.length; i += 1) {
|
2018-06-27 06:57:57 +02:00
|
|
|
const cp = str.codePointAt(i);
|
|
|
|
if (!isIdentifierChar(cp)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (cp > 0xffff) {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2015-08-17 21:03:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function filteredOwnPropertyNames(obj) {
|
|
|
|
if (!obj) return [];
|
2018-08-20 03:26:27 +02:00
|
|
|
const filter = ALL_PROPERTIES | SKIP_SYMBOLS;
|
|
|
|
return getOwnNonIndexProperties(obj, filter).filter(isIdentifier);
|
2015-08-17 21:03:32 +02:00
|
|
|
}
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2017-10-29 18:19:24 +01:00
|
|
|
function getGlobalLexicalScopeNames(contextId) {
|
|
|
|
return sendInspectorCommand((session) => {
|
|
|
|
let names = [];
|
|
|
|
session.post('Runtime.globalLexicalScopeNames', {
|
|
|
|
executionContextId: contextId
|
|
|
|
}, (error, result) => {
|
|
|
|
if (!error) names = result.names;
|
|
|
|
});
|
|
|
|
return names;
|
|
|
|
}, () => []);
|
|
|
|
}
|
|
|
|
|
2016-07-04 00:29:34 +02:00
|
|
|
REPLServer.prototype.complete = function() {
|
|
|
|
this.completer.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
2020-05-07 17:13:07 +02:00
|
|
|
function gracefulOperation(fn, args, alternative) {
|
|
|
|
try {
|
|
|
|
return fn(...args);
|
|
|
|
} catch {
|
|
|
|
return alternative;
|
|
|
|
}
|
|
|
|
}
|
2019-12-16 10:22:48 +01:00
|
|
|
|
2010-12-02 03:07:20 +01:00
|
|
|
// Provide a list of completions for the given leading text. This is
|
|
|
|
// given to the readline interface for handling tab completion.
|
|
|
|
//
|
|
|
|
// Example:
|
2019-09-16 13:09:26 +02:00
|
|
|
// complete('let foo = util.')
|
2015-08-24 23:53:46 +02:00
|
|
|
// -> [['util.print', 'util.debug', 'util.log', 'util.inspect'],
|
2010-12-02 03:07:20 +01:00
|
|
|
// 'util.' ]
|
|
|
|
//
|
|
|
|
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
|
|
|
// getter code.
|
2016-07-04 00:29:34 +02:00
|
|
|
function complete(line, callback) {
|
2017-12-30 03:20:51 +01:00
|
|
|
// List of completion lists, one for each inheritance "level"
|
2019-09-16 13:09:26 +02:00
|
|
|
let completionGroups = [];
|
|
|
|
let completeOn, group;
|
2010-08-09 11:18:32 +02:00
|
|
|
|
2019-12-11 19:26:47 +01:00
|
|
|
// Ignore right whitespace. It could change the outcome.
|
|
|
|
line = line.trimLeft();
|
|
|
|
|
2010-08-17 17:39:54 +02:00
|
|
|
// REPL commands (e.g. ".break").
|
2019-09-16 13:09:26 +02:00
|
|
|
let filter;
|
2020-05-07 17:13:07 +02:00
|
|
|
if (/^\s*\.(\w*)$/.test(line)) {
|
2019-11-22 18:04:46 +01:00
|
|
|
completionGroups.push(ObjectKeys(this.commands));
|
2020-05-07 17:13:07 +02:00
|
|
|
completeOn = line.match(/^\s*\.(\w*)$/)[1];
|
|
|
|
if (completeOn.length) {
|
|
|
|
filter = completeOn;
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
completionGroupsLoaded();
|
2020-05-07 17:13:07 +02:00
|
|
|
} else if (requireRE.test(line)) {
|
2010-12-02 03:07:20 +01:00
|
|
|
// require('...<Tab>')
|
2020-05-07 17:13:07 +02:00
|
|
|
const extensions = ObjectKeys(this.context.require.extensions);
|
|
|
|
const indexes = extensions.map((extension) => `index${extension}`);
|
|
|
|
indexes.push('package.json', 'index');
|
2019-09-16 13:09:26 +02:00
|
|
|
const versionedFileNamesRe = /-\d+\.\d+/;
|
2010-08-17 17:39:54 +02:00
|
|
|
|
2020-05-07 17:13:07 +02:00
|
|
|
const match = line.match(requireRE);
|
2010-08-17 17:39:54 +02:00
|
|
|
completeOn = match[1];
|
2019-09-16 13:09:26 +02:00
|
|
|
const subdir = match[2] || '';
|
2020-05-07 17:13:07 +02:00
|
|
|
filter = completeOn;
|
2010-08-17 17:39:54 +02:00
|
|
|
group = [];
|
2017-07-21 10:30:07 +02:00
|
|
|
let paths = [];
|
|
|
|
|
|
|
|
if (completeOn === '.') {
|
|
|
|
group = ['./', '../'];
|
|
|
|
} else if (completeOn === '..') {
|
|
|
|
group = ['../'];
|
|
|
|
} else if (/^\.\.?\//.test(completeOn)) {
|
|
|
|
paths = [process.cwd()];
|
|
|
|
} else {
|
2018-03-06 19:30:18 +01:00
|
|
|
paths = module.paths.concat(CJSModule.globalPaths);
|
2017-07-21 10:30:07 +02:00
|
|
|
}
|
|
|
|
|
2020-05-07 17:13:07 +02:00
|
|
|
for (let dir of paths) {
|
|
|
|
dir = path.resolve(dir, subdir);
|
|
|
|
const dirents = gracefulOperation(
|
|
|
|
fs.readdirSync,
|
|
|
|
[dir, { withFileTypes: true }],
|
|
|
|
[]
|
|
|
|
);
|
|
|
|
for (const dirent of dirents) {
|
|
|
|
if (versionedFileNamesRe.test(dirent.name) || dirent.name === '.npm') {
|
2010-08-17 17:39:54 +02:00
|
|
|
// Exclude versioned names that 'npm' installs.
|
|
|
|
continue;
|
|
|
|
}
|
2020-05-07 17:13:07 +02:00
|
|
|
const extension = path.extname(dirent.name);
|
|
|
|
const base = dirent.name.slice(0, -extension.length);
|
|
|
|
if (!dirent.isDirectory()) {
|
|
|
|
if (extensions.includes(extension) && (!subdir || base !== 'index')) {
|
|
|
|
group.push(`${subdir}${base}`);
|
|
|
|
}
|
2017-08-10 06:51:56 +02:00
|
|
|
continue;
|
|
|
|
}
|
2020-05-07 17:13:07 +02:00
|
|
|
group.push(`${subdir}${dirent.name}/`);
|
|
|
|
const absolute = path.resolve(dir, dirent.name);
|
|
|
|
const subfiles = gracefulOperation(fs.readdirSync, [absolute], []);
|
|
|
|
for (const subfile of subfiles) {
|
|
|
|
if (indexes.includes(subfile)) {
|
|
|
|
group.push(`${subdir}${dirent.name}`);
|
|
|
|
break;
|
2017-08-10 06:51:56 +02:00
|
|
|
}
|
2010-08-17 17:39:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (group.length) {
|
|
|
|
completionGroups.push(group);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!subdir) {
|
2012-03-19 17:08:25 +01:00
|
|
|
completionGroups.push(exports._builtinLibs);
|
2010-08-17 17:39:54 +02:00
|
|
|
}
|
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
completionGroupsLoaded();
|
2020-05-07 17:13:07 +02:00
|
|
|
} else if (fsAutoCompleteRE.test(line)) {
|
2019-04-25 16:44:47 +02:00
|
|
|
filter = '';
|
2020-05-07 17:13:07 +02:00
|
|
|
let filePath = line.match(fsAutoCompleteRE)[1];
|
|
|
|
let fileList;
|
2019-04-25 16:44:47 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
fileList = fs.readdirSync(filePath, { withFileTypes: true });
|
|
|
|
completionGroups.push(fileList.map((dirent) => dirent.name));
|
|
|
|
completeOn = '';
|
|
|
|
} catch {
|
|
|
|
try {
|
|
|
|
const baseName = path.basename(filePath);
|
|
|
|
filePath = path.dirname(filePath);
|
|
|
|
fileList = fs.readdirSync(filePath, { withFileTypes: true });
|
|
|
|
const filteredValue = fileList.filter((d) =>
|
|
|
|
d.name.startsWith(baseName))
|
|
|
|
.map((d) => d.name);
|
|
|
|
completionGroups.push(filteredValue);
|
2019-09-16 11:08:18 +02:00
|
|
|
completeOn = baseName;
|
2019-04-25 16:44:47 +02:00
|
|
|
} catch {}
|
|
|
|
}
|
|
|
|
|
|
|
|
completionGroupsLoaded();
|
2010-08-09 11:18:32 +02:00
|
|
|
// 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 ''
|
2017-06-08 01:00:33 +02:00
|
|
|
} else if (line.length === 0 || /\w|\.|\$/.test(line[line.length - 1])) {
|
2020-05-07 17:13:07 +02:00
|
|
|
const match = simpleExpressionRE.exec(line);
|
2019-12-11 19:24:39 +01:00
|
|
|
if (line.length !== 0 && !match) {
|
|
|
|
completionGroupsLoaded();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let expr;
|
|
|
|
completeOn = (match ? match[0] : '');
|
|
|
|
if (line.length === 0) {
|
|
|
|
filter = '';
|
|
|
|
expr = '';
|
|
|
|
} else if (line[line.length - 1] === '.') {
|
|
|
|
filter = '';
|
|
|
|
expr = match[0].slice(0, match[0].length - 1);
|
|
|
|
} else {
|
|
|
|
const bits = match[0].split('.');
|
|
|
|
filter = bits.pop();
|
|
|
|
expr = bits.join('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resolve expr and get its completions.
|
|
|
|
const memberGroups = [];
|
|
|
|
if (!expr) {
|
|
|
|
// Get global vars synchronously
|
|
|
|
completionGroups.push(getGlobalLexicalScopeNames(this[kContextId]));
|
|
|
|
let contextProto = this.context;
|
|
|
|
while (contextProto = ObjectGetPrototypeOf(contextProto)) {
|
|
|
|
completionGroups.push(filteredOwnPropertyNames(contextProto));
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
const contextOwnNames = filteredOwnPropertyNames(this.context);
|
|
|
|
if (!this.useGlobal) {
|
|
|
|
// When the context is not `global`, builtins are not own
|
|
|
|
// properties of it.
|
|
|
|
contextOwnNames.push(...globalBuiltins);
|
|
|
|
}
|
|
|
|
completionGroups.push(contextOwnNames);
|
|
|
|
if (filter !== '') addCommonWords(completionGroups);
|
|
|
|
completionGroupsLoaded();
|
|
|
|
return;
|
|
|
|
}
|
2010-12-02 03:07:20 +01:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
const evalExpr = `try { ${expr} } catch {}`;
|
|
|
|
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
|
|
|
|
if (obj != null) {
|
|
|
|
if (typeof obj === 'object' || typeof obj === 'function') {
|
|
|
|
try {
|
|
|
|
memberGroups.push(filteredOwnPropertyNames(obj));
|
|
|
|
} catch {
|
|
|
|
// Probably 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
|
|
|
|
}
|
2011-10-20 17:54:50 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
// Works for non-objects
|
|
|
|
try {
|
|
|
|
let p;
|
|
|
|
if (typeof obj === 'object' || typeof obj === 'function') {
|
|
|
|
p = ObjectGetPrototypeOf(obj);
|
|
|
|
} else {
|
|
|
|
p = obj.constructor ? obj.constructor.prototype : null;
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
// Circular refs possible? Let's guard against that.
|
|
|
|
let sentinel = 5;
|
|
|
|
while (p !== null && sentinel-- !== 0) {
|
|
|
|
memberGroups.push(filteredOwnPropertyNames(p));
|
|
|
|
p = ObjectGetPrototypeOf(p);
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
} catch {}
|
|
|
|
}
|
2011-09-07 10:12:10 +02:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
if (memberGroups.length) {
|
|
|
|
for (let i = 0; i < memberGroups.length; i++) {
|
|
|
|
completionGroups.push(
|
|
|
|
memberGroups[i].map((member) => `${expr}.${member}`));
|
|
|
|
}
|
|
|
|
if (filter) {
|
|
|
|
filter = `${expr}.${filter}`;
|
|
|
|
}
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
completionGroupsLoaded();
|
2019-12-11 19:24:39 +01:00
|
|
|
});
|
2012-03-20 02:44:22 +01:00
|
|
|
} else {
|
|
|
|
completionGroupsLoaded();
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
|
|
|
|
2011-09-08 11:03:27 +02:00
|
|
|
// Will be called when all completionGroups are in place
|
|
|
|
// Useful for async autocompletion
|
2017-10-11 22:57:35 +02:00
|
|
|
function completionGroupsLoaded() {
|
2011-09-07 09:39:49 +02:00
|
|
|
// Filter, sort (within each group), uniq and merge the completion groups.
|
|
|
|
if (completionGroups.length && filter) {
|
2019-09-16 13:09:26 +02:00
|
|
|
const newCompletionGroups = [];
|
2019-09-12 12:15:27 +02:00
|
|
|
for (let i = 0; i < completionGroups.length; i++) {
|
2017-12-30 03:22:01 +01:00
|
|
|
group = completionGroups[i]
|
|
|
|
.filter((elem) => elem.indexOf(filter) === 0);
|
2011-09-07 09:39:49 +02:00
|
|
|
if (group.length) {
|
|
|
|
newCompletionGroups.push(group);
|
|
|
|
}
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2011-09-07 09:39:49 +02:00
|
|
|
completionGroups = newCompletionGroups;
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
const completions = [];
|
|
|
|
// Unique completions across all groups.
|
|
|
|
const uniqueSet = new Set(['']);
|
|
|
|
// Completion group 0 is the "closest" (least far up the inheritance
|
|
|
|
// chain) so we put its completions last: to be closest in the REPL.
|
|
|
|
for (const group of completionGroups) {
|
|
|
|
group.sort((a, b) => (b > a ? 1 : -1));
|
|
|
|
const setSize = uniqueSet.size;
|
|
|
|
for (const entry of group) {
|
|
|
|
if (!uniqueSet.has(entry)) {
|
|
|
|
completions.unshift(entry);
|
|
|
|
uniqueSet.add(entry);
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
2011-09-07 09:39:49 +02:00
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
// Add a separator between groups.
|
|
|
|
if (uniqueSet.size !== setSize) {
|
|
|
|
completions.unshift('');
|
2010-08-09 11:18:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
// Remove obsolete group entry, if present.
|
|
|
|
if (completions[0] === '') {
|
|
|
|
completions.shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, [completions, completeOn]);
|
2011-09-07 09:39:49 +02:00
|
|
|
}
|
2016-07-04 00:29:34 +02:00
|
|
|
}
|
2010-08-09 11:18:32 +02:00
|
|
|
|
2016-06-12 10:07:33 +02:00
|
|
|
REPLServer.prototype.completeOnEditorMode = (callback) => (err, results) => {
|
|
|
|
if (err) return callback(err);
|
|
|
|
|
|
|
|
const [completions, completeOn = ''] = results;
|
2020-01-10 13:52:33 +01:00
|
|
|
let result = completions.filter((v) => v);
|
2016-06-12 10:07:33 +02:00
|
|
|
|
2020-01-10 13:52:33 +01:00
|
|
|
if (completeOn && result.length !== 0) {
|
|
|
|
result = [commonPrefix(result)];
|
|
|
|
}
|
2016-06-12 10:07:33 +02:00
|
|
|
|
2020-01-10 13:52:33 +01:00
|
|
|
callback(null, [result, completeOn]);
|
2016-06-12 10:07:33 +02:00
|
|
|
};
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2010-10-13 16:51:53 +02:00
|
|
|
REPLServer.prototype.defineCommand = function(keyword, cmd) {
|
2015-01-29 02:05:53 +01:00
|
|
|
if (typeof cmd === 'function') {
|
2017-07-11 02:55:21 +02:00
|
|
|
cmd = { action: cmd };
|
2015-01-29 02:05:53 +01:00
|
|
|
} else if (typeof cmd.action !== 'function') {
|
2020-03-24 23:05:39 +01:00
|
|
|
throw new ERR_INVALID_ARG_TYPE('cmd.action', 'Function', cmd.action);
|
2010-10-13 16:51:53 +02:00
|
|
|
}
|
2014-02-17 23:59:28 +01:00
|
|
|
this.commands[keyword] = cmd;
|
2010-10-13 16:51:53 +02:00
|
|
|
};
|
|
|
|
|
2019-03-21 10:24:13 +01:00
|
|
|
REPLServer.prototype.memory = deprecate(
|
2017-10-16 23:24:17 +02:00
|
|
|
_memory,
|
|
|
|
'REPLServer.memory() is deprecated',
|
|
|
|
'DEP0082');
|
2011-11-12 02:44:39 +01:00
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
// 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.
|
2017-10-16 23:24:17 +02:00
|
|
|
function _memory(cmd) {
|
|
|
|
const self = this;
|
2011-11-12 02:44:39 +01:00
|
|
|
self.lines = self.lines || [];
|
|
|
|
self.lines.level = self.lines.level || [];
|
|
|
|
|
2017-12-30 03:20:51 +01:00
|
|
|
// Save the line so I can do magic later
|
2011-11-12 02:44:39 +01:00
|
|
|
if (cmd) {
|
2015-11-18 13:59:43 +01:00
|
|
|
const len = self.lines.level.length ? self.lines.level.length - 1 : 0;
|
|
|
|
self.lines.push(' '.repeat(len) + cmd);
|
2011-11-12 02:44:39 +01:00
|
|
|
} else {
|
|
|
|
// I don't want to not change the format too much...
|
|
|
|
self.lines.push('');
|
|
|
|
}
|
|
|
|
|
2019-12-11 19:24:39 +01:00
|
|
|
if (!cmd) {
|
|
|
|
self.lines.level = [];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-12 02:44:39 +01:00
|
|
|
// 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
|
2019-12-11 19:24:39 +01:00
|
|
|
|
|
|
|
// Going down is { and ( e.g. function() {
|
|
|
|
// going up is } and )
|
|
|
|
let dw = cmd.match(/[{(]/g);
|
|
|
|
let up = cmd.match(/[})]/g);
|
|
|
|
up = up ? up.length : 0;
|
|
|
|
dw = dw ? dw.length : 0;
|
|
|
|
let depth = dw - up;
|
|
|
|
|
|
|
|
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.
|
|
|
|
self.lines.level.push({
|
|
|
|
line: self.lines.length - 1,
|
2019-12-11 19:26:47 +01:00
|
|
|
depth: depth
|
2019-12-11 19:24:39 +01:00
|
|
|
});
|
|
|
|
} else if (depth < 0) {
|
|
|
|
// Going... up.
|
|
|
|
const curr = self.lines.level.pop();
|
|
|
|
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;
|
|
|
|
self.lines.level.push(curr);
|
2011-11-12 02:44:39 +01:00
|
|
|
}
|
|
|
|
}
|
2019-12-11 19:24:39 +01:00
|
|
|
}
|
|
|
|
}());
|
2011-11-12 02:44:39 +01:00
|
|
|
}
|
2017-10-16 23:24:17 +02:00
|
|
|
}
|
2011-11-12 02:44:39 +01:00
|
|
|
|
2019-01-25 08:26:53 +01:00
|
|
|
function addCommonWords(completionGroups) {
|
|
|
|
// Only words which do not yet exist as global property should be added to
|
|
|
|
// this list.
|
|
|
|
completionGroups.push([
|
|
|
|
'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'
|
|
|
|
]);
|
2012-07-04 23:14:27 +02:00
|
|
|
}
|
2011-01-02 06:41:07 +01:00
|
|
|
|
2017-09-01 18:52:18 +02:00
|
|
|
function _turnOnEditorMode(repl) {
|
|
|
|
repl.editorMode = true;
|
2018-11-30 17:55:48 +01:00
|
|
|
Interface.prototype.setPrompt.call(repl, '');
|
2017-09-01 18:52:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function _turnOffEditorMode(repl) {
|
|
|
|
repl.editorMode = false;
|
|
|
|
repl.setPrompt(repl._initialPrompt);
|
|
|
|
}
|
|
|
|
|
2010-10-13 16:51:53 +02:00
|
|
|
function defineDefaultCommands(repl) {
|
|
|
|
repl.defineCommand('break', {
|
|
|
|
help: 'Sometimes you get stuck, this gets you out',
|
|
|
|
action: function() {
|
2017-06-14 21:52:15 +02:00
|
|
|
this.clearBufferedCommand();
|
2010-10-13 16:51:53 +02:00
|
|
|
this.displayPrompt();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-09-16 13:09:26 +02:00
|
|
|
let clearMessage;
|
2011-11-08 01:10:21 +01:00
|
|
|
if (repl.useGlobal) {
|
|
|
|
clearMessage = 'Alias for .break';
|
|
|
|
} else {
|
|
|
|
clearMessage = 'Break, and also clear the local context';
|
|
|
|
}
|
2011-10-20 17:54:50 +02:00
|
|
|
repl.defineCommand('clear', {
|
2011-11-08 01:10:21 +01:00
|
|
|
help: clearMessage,
|
2011-10-20 17:54:50 +02:00
|
|
|
action: function() {
|
2017-06-14 21:52:15 +02:00
|
|
|
this.clearBufferedCommand();
|
2011-11-08 01:10:21 +01:00
|
|
|
if (!this.useGlobal) {
|
2016-08-17 16:47:13 +02:00
|
|
|
this.outputStream.write('Clearing context...\n');
|
2012-10-13 01:34:36 +02:00
|
|
|
this.resetContext();
|
2011-11-08 01:10:21 +01:00
|
|
|
}
|
2011-10-20 17:54:50 +02:00
|
|
|
this.displayPrompt();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2010-10-13 16:51:53 +02:00
|
|
|
repl.defineCommand('exit', {
|
|
|
|
help: 'Exit the repl',
|
|
|
|
action: function() {
|
2014-02-15 15:21:26 +01:00
|
|
|
this.close();
|
2010-10-13 16:51:53 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
repl.defineCommand('help', {
|
2016-09-13 21:46:57 +02:00
|
|
|
help: 'Print this help message',
|
2010-10-13 16:51:53 +02:00
|
|
|
action: function() {
|
2019-11-22 18:04:46 +01:00
|
|
|
const names = ObjectKeys(this.commands).sort();
|
2017-01-12 21:52:20 +01:00
|
|
|
const longestNameLength = names.reduce(
|
2019-11-22 18:04:46 +01:00
|
|
|
(max, name) => MathMax(max, name.length),
|
2017-01-12 21:52:20 +01:00
|
|
|
0
|
|
|
|
);
|
2019-09-16 13:09:26 +02:00
|
|
|
for (let n = 0; n < names.length; n++) {
|
|
|
|
const name = names[n];
|
|
|
|
const cmd = this.commands[name];
|
|
|
|
const spaces = ' '.repeat(longestNameLength - name.length + 3);
|
|
|
|
const line = `.${name}${cmd.help ? spaces + cmd.help : ''}\n`;
|
2016-09-13 21:46:57 +02:00
|
|
|
this.outputStream.write(line);
|
2017-02-27 18:28:15 +01:00
|
|
|
}
|
2019-02-21 15:32:08 +01:00
|
|
|
this.outputStream.write('\nPress ^C to abort current expression, ' +
|
|
|
|
'^D to exit the repl\n');
|
2010-10-13 16:51:53 +02:00
|
|
|
this.displayPrompt();
|
|
|
|
}
|
|
|
|
});
|
2011-11-12 02:44:39 +01:00
|
|
|
|
|
|
|
repl.defineCommand('save', {
|
|
|
|
help: 'Save all evaluated commands in this REPL session to a file',
|
|
|
|
action: function(file) {
|
|
|
|
try {
|
2019-07-09 13:36:57 +02:00
|
|
|
fs.writeFileSync(file, this.lines.join('\n'));
|
|
|
|
this.outputStream.write(`Session saved to: ${file}\n`);
|
2018-11-04 16:07:28 +01:00
|
|
|
} catch {
|
2019-07-09 13:36:57 +02:00
|
|
|
this.outputStream.write(`Failed to save: ${file}\n`);
|
2011-11-12 02:44:39 +01:00
|
|
|
}
|
|
|
|
this.displayPrompt();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
repl.defineCommand('load', {
|
|
|
|
help: 'Load JS from a file into the REPL session',
|
|
|
|
action: function(file) {
|
|
|
|
try {
|
2019-07-09 13:36:57 +02:00
|
|
|
const stats = fs.statSync(file);
|
2011-11-12 02:44:39 +01:00
|
|
|
if (stats && stats.isFile()) {
|
2017-09-01 18:52:18 +02:00
|
|
|
_turnOnEditorMode(this);
|
2019-07-09 13:36:57 +02:00
|
|
|
const data = fs.readFileSync(file, 'utf8');
|
|
|
|
this.write(data);
|
2017-09-01 18:52:18 +02:00
|
|
|
_turnOffEditorMode(this);
|
2017-08-16 17:31:57 +02:00
|
|
|
this.write('\n');
|
2015-12-06 10:16:52 +01:00
|
|
|
} else {
|
2019-07-09 13:36:57 +02:00
|
|
|
this.outputStream.write(
|
|
|
|
`Failed to load: ${file} is not a valid file\n`
|
|
|
|
);
|
2011-11-12 02:44:39 +01:00
|
|
|
}
|
2018-10-12 18:20:38 +02:00
|
|
|
} catch {
|
2019-07-09 13:36:57 +02:00
|
|
|
this.outputStream.write(`Failed to load: ${file}\n`);
|
2011-11-12 02:44:39 +01:00
|
|
|
}
|
|
|
|
this.displayPrompt();
|
|
|
|
}
|
|
|
|
});
|
2019-02-21 15:33:46 +01:00
|
|
|
if (repl.terminal) {
|
|
|
|
repl.defineCommand('editor', {
|
|
|
|
help: 'Enter editor mode',
|
|
|
|
action() {
|
|
|
|
_turnOnEditorMode(this);
|
|
|
|
this.outputStream.write(
|
|
|
|
'// Entering editor mode (^D to finish, ^C to cancel)\n');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2010-10-13 16:51:53 +02:00
|
|
|
}
|
|
|
|
|
2014-06-07 08:00:55 +02:00
|
|
|
function Recoverable(err) {
|
|
|
|
this.err = err;
|
2012-10-06 03:33:28 +02:00
|
|
|
}
|
2019-11-22 18:04:46 +01:00
|
|
|
ObjectSetPrototypeOf(Recoverable.prototype, SyntaxError.prototype);
|
|
|
|
ObjectSetPrototypeOf(Recoverable, SyntaxError);
|
2015-10-22 18:38:40 +02:00
|
|
|
exports.Recoverable = Recoverable;
|