diff --git a/lib/repl.js b/lib/repl.js index e7c1ae9b32..151d6cafaf 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -50,6 +50,7 @@ var rl = require('readline'); var Console = require('console').Console; var EventEmitter = require('events').EventEmitter; var domain = require('domain'); +var debug = util.debuglog('repl'); // If obj.hasOwnProperty has been overridden, then calling // obj.hasOwnProperty(prop) will break. @@ -119,8 +120,9 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { }); } catch (e) { err = e; - err._isSyntaxError = isSyntaxError(err); + debug('parse error %j', code, e); } + if (!err) { try { if (self.useGlobal) { @@ -130,21 +132,22 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { } } catch (e) { err = e; - err._isSyntaxError = false; + if (err && process.domain) { + debug('not recoverable, send to domain'); + process.domain.emit('error', err); + process.domain.exit(); + return; + } } } - if (err && process.domain && !err._isSyntaxError) { - process.domain.emit('error', err); - process.domain.exit(); - } - else { - cb(err, result); - } + + cb(err, result); } self.eval = self._domain.bind(eval_); self._domain.on('error', function(e) { + debug('domain error'); self.outputStream.write((e.stack || e) + '\n'); self.bufferedCommand = ''; self.lines.level = []; @@ -236,6 +239,7 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { }); rli.on('line', function(cmd) { + debug('line %j', cmd); sawSIGINT = false; var skipCatchall = false; cmd = trimWhitespace(cmd); @@ -255,60 +259,52 @@ function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) { } if (!skipCatchall) { - var evalCmd = self.bufferedCommand + cmd + '\n'; - - // This try is for determining if the command is complete, or should - // continue onto the next line. - // We try to evaluate both expressions e.g. - // '{ a : 1 }' - // and statements e.g. - // 'for (var i = 0; i < 10; i++) console.log(i);' - - // First we attempt to eval as expression with parens. - // This catches '{a : 1}' properly. - self.eval('(' + evalCmd + ')', - self.context, - 'repl', - function(e, ret) { - if (e && !e._isSyntaxError) return finish(e); - - if (util.isFunction(ret) && - /^[\r\n\s]*function/.test(evalCmd) || e) { - // Now as statement without parens. - self.eval(evalCmd, self.context, 'repl', finish); - } else { - finish(null, ret); - } - }); + var evalCmd = self.bufferedCommand + cmd; + if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) { + // 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. + evalCmd = '(' + evalCmd + ')\n'; + } else { + // otherwise we just append a \n so that it will be either + // terminated, or continued onto the next expression if it's an + // unexpected end of input. + evalCmd = evalCmd + '\n'; + } + debug('eval %j', evalCmd); + self.eval(evalCmd, self.context, 'repl', finish); } else { finish(null); } function finish(e, ret) { - + debug('finish', e, ret); self.memory(cmd); + if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) { + self.outputStream.write('npm should be run outside of the ' + + 'node repl, in your normal shell.\n' + + '(Press Control-D to exit.)\n'); + self.bufferedCommand = ''; + self.displayPrompt(); + return; + } + // If error was SyntaxError and not JSON.parse error - if (e && e._isSyntaxError) { - if (!self.bufferedCommand && cmd.trim().match(/^npm /)) { - self.outputStream.write('npm should be run outside of the ' + - 'node repl, in your normal shell.\n' + - '(Press Control-D to exit.)\n'); - self.bufferedCommand = ''; + if (e) { + if (isRecoverableError(e)) { + // Start buffering data like that: + // { + // ... x: 1 + // ... } + self.bufferedCommand += cmd + '\n'; self.displayPrompt(); return; + } else { + self._domain.emit('error', e); } - - // Start buffering data like that: - // { - // ... x: 1 - // ... } - self.bufferedCommand += cmd + '\n'; - self.displayPrompt(); - return; - } else if (e) { - self._domain.emit('error', e); } // Clear buffer if no SyntaxErrors @@ -940,15 +936,10 @@ REPLServer.prototype.convertToContext = function(cmd) { }; -/** - * Returns `true` if "e" is a SyntaxError, `false` otherwise. - * filters out strict-mode errors, which are not recoverable - */ -function isSyntaxError(e) { - // Convert error to string - e = e && (e.stack || e.toString()); - return e && e.match(/^SyntaxError/) && - // "strict mode" syntax errors - !e.match(/^SyntaxError: .*strict mode.*/i) && - !e.match(/^SyntaxError: Assignment to constant variable/i); +// If the error is that we've unexpectedly ended the input, +// then let the user try to recover by adding more input. +function isRecoverableError(e) { + return e && + e.name === 'SyntaxError' && + /^Unexpected end of input/.test(e.message); }