1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Merge branch 'node_repl_multiline' of https://github.com/asalant/coffee-script into repl-rewrite

This commit is contained in:
Michael Ficarra 2013-01-18 21:16:39 -06:00
commit 2e191dc0e7
6 changed files with 158 additions and 448 deletions

View file

@ -67,7 +67,7 @@
loadRequires(); loadRequires();
} }
if (opts.interactive) { if (opts.interactive) {
return require('./repl'); return require('./repl').start();
} }
if (opts.watch && !fs.watch) { if (opts.watch && !fs.watch) {
return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + "."); return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + ".");
@ -79,7 +79,7 @@
return compileScript(null, sources[0]); return compileScript(null, sources[0]);
} }
if (!sources.length) { if (!sources.length) {
return require('./repl'); return require('./repl').start();
} }
literals = opts.run ? sources.splice(1) : []; literals = opts.run ? sources.splice(1) : [];
process.argv = process.argv.slice(0, 2).concat(literals); process.argv = process.argv.slice(0, 2).concat(literals);

View file

@ -1,276 +1,84 @@
// Generated by CoffeeScript 1.5.0-pre // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout, var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm;
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
stdin = process.openStdin(); vm = require('vm');
stdout = process.stdout; nodeREPL = require('repl');
CoffeeScript = require('./coffee-script'); CoffeeScript = require('./coffee-script');
readline = require('readline'); merge = require('./helpers').merge;
inspect = require('util').inspect; replDefaults = {
prompt: 'coffee> ',
Script = require('vm').Script; "eval": function(code, context, file, cb) {
Module = require('module');
REPL_PROMPT = 'coffee> ';
REPL_PROMPT_MULTILINE = '------> ';
REPL_PROMPT_CONTINUATION = '......> ';
enableColours = false;
if (process.platform !== 'win32') {
enableColours = !process.env.NODE_DISABLE_COLORS;
}
error = function(err) {
return stdout.write((err.stack || err.toString()) + '\n');
};
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/;
SIMPLEVAR = /(\w+)$/i;
autocomplete = function(text) {
return completeAttribute(text) || completeVariable(text) || [[], text];
};
completeAttribute = function(text) {
var all, candidates, completions, key, match, obj, prefix, _i, _len, _ref;
if (match = text.match(ACCESSOR)) {
all = match[0], obj = match[1], prefix = match[2];
try { try {
obj = Script.runInThisContext(obj); if (/^\(\s+\)$/.test(code)) {
} catch (e) { return cb(null);
return;
} }
if (obj == null) { code = code.replace(/子/mg, '\n');
return; code = CoffeeScript.compile(code, {
} filename: file,
obj = Object(obj); bare: true
candidates = Object.getOwnPropertyNames(obj);
while (obj = Object.getPrototypeOf(obj)) {
_ref = Object.getOwnPropertyNames(obj);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
if (__indexOf.call(candidates, key) < 0) {
candidates.push(key);
}
}
}
completions = getCompletions(prefix, candidates);
return [completions, prefix];
}
};
completeVariable = function(text) {
var candidates, completions, free, key, keywords, r, vars, _i, _len, _ref;
free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0;
if (text === "") {
free = "";
}
if (free != null) {
vars = Script.runInThisContext('Object.getOwnPropertyNames(Object(this))');
keywords = (function() {
var _i, _len, _ref1, _results;
_ref1 = CoffeeScript.RESERVED;
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
r = _ref1[_i];
if (r.slice(0, 2) !== '__') {
_results.push(r);
}
}
return _results;
})();
candidates = vars;
for (_i = 0, _len = keywords.length; _i < _len; _i++) {
key = keywords[_i];
if (__indexOf.call(candidates, key) < 0) {
candidates.push(key);
}
}
completions = getCompletions(free, candidates);
return [completions, free];
}
};
getCompletions = function(prefix, candidates) {
var el, _i, _len, _results;
_results = [];
for (_i = 0, _len = candidates.length; _i < _len; _i++) {
el = candidates[_i];
if (0 === el.indexOf(prefix)) {
_results.push(el);
}
}
return _results;
};
process.on('uncaughtException', error);
backlog = '';
run = function(buffer) {
var code, returnValue, _;
buffer = buffer.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3");
buffer = buffer.replace(/[\r\n]+$/, "");
if (multilineMode) {
backlog += "" + buffer + "\n";
repl.setPrompt(REPL_PROMPT_CONTINUATION);
repl.prompt();
return;
}
if (!buffer.toString().trim() && !backlog) {
repl.prompt();
return;
}
code = backlog += buffer;
if (code[code.length - 1] === '\\') {
backlog = "" + backlog.slice(0, -1) + "\n";
repl.setPrompt(REPL_PROMPT_CONTINUATION);
repl.prompt();
return;
}
repl.setPrompt(REPL_PROMPT);
backlog = '';
try {
_ = global._;
returnValue = CoffeeScript["eval"]("_=(" + code + "\n)", {
filename: 'repl',
modulename: 'repl'
}); });
if (returnValue === void 0) { return cb(null, vm.runInContext(code, context, file));
global._ = _;
}
repl.output.write("" + (inspect(returnValue, false, 2, enableColours)) + "\n");
} catch (err) { } catch (err) {
error(err); return cb(err);
}
} }
return repl.prompt();
}; };
if (stdin.readable && stdin.isRaw) { addMultilineHandler = function(repl) {
pipedInput = ''; var inputStream, multiline, nodeLineListener, outputStream, rli;
repl = { rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream;
prompt: function() { multiline = {
return stdout.write(this._prompt); enabled: false,
}, prompt: new Array(repl.prompt.length).join('.') + ' ',
setPrompt: function(p) { buffer: ''
return this._prompt = p;
},
input: stdin,
output: stdout,
on: function() {}
}; };
stdin.on('data', function(chunk) { nodeLineListener = rli.listeners('line')[0];
var line, lines, _i, _len, _ref; rli.removeListener('line', nodeLineListener);
pipedInput += chunk; rli.on('line', function(cmd) {
if (!/\n/.test(pipedInput)) { if (multiline.enabled === true) {
return; multiline.buffer += "" + cmd + "\n";
} return rli.prompt(true);
lines = pipedInput.split("\n");
pipedInput = lines[lines.length - 1];
_ref = lines.slice(0, -1);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
if (!(line)) {
continue;
}
stdout.write("" + line + "\n");
run(line);
}
});
stdin.on('end', function() {
var line, _i, _len, _ref;
_ref = pipedInput.trim().split("\n");
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
line = _ref[_i];
if (!(line)) {
continue;
}
stdout.write("" + line + "\n");
run(line);
}
stdout.write('\n');
return process.exit(0);
});
} else { } else {
if (readline.createInterface.length < 3) { return nodeLineListener(cmd);
repl = readline.createInterface(stdin, autocomplete); }
stdin.on('data', function(buffer) {
return repl.write(buffer);
}); });
} else { return inputStream.on('keypress', function(char, key) {
repl = readline.createInterface(stdin, stdout, autocomplete);
}
}
multilineMode = false;
repl.input.on('keypress', function(char, key) {
var cursorPos, newPrompt;
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) { if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
return; return;
} }
cursorPos = repl.cursor; multiline.enabled = !multiline.enabled;
repl.output.cursorTo(0); if (multiline.enabled === false) {
repl.output.clearLine(1); if (!multiline.buffer.match(/\n/)) {
multilineMode = !multilineMode; rli.setPrompt(repl.prompt);
if (!multilineMode && backlog) { rli.prompt(true);
repl._line();
}
backlog = '';
repl.setPrompt((newPrompt = multilineMode ? REPL_PROMPT_MULTILINE : REPL_PROMPT));
repl.prompt();
return repl.output.cursorTo(newPrompt.length + (repl.cursor = cursorPos));
});
repl.input.on('keypress', function(char, key) {
if (!(multilineMode && repl.line)) {
return; return;
} }
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'd')) { multiline.buffer = multiline.buffer.replace(/\n/mg, '子');
return; rli.emit('line', multiline.buffer);
} return multiline.buffer = '';
multilineMode = false;
return repl._line();
});
repl.on('attemptClose', function() {
if (multilineMode) {
multilineMode = false;
repl.output.cursorTo(0);
repl.output.clearLine(1);
repl._onLine(repl.line);
return;
}
if (backlog || repl.line) {
backlog = '';
repl.historyIndex = -1;
repl.setPrompt(REPL_PROMPT);
repl.output.write('\n(^C again to quit)');
return repl._line((repl.line = ''));
} else { } else {
return repl.close(); rli.setPrompt(multiline.prompt);
return rli.prompt(true);
} }
}); });
};
repl.on('close', function() { module.exports = {
repl.output.write('\n'); start: function(opts) {
return repl.input.destroy(); var repl;
}); if (opts == null) {
opts = {};
repl.on('line', run); }
opts = merge(replDefaults, opts);
repl.setPrompt(REPL_PROMPT); repl = nodeREPL.start(opts);
addMultilineHandler(repl);
repl.prompt(); return repl;
}
};
}).call(this); }).call(this);

View file

@ -68,12 +68,12 @@ exports.run = ->
return usage() if opts.help return usage() if opts.help
return version() if opts.version return version() if opts.version
loadRequires() if opts.require loadRequires() if opts.require
return require './repl' if opts.interactive return require('./repl').start() if opts.interactive
if opts.watch and !fs.watch if opts.watch and !fs.watch
return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}." return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
return compileStdio() if opts.stdio return compileStdio() if opts.stdio
return compileScript null, sources[0] if opts.eval return compileScript null, sources[0] if opts.eval
return require './repl' unless sources.length return require('./repl').start() unless sources.length
literals = if opts.run then sources.splice 1 else [] literals = if opts.run then sources.splice 1 else []
process.argv = process.argv[0..1].concat literals process.argv = process.argv[0..1].concat literals
process.argv[0] = 'coffee' process.argv[0] = 'coffee'

View file

@ -1,197 +1,58 @@
# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript vm = require 'vm'
# and evaluates it. Good for simple tests, or poking around the **Node.js** API. nodeREPL = require 'repl'
# Using it looks like this:
#
# coffee> console.log "#{num} bottles of beer" for num in [99..1]
# Start by opening up `stdin` and `stdout`.
stdin = process.openStdin()
stdout = process.stdout
# Require the **coffee-script** module to get access to the compiler.
CoffeeScript = require './coffee-script' CoffeeScript = require './coffee-script'
readline = require 'readline' {merge} = require './helpers'
{inspect} = require 'util'
{Script} = require 'vm'
Module = require 'module'
# REPL Setup replDefaults =
prompt: 'coffee> ',
# Config eval: (code, context, file, cb) ->
REPL_PROMPT = 'coffee> '
REPL_PROMPT_MULTILINE = '------> '
REPL_PROMPT_CONTINUATION = '......> '
enableColours = no
unless process.platform is 'win32'
enableColours = not process.env.NODE_DISABLE_COLORS
# Log an error.
error = (err) ->
stdout.write (err.stack or err.toString()) + '\n'
## Autocompletion
# Regexes to match complete-able bits of text.
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/
SIMPLEVAR = /(\w+)$/i
# Returns a list of completions, and the completed text.
autocomplete = (text) ->
completeAttribute(text) or completeVariable(text) or [[], text]
# Attempt to autocomplete a chained dotted attribute: `one.two.three`.
completeAttribute = (text) ->
if match = text.match ACCESSOR
[all, obj, prefix] = match
try obj = Script.runInThisContext obj
catch e
return
return unless obj?
obj = Object obj
candidates = Object.getOwnPropertyNames obj
while obj = Object.getPrototypeOf obj
for key in Object.getOwnPropertyNames obj when key not in candidates
candidates.push key
completions = getCompletions prefix, candidates
[completions, prefix]
# Attempt to autocomplete an in-scope free variable: `one`.
completeVariable = (text) ->
free = text.match(SIMPLEVAR)?[1]
free = "" if text is ""
if free?
vars = Script.runInThisContext 'Object.getOwnPropertyNames(Object(this))'
keywords = (r for r in CoffeeScript.RESERVED when r[..1] isnt '__')
candidates = vars
for key in keywords when key not in candidates
candidates.push key
completions = getCompletions free, candidates
[completions, free]
# Return elements of candidates for which `prefix` is a prefix.
getCompletions = (prefix, candidates) ->
el for el in candidates when 0 is el.indexOf prefix
# Make sure that uncaught exceptions don't kill the REPL.
process.on 'uncaughtException', error
# The current backlog of multi-line code.
backlog = ''
# The main REPL function. **run** is called every time a line of code is entered.
# Attempt to evaluate the command. If there's an exception, print it out instead
# of exiting.
run = (buffer) ->
# remove single-line comments
buffer = buffer.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3"
# remove trailing newlines
buffer = buffer.replace /[\r\n]+$/, ""
if multilineMode
backlog += "#{buffer}\n"
repl.setPrompt REPL_PROMPT_CONTINUATION
repl.prompt()
return
if !buffer.toString().trim() and !backlog
repl.prompt()
return
code = backlog += buffer
if code[code.length - 1] is '\\'
backlog = "#{backlog[...-1]}\n"
repl.setPrompt REPL_PROMPT_CONTINUATION
repl.prompt()
return
repl.setPrompt REPL_PROMPT
backlog = ''
try try
_ = global._ return cb(null) if /^\(\s+\)$/.test code # Empty command
returnValue = CoffeeScript.eval "_=(#{code}\n)", { code = code.replace //mg, '\n' # Temporary hack, see TODO below
filename: 'repl' code = CoffeeScript.compile(code, {filename: file, bare: true})
modulename: 'repl' cb(null, vm.runInContext(code, context, file))
}
if returnValue is undefined
global._ = _
repl.output.write "#{inspect returnValue, no, 2, enableColours}\n"
catch err catch err
error err cb(err)
repl.prompt()
if stdin.readable and stdin.isRaw # TODO: how to test?
# handle piped input addMultilineHandler = (repl) ->
pipedInput = '' {rli, inputStream, outputStream} = repl
repl =
prompt: -> stdout.write @_prompt multiline =
setPrompt: (p) -> @_prompt = p enabled: off
input: stdin prompt: new Array(repl.prompt.length).join('.') + ' '
output: stdout buffer: ''
on: ->
stdin.on 'data', (chunk) -> # Proxy node's line listener
pipedInput += chunk nodeLineListener = rli.listeners('line')[0]
return unless /\n/.test pipedInput rli.removeListener 'line', nodeLineListener
lines = pipedInput.split "\n" rli.on 'line', (cmd) ->
pipedInput = lines[lines.length - 1] if multiline.enabled is on
for line in lines[...-1] when line multiline.buffer += "#{cmd}\n"
stdout.write "#{line}\n" rli.prompt true
run line
return
stdin.on 'end', ->
for line in pipedInput.trim().split "\n" when line
stdout.write "#{line}\n"
run line
stdout.write '\n'
process.exit 0
else else
# Create the REPL by listening to **stdin**. nodeLineListener(cmd)
if readline.createInterface.length < 3
repl = readline.createInterface stdin, autocomplete
stdin.on 'data', (buffer) -> repl.write buffer
else
repl = readline.createInterface stdin, stdout, autocomplete
multilineMode = off # Handle Ctrl-v
inputStream.on 'keypress', (char, key) ->
# Handle multi-line mode switch
repl.input.on 'keypress', (char, key) ->
# test for Ctrl-v
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v' return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
cursorPos = repl.cursor multiline.enabled = !multiline.enabled
repl.output.cursorTo 0 if multiline.enabled is off
repl.output.clearLine 1 unless multiline.buffer.match /\n/
multilineMode = not multilineMode rli.setPrompt repl.prompt
repl._line() if not multilineMode and backlog rli.prompt true
backlog = ''
repl.setPrompt (newPrompt = if multilineMode then REPL_PROMPT_MULTILINE else REPL_PROMPT)
repl.prompt()
repl.output.cursorTo newPrompt.length + (repl.cursor = cursorPos)
# Handle Ctrl-d press at end of last line in multiline mode
repl.input.on 'keypress', (char, key) ->
return unless multilineMode and repl.line
# test for Ctrl-d
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'd'
multilineMode = off
repl._line()
repl.on 'attemptClose', ->
if multilineMode
multilineMode = off
repl.output.cursorTo 0
repl.output.clearLine 1
repl._onLine repl.line
return return
if backlog or repl.line # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval?
backlog = '' multiline.buffer = multiline.buffer.replace /\n/mg, ''
repl.historyIndex = -1 rli.emit 'line', multiline.buffer
repl.setPrompt REPL_PROMPT multiline.buffer = ''
repl.output.write '\n(^C again to quit)'
repl._line (repl.line = '')
else else
repl.close() rli.setPrompt multiline.prompt
rli.prompt true
repl.on 'close', -> module.exports =
repl.output.write '\n' start: (opts = {}) ->
repl.input.destroy() opts = merge(replDefaults, opts)
repl = nodeREPL.start opts
repl.on 'line', run addMultilineHandler(repl)
repl
repl.setPrompt REPL_PROMPT
repl.prompt()

View file

@ -1,4 +1,46 @@
# REPL # REPL
# ---- # ----
# TODO: add tests # TODO: add more tests
{spawn} = require 'child_process'
PROMPT = 'coffee> '
testOutput = (expected, actual) ->
eq expected, actual.slice(0, expected.length)
actual.substr expected.length
testCommands = (input, expectedOutput) ->
input = [input] if typeof input is 'string'
expectedOutput = [expectedOutput] if typeof expectedOutput in ['string', 'undefined']
output = ''
coffee = spawn 'bin/coffee'
input.push 'process.exit()'
coffee.stdout.on 'data', (data) ->
output += data.toString().replace(/\u001b\[\d{0,2}m/g, '')
coffee.stdin.write "#{input.shift()}\n"
coffee.on 'exit', ->
output = testOutput PROMPT, output
while expectedOutput.length > 0
output = testOutput "#{expectedOutput.shift()}\n#{PROMPT}", output
eq '', output
test "comments are ignored", ->
testCommands "1 + 1 #foo", "2"
test "output in inspect mode", ->
testCommands '"1 + 1\\n"', "'1 + 1\\n'"
test "variables are saved", ->
input = [
"foo = 'foo'"
'foobar = "#{foo}bar"'
]
testCommands input, [
"'foo'"
"'foobar'"
]
test "empty command evaluates to undefined", ->
testCommands "", undefined

View file

@ -106,7 +106,6 @@
'option_parser' 'option_parser'
'ranges' 'ranges'
'regexps' 'regexps'
'repl'
'scope' 'scope'
'slicing_and_splicing' 'slicing_and_splicing'
'soaks' 'soaks'