PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/repl.js

https://gitlab.com/GeekSir/node
JavaScript | 960 lines | 763 code | 85 blank | 112 comment | 78 complexity | 4581c63fbb6894b26bbd9196ae624461 MD5 | raw file
Possible License(s): 0BSD, Apache-2.0, MPL-2.0-no-copyleft-exception, JSON, WTFPL, CC-BY-SA-3.0, Unlicense, ISC, BSD-3-Clause, MIT, AGPL-3.0
  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. 'use strict';
  22. /* A repl library that you can include in your own code to get a runtime
  23. * interface to your program.
  24. *
  25. * var 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. var util = require('util');
  43. var inherits = require('util').inherits;
  44. var Stream = require('stream');
  45. var vm = require('vm');
  46. var path = require('path');
  47. var fs = require('fs');
  48. var rl = require('readline');
  49. var Console = require('console').Console;
  50. var domain = require('domain');
  51. var debug = util.debuglog('repl');
  52. // If obj.hasOwnProperty has been overridden, then calling
  53. // obj.hasOwnProperty(prop) will break.
  54. // See: https://github.com/joyent/node/issues/1707
  55. function hasOwnProperty(obj, prop) {
  56. return Object.prototype.hasOwnProperty.call(obj, prop);
  57. }
  58. // hack for require.resolve("./relative") to work properly.
  59. module.filename = path.resolve('repl');
  60. // hack for repl require to work properly with node_modules folders
  61. module.paths = require('module')._nodeModulePaths(module.filename);
  62. // Can overridden with custom print functions, such as `probe` or `eyes.js`.
  63. // This is the default "writer" value if none is passed in the REPL options.
  64. exports.writer = util.inspect;
  65. exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
  66. 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
  67. 'os', 'path', 'punycode', 'querystring', 'readline', 'stream',
  68. 'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib', 'smalloc'];
  69. function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
  70. if (!(this instanceof REPLServer)) {
  71. return new REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined);
  72. }
  73. var options, input, output, dom;
  74. if (util.isObject(prompt)) {
  75. // an options object was given
  76. options = prompt;
  77. stream = options.stream || options.socket;
  78. input = options.input;
  79. output = options.output;
  80. eval_ = options.eval;
  81. useGlobal = options.useGlobal;
  82. ignoreUndefined = options.ignoreUndefined;
  83. prompt = options.prompt;
  84. dom = options.domain;
  85. } else if (!util.isString(prompt)) {
  86. throw new Error('An options Object, or a prompt String are required');
  87. } else {
  88. options = {};
  89. }
  90. var self = this;
  91. self._domain = dom || domain.create();
  92. self.useGlobal = !!useGlobal;
  93. self.ignoreUndefined = !!ignoreUndefined;
  94. // just for backwards compat, see github.com/joyent/node/pull/7127
  95. self.rli = this;
  96. eval_ = eval_ || defaultEval;
  97. function defaultEval(code, context, file, cb) {
  98. var err, result;
  99. // first, create the Script object to check the syntax
  100. try {
  101. var script = vm.createScript(code, {
  102. filename: file,
  103. displayErrors: false
  104. });
  105. } catch (e) {
  106. debug('parse error %j', code, e);
  107. if (isRecoverableError(e))
  108. err = new Recoverable(e);
  109. else
  110. err = e;
  111. }
  112. if (!err) {
  113. try {
  114. if (self.useGlobal) {
  115. result = script.runInThisContext({ displayErrors: false });
  116. } else {
  117. result = script.runInContext(context, { displayErrors: false });
  118. }
  119. } catch (e) {
  120. err = e;
  121. if (err && process.domain) {
  122. debug('not recoverable, send to domain');
  123. process.domain.emit('error', err);
  124. process.domain.exit();
  125. return;
  126. }
  127. }
  128. }
  129. cb(err, result);
  130. }
  131. self.eval = self._domain.bind(eval_);
  132. self._domain.on('error', function(e) {
  133. debug('domain error');
  134. self.outputStream.write((e.stack || e) + '\n');
  135. self.bufferedCommand = '';
  136. self.lines.level = [];
  137. self.displayPrompt();
  138. });
  139. if (!input && !output) {
  140. // legacy API, passing a 'stream'/'socket' option
  141. if (!stream) {
  142. // use stdin and stdout as the default streams if none were given
  143. stream = process;
  144. }
  145. if (stream.stdin && stream.stdout) {
  146. // We're given custom object with 2 streams, or the `process` object
  147. input = stream.stdin;
  148. output = stream.stdout;
  149. } else {
  150. // We're given a duplex readable/writable Stream, like a `net.Socket`
  151. input = stream;
  152. output = stream;
  153. }
  154. }
  155. self.inputStream = input;
  156. self.outputStream = output;
  157. self.resetContext();
  158. self.bufferedCommand = '';
  159. self.lines.level = [];
  160. function complete(text, callback) {
  161. self.complete(text, callback);
  162. }
  163. rl.Interface.apply(this, [
  164. self.inputStream,
  165. self.outputStream,
  166. complete,
  167. options.terminal
  168. ]);
  169. self.setPrompt(!util.isUndefined(prompt) ? prompt : '> ');
  170. this.commands = {};
  171. defineDefaultCommands(this);
  172. // figure out which "writer" function to use
  173. self.writer = options.writer || exports.writer;
  174. if (util.isUndefined(options.useColors)) {
  175. options.useColors = self.terminal;
  176. }
  177. self.useColors = !!options.useColors;
  178. if (self.useColors && self.writer === util.inspect) {
  179. // Turn on ANSI coloring.
  180. self.writer = function(obj, showHidden, depth) {
  181. return util.inspect(obj, showHidden, depth, true);
  182. };
  183. }
  184. self.setPrompt(self._prompt);
  185. self.on('close', function() {
  186. self.emit('exit');
  187. });
  188. var sawSIGINT = false;
  189. self.on('SIGINT', function() {
  190. var empty = self.line.length === 0;
  191. self.clearLine();
  192. if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) {
  193. if (sawSIGINT) {
  194. self.close();
  195. sawSIGINT = false;
  196. return;
  197. }
  198. self.output.write('(^C again to quit)\n');
  199. sawSIGINT = true;
  200. } else {
  201. sawSIGINT = false;
  202. }
  203. self.bufferedCommand = '';
  204. self.lines.level = [];
  205. self.displayPrompt();
  206. });
  207. self.on('line', function(cmd) {
  208. debug('line %j', cmd);
  209. sawSIGINT = false;
  210. var skipCatchall = false;
  211. cmd = trimWhitespace(cmd);
  212. // Check to see if a REPL keyword was used. If it returns true,
  213. // display next prompt and return.
  214. if (cmd && cmd.charAt(0) === '.' && isNaN(parseFloat(cmd))) {
  215. var matches = cmd.match(/^\.([^\s]+)\s*(.*)$/);
  216. var keyword = matches && matches[1];
  217. var rest = matches && matches[2];
  218. if (self.parseREPLKeyword(keyword, rest) === true) {
  219. return;
  220. } else {
  221. self.outputStream.write('Invalid REPL keyword\n');
  222. skipCatchall = true;
  223. }
  224. }
  225. if (!skipCatchall) {
  226. var evalCmd = self.bufferedCommand + cmd;
  227. if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
  228. // It's confusing for `{ a : 1 }` to be interpreted as a block
  229. // statement rather than an object literal. So, we first try
  230. // to wrap it in parentheses, so that it will be interpreted as
  231. // an expression.
  232. evalCmd = '(' + evalCmd + ')\n';
  233. } else {
  234. // otherwise we just append a \n so that it will be either
  235. // terminated, or continued onto the next expression if it's an
  236. // unexpected end of input.
  237. evalCmd = evalCmd + '\n';
  238. }
  239. debug('eval %j', evalCmd);
  240. self.eval(evalCmd, self.context, 'repl', finish);
  241. } else {
  242. finish(null);
  243. }
  244. function finish(e, ret) {
  245. debug('finish', e, ret);
  246. self.memory(cmd);
  247. if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) {
  248. self.outputStream.write('npm should be run outside of the ' +
  249. 'node repl, in your normal shell.\n' +
  250. '(Press Control-D to exit.)\n');
  251. self.bufferedCommand = '';
  252. self.displayPrompt();
  253. return;
  254. }
  255. // If error was SyntaxError and not JSON.parse error
  256. if (e) {
  257. if (e instanceof Recoverable) {
  258. // Start buffering data like that:
  259. // {
  260. // ... x: 1
  261. // ... }
  262. self.bufferedCommand += cmd + '\n';
  263. self.displayPrompt();
  264. return;
  265. } else {
  266. self._domain.emit('error', e);
  267. }
  268. }
  269. // Clear buffer if no SyntaxErrors
  270. self.bufferedCommand = '';
  271. // If we got any output - print it (if no error)
  272. if (!e && (!self.ignoreUndefined || !util.isUndefined(ret))) {
  273. self.context._ = ret;
  274. self.outputStream.write(self.writer(ret) + '\n');
  275. }
  276. // Display prompt again
  277. self.displayPrompt();
  278. };
  279. });
  280. self.on('SIGCONT', function() {
  281. self.displayPrompt(true);
  282. });
  283. self.displayPrompt();
  284. }
  285. inherits(REPLServer, rl.Interface);
  286. exports.REPLServer = REPLServer;
  287. // prompt is a string to print on each line for the prompt,
  288. // source is a stream to use for I/O, defaulting to stdin/stdout.
  289. exports.start = function(prompt, source, eval_, useGlobal, ignoreUndefined) {
  290. var repl = new REPLServer(prompt, source, eval_, useGlobal, ignoreUndefined);
  291. if (!exports.repl) exports.repl = repl;
  292. return repl;
  293. };
  294. REPLServer.prototype.createContext = function() {
  295. var context;
  296. if (this.useGlobal) {
  297. context = global;
  298. } else {
  299. context = vm.createContext();
  300. for (var i in global) context[i] = global[i];
  301. context.console = new Console(this.outputStream);
  302. context.global = context;
  303. context.global.global = context;
  304. }
  305. context.module = module;
  306. context.require = require;
  307. this.lines = [];
  308. this.lines.level = [];
  309. // make built-in modules available directly
  310. // (loaded lazily)
  311. exports._builtinLibs.forEach(function(name) {
  312. Object.defineProperty(context, name, {
  313. get: function() {
  314. var lib = require(name);
  315. context._ = context[name] = lib;
  316. return lib;
  317. },
  318. // allow the creation of other globals with this name
  319. set: function(val) {
  320. delete context[name];
  321. context[name] = val;
  322. },
  323. configurable: true
  324. });
  325. });
  326. return context;
  327. };
  328. REPLServer.prototype.resetContext = function() {
  329. this.context = this.createContext();
  330. // Allow REPL extensions to extend the new context
  331. this.emit('reset', this.context);
  332. };
  333. REPLServer.prototype.displayPrompt = function(preserveCursor) {
  334. var prompt = this._initialPrompt;
  335. if (this.bufferedCommand.length) {
  336. prompt = '...';
  337. var levelInd = new Array(this.lines.level.length).join('..');
  338. prompt += levelInd + ' ';
  339. }
  340. // Do not overwrite `_initialPrompt` here
  341. REPLServer.super_.prototype.setPrompt.call(this, prompt);
  342. this.prompt(preserveCursor);
  343. };
  344. // When invoked as an API method, overwrite _initialPrompt
  345. REPLServer.prototype.setPrompt = function setPrompt(prompt) {
  346. this._initialPrompt = prompt;
  347. REPLServer.super_.prototype.setPrompt.call(this, prompt);
  348. };
  349. // A stream to push an array into a REPL
  350. // used in REPLServer.complete
  351. function ArrayStream() {
  352. Stream.call(this);
  353. this.run = function(data) {
  354. var self = this;
  355. data.forEach(function(line) {
  356. self.emit('data', line + '\n');
  357. });
  358. }
  359. }
  360. util.inherits(ArrayStream, Stream);
  361. ArrayStream.prototype.readable = true;
  362. ArrayStream.prototype.writable = true;
  363. ArrayStream.prototype.resume = function() {};
  364. ArrayStream.prototype.write = function() {};
  365. var requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
  366. var simpleExpressionRE =
  367. /(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;
  368. // Provide a list of completions for the given leading text. This is
  369. // given to the readline interface for handling tab completion.
  370. //
  371. // Example:
  372. // complete('var foo = util.')
  373. // -> [['util.print', 'util.debug', 'util.log', 'util.inspect', 'util.pump'],
  374. // 'util.' ]
  375. //
  376. // Warning: This eval's code like "foo.bar.baz", so it will run property
  377. // getter code.
  378. REPLServer.prototype.complete = function(line, callback) {
  379. // There may be local variables to evaluate, try a nested REPL
  380. if (!util.isUndefined(this.bufferedCommand) && this.bufferedCommand.length) {
  381. // Get a new array of inputed lines
  382. var tmp = this.lines.slice();
  383. // Kill off all function declarations to push all local variables into
  384. // global scope
  385. this.lines.level.forEach(function(kill) {
  386. if (kill.isFunction) {
  387. tmp[kill.line] = '';
  388. }
  389. });
  390. var flat = new ArrayStream(); // make a new "input" stream
  391. var magic = new REPLServer('', flat); // make a nested REPL
  392. magic.context = magic.createContext();
  393. flat.run(tmp); // eval the flattened code
  394. // all this is only profitable if the nested REPL
  395. // does not have a bufferedCommand
  396. if (!magic.bufferedCommand) {
  397. return magic.complete(line, callback);
  398. }
  399. }
  400. var completions;
  401. // list of completion lists, one for each inheritance "level"
  402. var completionGroups = [];
  403. var completeOn, match, filter, i, group, c;
  404. // REPL commands (e.g. ".break").
  405. var match = null;
  406. match = line.match(/^\s*(\.\w*)$/);
  407. if (match) {
  408. completionGroups.push(Object.keys(this.commands));
  409. completeOn = match[1];
  410. if (match[1].length > 1) {
  411. filter = match[1];
  412. }
  413. completionGroupsLoaded();
  414. } else if (match = line.match(requireRE)) {
  415. // require('...<Tab>')
  416. var exts = Object.keys(require.extensions);
  417. var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') +
  418. ')$');
  419. completeOn = match[1];
  420. var subdir = match[2] || '';
  421. var filter = match[1];
  422. var dir, files, f, name, base, ext, abs, subfiles, s;
  423. group = [];
  424. var paths = module.paths.concat(require('module').globalPaths);
  425. for (i = 0; i < paths.length; i++) {
  426. dir = path.resolve(paths[i], subdir);
  427. try {
  428. files = fs.readdirSync(dir);
  429. } catch (e) {
  430. continue;
  431. }
  432. for (f = 0; f < files.length; f++) {
  433. name = files[f];
  434. ext = path.extname(name);
  435. base = name.slice(0, -ext.length);
  436. if (base.match(/-\d+\.\d+(\.\d+)?/) || name === '.npm') {
  437. // Exclude versioned names that 'npm' installs.
  438. continue;
  439. }
  440. if (exts.indexOf(ext) !== -1) {
  441. if (!subdir || base !== 'index') {
  442. group.push(subdir + base);
  443. }
  444. } else {
  445. abs = path.resolve(dir, name);
  446. try {
  447. if (fs.statSync(abs).isDirectory()) {
  448. group.push(subdir + name + '/');
  449. subfiles = fs.readdirSync(abs);
  450. for (s = 0; s < subfiles.length; s++) {
  451. if (indexRe.test(subfiles[s])) {
  452. group.push(subdir + name);
  453. }
  454. }
  455. }
  456. } catch (e) {}
  457. }
  458. }
  459. }
  460. if (group.length) {
  461. completionGroups.push(group);
  462. }
  463. if (!subdir) {
  464. completionGroups.push(exports._builtinLibs);
  465. }
  466. completionGroupsLoaded();
  467. // Handle variable member lookup.
  468. // We support simple chained expressions like the following (no function
  469. // calls, etc.). That is for simplicity and also because we *eval* that
  470. // leading expression so for safety (see WARNING above) don't want to
  471. // eval function calls.
  472. //
  473. // foo.bar<|> # completions for 'foo' with filter 'bar'
  474. // spam.eggs.<|> # completions for 'spam.eggs' with filter ''
  475. // foo<|> # all scope vars with filter 'foo'
  476. // foo.<|> # completions for 'foo' with filter ''
  477. } else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) {
  478. match = simpleExpressionRE.exec(line);
  479. if (line.length === 0 || match) {
  480. var expr;
  481. completeOn = (match ? match[0] : '');
  482. if (line.length === 0) {
  483. filter = '';
  484. expr = '';
  485. } else if (line[line.length - 1] === '.') {
  486. filter = '';
  487. expr = match[0].slice(0, match[0].length - 1);
  488. } else {
  489. var bits = match[0].split('.');
  490. filter = bits.pop();
  491. expr = bits.join('.');
  492. }
  493. // Resolve expr and get its completions.
  494. var memberGroups = [];
  495. if (!expr) {
  496. // If context is instance of vm.ScriptContext
  497. // Get global vars synchronously
  498. if (this.useGlobal ||
  499. this.context.constructor &&
  500. this.context.constructor.name === 'Context') {
  501. var contextProto = this.context;
  502. while (contextProto = Object.getPrototypeOf(contextProto)) {
  503. completionGroups.push(Object.getOwnPropertyNames(contextProto));
  504. }
  505. completionGroups.push(Object.getOwnPropertyNames(this.context));
  506. addStandardGlobals(completionGroups, filter);
  507. completionGroupsLoaded();
  508. } else {
  509. this.eval('.scope', this.context, 'repl', function(err, globals) {
  510. if (err || !globals) {
  511. addStandardGlobals(completionGroups, filter);
  512. } else if (util.isArray(globals[0])) {
  513. // Add grouped globals
  514. globals.forEach(function(group) {
  515. completionGroups.push(group);
  516. });
  517. } else {
  518. completionGroups.push(globals);
  519. addStandardGlobals(completionGroups, filter);
  520. }
  521. completionGroupsLoaded();
  522. });
  523. }
  524. } else {
  525. this.eval(expr, this.context, 'repl', function(e, obj) {
  526. // if (e) console.log(e);
  527. if (obj != null) {
  528. if (util.isObject(obj) || util.isFunction(obj)) {
  529. memberGroups.push(Object.getOwnPropertyNames(obj));
  530. }
  531. // works for non-objects
  532. try {
  533. var sentinel = 5;
  534. var p;
  535. if (util.isObject(obj) || util.isFunction(obj)) {
  536. p = Object.getPrototypeOf(obj);
  537. } else {
  538. p = obj.constructor ? obj.constructor.prototype : null;
  539. }
  540. while (!util.isNull(p)) {
  541. memberGroups.push(Object.getOwnPropertyNames(p));
  542. p = Object.getPrototypeOf(p);
  543. // Circular refs possible? Let's guard against that.
  544. sentinel--;
  545. if (sentinel <= 0) {
  546. break;
  547. }
  548. }
  549. } catch (e) {
  550. //console.log("completion error walking prototype chain:" + e);
  551. }
  552. }
  553. if (memberGroups.length) {
  554. for (i = 0; i < memberGroups.length; i++) {
  555. completionGroups.push(memberGroups[i].map(function(member) {
  556. return expr + '.' + member;
  557. }));
  558. }
  559. if (filter) {
  560. filter = expr + '.' + filter;
  561. }
  562. }
  563. completionGroupsLoaded();
  564. });
  565. }
  566. } else {
  567. completionGroupsLoaded();
  568. }
  569. } else {
  570. completionGroupsLoaded();
  571. }
  572. // Will be called when all completionGroups are in place
  573. // Useful for async autocompletion
  574. function completionGroupsLoaded(err) {
  575. if (err) throw err;
  576. // Filter, sort (within each group), uniq and merge the completion groups.
  577. if (completionGroups.length && filter) {
  578. var newCompletionGroups = [];
  579. for (i = 0; i < completionGroups.length; i++) {
  580. group = completionGroups[i].filter(function(elem) {
  581. return elem.indexOf(filter) == 0;
  582. });
  583. if (group.length) {
  584. newCompletionGroups.push(group);
  585. }
  586. }
  587. completionGroups = newCompletionGroups;
  588. }
  589. if (completionGroups.length) {
  590. var uniq = {}; // unique completions across all groups
  591. completions = [];
  592. // Completion group 0 is the "closest"
  593. // (least far up the inheritance chain)
  594. // so we put its completions last: to be closest in the REPL.
  595. for (i = completionGroups.length - 1; i >= 0; i--) {
  596. group = completionGroups[i];
  597. group.sort();
  598. for (var j = 0; j < group.length; j++) {
  599. c = group[j];
  600. if (!hasOwnProperty(uniq, c)) {
  601. completions.push(c);
  602. uniq[c] = true;
  603. }
  604. }
  605. completions.push(''); // separator btwn groups
  606. }
  607. while (completions.length && completions[completions.length - 1] === '') {
  608. completions.pop();
  609. }
  610. }
  611. callback(null, [completions || [], completeOn]);
  612. }
  613. };
  614. /**
  615. * Used to parse and execute the Node REPL commands.
  616. *
  617. * @param {keyword} keyword The command entered to check.
  618. * @return {Boolean} If true it means don't continue parsing the command.
  619. */
  620. REPLServer.prototype.parseREPLKeyword = function(keyword, rest) {
  621. var cmd = this.commands[keyword];
  622. if (cmd) {
  623. cmd.action.call(this, rest);
  624. return true;
  625. }
  626. return false;
  627. };
  628. REPLServer.prototype.defineCommand = function(keyword, cmd) {
  629. if (util.isFunction(cmd)) {
  630. cmd = {action: cmd};
  631. } else if (!util.isFunction(cmd.action)) {
  632. throw new Error('bad argument, action must be a function');
  633. }
  634. this.commands[keyword] = cmd;
  635. };
  636. REPLServer.prototype.memory = function memory(cmd) {
  637. var self = this;
  638. self.lines = self.lines || [];
  639. self.lines.level = self.lines.level || [];
  640. // save the line so I can do magic later
  641. if (cmd) {
  642. // TODO should I tab the level?
  643. self.lines.push(new Array(self.lines.level.length).join(' ') + cmd);
  644. } else {
  645. // I don't want to not change the format too much...
  646. self.lines.push('');
  647. }
  648. // I need to know "depth."
  649. // Because I can not tell the difference between a } that
  650. // closes an object literal and a } that closes a function
  651. if (cmd) {
  652. // going down is { and ( e.g. function() {
  653. // going up is } and )
  654. var dw = cmd.match(/{|\(/g);
  655. var up = cmd.match(/}|\)/g);
  656. up = up ? up.length : 0;
  657. dw = dw ? dw.length : 0;
  658. var depth = dw - up;
  659. if (depth) {
  660. (function workIt() {
  661. if (depth > 0) {
  662. // going... down.
  663. // push the line#, depth count, and if the line is a function.
  664. // Since JS only has functional scope I only need to remove
  665. // "function() {" lines, clearly this will not work for
  666. // "function()
  667. // {" but nothing should break, only tab completion for local
  668. // scope will not work for this function.
  669. self.lines.level.push({
  670. line: self.lines.length - 1,
  671. depth: depth,
  672. isFunction: /\s*function\s*/.test(cmd)
  673. });
  674. } else if (depth < 0) {
  675. // going... up.
  676. var curr = self.lines.level.pop();
  677. if (curr) {
  678. var tmp = curr.depth + depth;
  679. if (tmp < 0) {
  680. //more to go, recurse
  681. depth += curr.depth;
  682. workIt();
  683. } else if (tmp > 0) {
  684. //remove and push back
  685. curr.depth += depth;
  686. self.lines.level.push(curr);
  687. }
  688. }
  689. }
  690. }());
  691. }
  692. // it is possible to determine a syntax error at this point.
  693. // if the REPL still has a bufferedCommand and
  694. // self.lines.level.length === 0
  695. // TODO? keep a log of level so that any syntax breaking lines can
  696. // be cleared on .break and in the case of a syntax error?
  697. // TODO? if a log was kept, then I could clear the bufferedComand and
  698. // eval these lines and throw the syntax error
  699. } else {
  700. self.lines.level = [];
  701. }
  702. };
  703. function addStandardGlobals(completionGroups, filter) {
  704. // Global object properties
  705. // (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
  706. completionGroups.push(['NaN', 'Infinity', 'undefined',
  707. 'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
  708. 'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
  709. 'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
  710. 'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
  711. 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
  712. 'Math', 'JSON']);
  713. // Common keywords. Exclude for completion on the empty string, b/c
  714. // they just get in the way.
  715. if (filter) {
  716. completionGroups.push(['break', 'case', 'catch', 'const',
  717. 'continue', 'debugger', 'default', 'delete', 'do', 'else',
  718. 'export', 'false', 'finally', 'for', 'function', 'if',
  719. 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return',
  720. 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'undefined',
  721. 'var', 'void', 'while', 'with', 'yield']);
  722. }
  723. }
  724. function defineDefaultCommands(repl) {
  725. // TODO remove me after 0.3.x
  726. repl.defineCommand('break', {
  727. help: 'Sometimes you get stuck, this gets you out',
  728. action: function() {
  729. this.bufferedCommand = '';
  730. this.displayPrompt();
  731. }
  732. });
  733. var clearMessage;
  734. if (repl.useGlobal) {
  735. clearMessage = 'Alias for .break';
  736. } else {
  737. clearMessage = 'Break, and also clear the local context';
  738. }
  739. repl.defineCommand('clear', {
  740. help: clearMessage,
  741. action: function() {
  742. this.bufferedCommand = '';
  743. if (!this.useGlobal) {
  744. this.outputStream.write('Clearing context...\n');
  745. this.resetContext();
  746. }
  747. this.displayPrompt();
  748. }
  749. });
  750. repl.defineCommand('exit', {
  751. help: 'Exit the repl',
  752. action: function() {
  753. this.close();
  754. }
  755. });
  756. repl.defineCommand('help', {
  757. help: 'Show repl options',
  758. action: function() {
  759. var self = this;
  760. Object.keys(this.commands).sort().forEach(function(name) {
  761. var cmd = self.commands[name];
  762. self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
  763. });
  764. this.displayPrompt();
  765. }
  766. });
  767. repl.defineCommand('save', {
  768. help: 'Save all evaluated commands in this REPL session to a file',
  769. action: function(file) {
  770. try {
  771. fs.writeFileSync(file, this.lines.join('\n') + '\n');
  772. this.outputStream.write('Session saved to:' + file + '\n');
  773. } catch (e) {
  774. this.outputStream.write('Failed to save:' + file + '\n');
  775. }
  776. this.displayPrompt();
  777. }
  778. });
  779. repl.defineCommand('load', {
  780. help: 'Load JS from a file into the REPL session',
  781. action: function(file) {
  782. try {
  783. var stats = fs.statSync(file);
  784. if (stats && stats.isFile()) {
  785. var self = this;
  786. var data = fs.readFileSync(file, 'utf8');
  787. var lines = data.split('\n');
  788. this.displayPrompt();
  789. lines.forEach(function(line) {
  790. if (line) {
  791. self.write(line + '\n');
  792. }
  793. });
  794. }
  795. } catch (e) {
  796. this.outputStream.write('Failed to load:' + file + '\n');
  797. }
  798. this.displayPrompt();
  799. }
  800. });
  801. }
  802. function trimWhitespace(cmd) {
  803. var trimmer = /^\s*(.+)\s*$/m,
  804. matches = trimmer.exec(cmd);
  805. if (matches && matches.length === 2) {
  806. return matches[1];
  807. }
  808. return '';
  809. }
  810. function regexpEscape(s) {
  811. return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  812. }
  813. /**
  814. * Converts commands that use var and function <name>() to use the
  815. * local exports.context when evaled. This provides a local context
  816. * on the REPL.
  817. *
  818. * @param {String} cmd The cmd to convert.
  819. * @return {String} The converted command.
  820. */
  821. REPLServer.prototype.convertToContext = function(cmd) {
  822. var self = this, matches,
  823. scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m,
  824. scopeFunc = /^\s*function\s*([_\w\$]+)/;
  825. // Replaces: var foo = "bar"; with: self.context.foo = bar;
  826. matches = scopeVar.exec(cmd);
  827. if (matches && matches.length === 3) {
  828. return 'self.context.' + matches[1] + matches[2];
  829. }
  830. // Replaces: function foo() {}; with: foo = function foo() {};
  831. matches = scopeFunc.exec(self.bufferedCommand);
  832. if (matches && matches.length === 2) {
  833. return matches[1] + ' = ' + self.bufferedCommand;
  834. }
  835. return cmd;
  836. };
  837. // If the error is that we've unexpectedly ended the input,
  838. // then let the user try to recover by adding more input.
  839. function isRecoverableError(e) {
  840. return e &&
  841. e.name === 'SyntaxError' &&
  842. /^(Unexpected end of input|Unexpected token)/.test(e.message);
  843. }
  844. function Recoverable(err) {
  845. this.err = err;
  846. }
  847. inherits(Recoverable, SyntaxError);