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

[CS2] Support await in REPL without wrapper function (#4604)

* Support `await` in REPL without wrapper function

Rather than compiling top-level await expressions directly, which will
always throw a syntax error, the REPL wrapper will now wrap them in a
closure and handle the returned promise before calling back to the REPL.

Fixes #4603.

* Cancel REPL callback after a SIGINT during an awaited evaluation

This change allows users to 'cancel' awaited expressions that they don't
want to see the result of. Ultimately, this *does not* prevent the
expression from completing (e.g. for `await fetch(url)`, `url` will
still be downloaded, but the user won't have to wait for it to finish
and the result won't be printed).
This commit is contained in:
Chris Connelly 2017-07-24 20:47:14 +01:00 committed by Geoffrey Booth
parent 557351156e
commit 6c9cf37811
2 changed files with 46 additions and 16 deletions

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 2.0.0-beta3
(function() {
var CoffeeScript, addHistory, addMultilineHandler, fs, getCommandId, merge, nodeREPL, path, replDefaults, runInContext, updateSyntaxError, vm;
var CoffeeScript, addHistory, addMultilineHandler, fs, getCommandId, merge, nodeREPL, path, replDefaults, runInContext, sawSIGINT, updateSyntaxError, vm;
fs = require('fs');
@ -14,6 +14,8 @@
({merge, updateSyntaxError} = require('./helpers'));
sawSIGINT = false;
replDefaults = {
prompt: 'coffee> ',
historyFile: (function() {
@ -24,12 +26,12 @@
}
})(),
historyMaxInputSize: 10240,
eval: function(input, context, filename, cb) {
var Assign, Block, Literal, Value, ast, err, js, referencedVars, token, tokens;
eval: async function(input, context, filename, cb) {
var Assign, Block, Call, Code, Literal, Value, ast, err, isAsync, js, referencedVars, result, token, tokens;
input = input.replace(/\uFF00/g, '\n');
input = input.replace(/^\(([\s\S]*)\n\)$/m, '$1');
input = input.replace(/^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1');
({Block, Assign, Value, Literal} = require('./nodes'));
({Block, Assign, Value, Literal, Call, Code} = require('./nodes'));
try {
tokens = CoffeeScript.tokens(input);
referencedVars = (function() {
@ -45,12 +47,25 @@
})();
ast = CoffeeScript.nodes(tokens);
ast = new Block([new Assign(new Value(new Literal('__')), ast, '=')]);
ast = new Code([], ast);
isAsync = ast.isAsync;
ast = new Block([new Call(ast)]);
js = ast.compile({
bare: true,
locals: Object.keys(context),
referencedVars
referencedVars,
sharedScope: true
});
return cb(null, runInContext(js, context, filename));
result = runInContext(js, context, filename);
if (isAsync) {
result = (await result);
if (!sawSIGINT) {
cb(null, result);
}
return sawSIGINT = false;
} else {
return cb(null, result);
}
} catch (error) {
err = error;
updateSyntaxError(err, input);
@ -150,6 +165,9 @@
return lastLine = code;
}
});
repl.on('SIGINT', function() {
return sawSIGINT = true;
});
repl.on('exit', function() {
return fs.closeSync(fd);
});

View file

@ -5,6 +5,8 @@ nodeREPL = require 'repl'
CoffeeScript = require './'
{merge, updateSyntaxError} = require './helpers'
sawSIGINT = no
replDefaults =
prompt: 'coffee> ',
historyFile: do ->
@ -22,23 +24,31 @@ replDefaults =
input = input.replace /^\s*try\s*{([\s\S]*)}\s*catch.*$/m, '$1'
# Require AST nodes to do some AST manipulation.
{Block, Assign, Value, Literal} = require './nodes'
{Block, Assign, Value, Literal, Call, Code} = require './nodes'
try
# Tokenize the clean input.
tokens = CoffeeScript.tokens input
# Collect referenced variable names just like in `CoffeeScript.compile`.
referencedVars = (
token[1] for token in tokens when token[0] is 'IDENTIFIER'
)
referencedVars = (token[1] for token in tokens when token[0] is 'IDENTIFIER')
# Generate the AST of the tokens.
ast = CoffeeScript.nodes tokens
# 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), referencedVars}
cb null, runInContext js, context, filename
# Add assignment to `__` variable to force the input to be an expression.
ast = new Block [new Assign (new Value new Literal '__'), ast, '=']
# Wrap the expression in a closure to support top-level `await`
ast = new Code [], ast
isAsync = ast.isAsync
# Invoke the wrapping closure
ast = new Block [new Call ast]
js = ast.compile {bare: yes, locals: Object.keys(context), referencedVars, sharedScope: yes}
result = runInContext js, context, filename
# Await an async result, if necessary
if isAsync
result = await result
cb null, result unless sawSIGINT
sawSIGINT = false
else
cb null, result
catch err
# AST's `compile` does not add source code information to syntax errors.
updateSyntaxError err, input
@ -131,6 +141,8 @@ addHistory = (repl, filename, maxSize) ->
fs.writeSync fd, "#{code}\n"
lastLine = code
# XXX: The SIGINT event from REPLServer is undocumented, so this is a bit fragile
repl.on 'SIGINT', -> sawSIGINT = yes
repl.on 'exit', -> fs.closeSync fd
# Add a command to show the history stack