Files
coffeescript/src/repl.coffee
Jeremy Ashkenas 56413ba3b4 Merge pull request #2817 from epidemian/keep-repl-running
Keep REPL running on runtime errors
2013-03-13 01:56:33 -07:00

92 lines
2.9 KiB
CoffeeScript

vm = require 'vm'
nodeREPL = require 'repl'
CoffeeScript = require './coffee-script'
{merge, prettyErrorMessage} = require './helpers'
replDefaults =
prompt: 'coffee> ',
eval: (input, context, filename, cb) ->
# XXX: multiline hack.
input = input.replace /\uFF00/g, '\n'
# Node's REPL sends the input ending with a newline and then wrapped in
# parens. Unwrap all that.
input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'
# Require AST nodes to do some AST manipulation.
{Block, Assign, Value, Literal} = require './nodes'
try
# Generate the AST of the clean input.
ast = CoffeeScript.nodes input
# Add assignment to `_` variable to force the input to be an expression.
ast = new Block [
new Assign (new Value new Literal '_'), ast, '='
]
js = ast.compile bare: yes, locals: Object.keys(context)
cb null, vm.runInContext(js, context, filename)
catch err
cb prettyErrorMessage(err, filename, input, yes)
addMultilineHandler = (repl) ->
{rli, inputStream, outputStream} = repl
multiline =
enabled: off
initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-')
prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.')
buffer: ''
# Proxy node's line listener
nodeLineListener = rli.listeners('line')[0]
rli.removeListener 'line', nodeLineListener
rli.on 'line', (cmd) ->
if multiline.enabled
multiline.buffer += "#{cmd}\n"
rli.setPrompt multiline.prompt
rli.prompt true
else
nodeLineListener cmd
return
# Handle Ctrl-v
inputStream.on 'keypress', (char, key) ->
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
if multiline.enabled
# allow arbitrarily switching between modes any time before multiple lines are entered
unless multiline.buffer.match /\n/
multiline.enabled = not multiline.enabled
rli.setPrompt repl.prompt
rli.prompt true
return
# no-op unless the current line is empty
return if rli.line? and not rli.line.match /^\s*$/
# eval, print, loop
multiline.enabled = not multiline.enabled
rli.line = ''
rli.cursor = 0
rli.output.cursorTo 0
rli.output.clearLine 1
# XXX: multiline hack
multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
rli.emit 'line', multiline.buffer
multiline.buffer = ''
else
multiline.enabled = not multiline.enabled
rli.setPrompt multiline.initialPrompt
rli.prompt true
return
module.exports =
start: (opts = {}) ->
[major, minor, build] = process.versions.node.split('.').map (n) -> parseInt(n)
if major is 0 and minor < 8
console.warn "Node 0.8.0+ required for CoffeeScript REPL"
process.exit 1
opts = merge replDefaults, opts
repl = nodeREPL.start opts
repl.on 'exit', -> repl.outputStream.write '\n'
addMultilineHandler repl
repl