From 4283a96def67ffbd66e2bcf36fa8bef258556375 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sat, 24 Jan 2015 16:05:58 -0500 Subject: [PATCH 1/4] Add experimental async-assignment syntax. --- src/grammar.coffee | 12 ++++++++++-- src/nodes.coffee | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/grammar.coffee b/src/grammar.coffee index 12f8110242..412b533d72 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -38,7 +38,7 @@ o = (patternString, action, options) -> # All runtime functions we need are defined on "yy" action = action.replace /\bnew /g, '$&yy.' - action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&' + action = action.replace /\b(?:Block\.wrap|extend|awas)\b/g, 'yy.$&' # Returns a function which adds location data to the first parameter passed # in, and returns the parameter. If the parameter is not a node, it will @@ -85,6 +85,7 @@ grammar = # Block and statements, which make up a line in a body. Line: [ + o 'AwaitAssign' o 'Expression' o 'Statement' ] @@ -102,6 +103,13 @@ grammar = o 'AWAIT Expression', -> new Await Block.wrap [$2 ] ] + # ECMAScript 7-style x = await fn() + AwaitAssign: [ + o 'Assignable = AWAIT Expression', -> awas $1, $4, yylineno + o 'Assignable = TERMINATOR AWAIT Expression', -> awas $1, $5, yylineno + o 'Assignable = INDENT AWAIT Expression OUTDENT', -> awas $1, $5, yylineno + ] + # All the different types of expressions in our language. The basic unit of # CoffeeScript is the **Expression** -- everything that can be an expression # is one. Blocks serve as the building blocks of many other rules, making @@ -613,7 +621,7 @@ operators = [ ['nonassoc', 'INDENT', 'OUTDENT'] ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'] ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'] - ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'AWAIT'] + ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'AWAIT', 'WAITFOR'] ['left', 'POST_IF'] ] diff --git a/src/nodes.coffee b/src/nodes.coffee index 1e5fb82191..cb4e91d27e 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2660,6 +2660,22 @@ exports.Await = class Await extends Base super p, o @icedNodeFlag = o.foundAwaitFunc = o.foundAwait = true +#### Await.assign + +# The **Await.assign** is used to assign a local variable to value, +# or to set the property of an object -- including within object literals. +exports.awas = (variable, value, lineno) -> + # compile: + # x = await expr + # to: + # await (expr).then(defer x) + expr = new Value new Parens value + expr_then = expr.add new Access new Value new Literal "then" + call = new Call(expr_then, [ new Defer(variable, lineno) ]) + result = new Await Block.wrap [ call ] + return result + + #### IcedRuntime # # By default, the iced libraries are require'd via nodejs' require. From e722cec35c94bea2ab0008abe23504e3b4b8af00 Mon Sep 17 00:00:00 2001 From: David Bau Date: Sun, 25 Jan 2015 03:12:19 -0500 Subject: [PATCH 2/4] Handle bare await case by scanning for defer. --- src/grammar.coffee | 12 ++++++------ src/nodes.coffee | 33 +++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/grammar.coffee b/src/grammar.coffee index 412b533d72..c544b50a21 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -38,7 +38,7 @@ o = (patternString, action, options) -> # All runtime functions we need are defined on "yy" action = action.replace /\bnew /g, '$&yy.' - action = action.replace /\b(?:Block\.wrap|extend|awas)\b/g, 'yy.$&' + action = action.replace /\b(?:Block\.wrap|extend|makewait)\b/g, 'yy.$&' # Returns a function which adds location data to the first parameter passed # in, and returns the parameter. If the parameter is not a node, it will @@ -99,15 +99,15 @@ grammar = ] Await: [ - o 'AWAIT Block', -> new Await $2 - o 'AWAIT Expression', -> new Await Block.wrap [$2 ] + o 'AWAIT Block', -> makewait false, $2, yylineno + o 'AWAIT Expression', -> makewait true, $2, yylineno ] # ECMAScript 7-style x = await fn() AwaitAssign: [ - o 'Assignable = AWAIT Expression', -> awas $1, $4, yylineno - o 'Assignable = TERMINATOR AWAIT Expression', -> awas $1, $5, yylineno - o 'Assignable = INDENT AWAIT Expression OUTDENT', -> awas $1, $5, yylineno + o 'Assignable = AWAIT Expression', -> makewait $1, $4, yylineno + o 'Assignable = TERMINATOR AWAIT Expression', -> makewait $1, $5, yylineno + o 'Assignable = INDENT AWAIT Expression OUTDENT', -> makewait $1, $5, yylineno ] # All the different types of expressions in our language. The basic unit of diff --git a/src/nodes.coffee b/src/nodes.coffee index cb4e91d27e..b93b1aa04c 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2660,22 +2660,36 @@ exports.Await = class Await extends Base super p, o @icedNodeFlag = o.foundAwaitFunc = o.foundAwait = true -#### Await.assign +#### makewait # The **Await.assign** is used to assign a local variable to value, # or to set the property of an object -- including within object literals. -exports.awas = (variable, value, lineno) -> - # compile: - # x = await expr - # to: - # await (expr).then(defer x) +exports.makewait = (variable, value, lineno) -> + hasDefer = value.contains isDefer + # if this is a bare await containing a single expression (variable is true) + # then it should be treated as an iced-await if it contains 'defer'. + if variable is true and hasDefer + variable = false + value = Block.wrap [ value ] + # if this is a bare await containing a block, then it is an iced-style await. + if variable is false + result = new Await value + if not hasDefer + result.error 'await block is missing a defer statement' + return result + # if this is an await assignment, then parse it as if it uses defer notation. + # written as: x = await expr + # parses as: await (expr).then(defer x) + # TODO: consider doing this as part of the iced transformation expr = new Value new Parens value expr_then = expr.add new Access new Value new Literal "then" - call = new Call(expr_then, [ new Defer(variable, lineno) ]) + defer_args = if variable is true then [] else [variable] + call = new Call(expr_then, [ new Defer(defer_args, lineno) ]) result = new Await Block.wrap [ call ] + if hasDefer + result.error 'promise-style await must not have a defer statement' return result - #### IcedRuntime # # By default, the iced libraries are require'd via nodejs' require. @@ -3622,6 +3636,9 @@ isLiteralThis = (node) -> (node instanceof Code and node.bound) or (node instanceof Call and node.isSuper) +isDefer = (node) -> + node instanceof Defer + # Unfold a node's child if soak, then tuck the node under created `If` unfoldSoak = (o, parent, name) -> return unless ifn = parent[name].unfoldSoak o From 12fdcce4194fe89fce3149cfb692b2c09e176ca8 Mon Sep 17 00:00:00 2001 From: David Bau Date: Mon, 26 Jan 2015 13:28:21 -0500 Subject: [PATCH 3/4] Add uniced option. --- src/coffee-script.coffee | 8 ++++++-- src/command.coffee | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/coffee-script.coffee b/src/coffee-script.coffee index 3abf63483c..40073278b9 100644 --- a/src/coffee-script.coffee +++ b/src/coffee-script.coffee @@ -91,10 +91,14 @@ exports.tokens = withPrettyErrors (code, options) -> # return the AST. You can then compile it by calling `.compile()` on the root, # or traverse it by using `.traverseChildren()` with a callback. exports.nodes = withPrettyErrors (source, options) -> + console.log 'uniced', options.uniced if typeof source is 'string' - iced_transform(parser.parse(lexer.tokenize(source, options)), options) + result = parser.parse lexer.tokenize(source, options) else - iced_transform(parser.parse(source),options) + result = parser.parse source + if not options.uniced + result = iced_transform result, options + return result # Compile and execute a string of CoffeeScript (on the server), correctly # setting `__filename`, `__dirname`, and relative `require()`. diff --git a/src/command.coffee b/src/command.coffee index fd33bc3c06..2d63580e08 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -51,6 +51,7 @@ SWITCHES = [ ['-s', '--stdio', 'listen for and compile scripts over stdio'] ['-l', '--literate', 'treat stdio as literate style coffee-script'] ['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce'] + ['-u', '--uniced', 'print out the uniced parse tree that the parser produces'] ['-v', '--version', 'display the version number'] ['-w', '--watch', 'watch scripts for changes and rerun commands'] ['-I', '--runtime [WHICH]', "how to include the iced runtime, one of #{runtime_modes_str}; default is 'node'" ] @@ -168,8 +169,9 @@ compileScript = (file, input, base = null) -> CoffeeScript.emit 'compile', task if o.tokens printTokens CoffeeScript.tokens t.input, t.options - else if o.nodes - printLine CoffeeScript.nodes(t.input, t.options).toString().trim() + else if o.nodes or o.uniced + printLine CoffeeScript.nodes( + t.input, t.options, o.uniced).toString().trim() else if o.run CoffeeScript.register() CoffeeScript.run t.input, t.options @@ -403,6 +405,7 @@ compileOptions = (filename, base) -> sourceMap: opts.map runtime : opts.runtime runforce : opts.runforce + uniced: opts.uniced } if filename if base From 431a29153590285552111ddff4970da1a7b7cdb9 Mon Sep 17 00:00:00 2001 From: David Bau Date: Tue, 3 Feb 2015 12:25:41 -0500 Subject: [PATCH 4/4] Removed unused WAITFOR. --- src/grammar.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grammar.coffee b/src/grammar.coffee index c544b50a21..1e705fd080 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -621,7 +621,7 @@ operators = [ ['nonassoc', 'INDENT', 'OUTDENT'] ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'] ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'] - ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'AWAIT', 'WAITFOR'] + ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'AWAIT'] ['left', 'POST_IF'] ]