From b323b909bf26b9a4e45f663e4465f6037be39c9b Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Wed, 5 Jul 2017 18:07:02 +0100 Subject: [PATCH 1/2] 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. --- lib/coffeescript/repl.js | 19 ++++++++++++++----- src/repl.coffee | 26 ++++++++++++++++---------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/coffeescript/repl.js b/lib/coffeescript/repl.js index f027e54853..bb8fa5a529 100644 --- a/lib/coffeescript/repl.js +++ b/lib/coffeescript/repl.js @@ -24,12 +24,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 +45,21 @@ })(); 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) { + return cb(null, (await result)); + } else { + return cb(null, result); + } } catch (error) { err = error; updateSyntaxError(err, input); diff --git a/src/repl.coffee b/src/repl.coffee index aa89f617fe..62b16cc208 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -22,23 +22,29 @@ 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 + cb null, await result + else + cb null, result catch err # AST's `compile` does not add source code information to syntax errors. updateSyntaxError err, input From 1c5ed1268bdc6fc18d82f915268e45a8916157ac Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Thu, 6 Jul 2017 10:58:44 +0100 Subject: [PATCH 2/2] 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). --- lib/coffeescript/repl.js | 13 +++++++++++-- src/repl.coffee | 8 +++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/coffeescript/repl.js b/lib/coffeescript/repl.js index bb8fa5a529..6b561527c2 100644 --- a/lib/coffeescript/repl.js +++ b/lib/coffeescript/repl.js @@ -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() { @@ -56,7 +58,11 @@ }); result = runInContext(js, context, filename); if (isAsync) { - return cb(null, (await result)); + result = (await result); + if (!sawSIGINT) { + cb(null, result); + } + return sawSIGINT = false; } else { return cb(null, result); } @@ -159,6 +165,9 @@ return lastLine = code; } }); + repl.on('SIGINT', function() { + return sawSIGINT = true; + }); repl.on('exit', function() { return fs.closeSync(fd); }); diff --git a/src/repl.coffee b/src/repl.coffee index 62b16cc208..7ac2cc1ac3 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -5,6 +5,8 @@ nodeREPL = require 'repl' CoffeeScript = require './' {merge, updateSyntaxError} = require './helpers' +sawSIGINT = no + replDefaults = prompt: 'coffee> ', historyFile: do -> @@ -42,7 +44,9 @@ replDefaults = result = runInContext js, context, filename # Await an async result, if necessary if isAsync - cb null, await result + result = await result + cb null, result unless sawSIGINT + sawSIGINT = false else cb null, result catch err @@ -137,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