From 4e7408dc25d17c5ffd02e71e3e6446a676ec75eb Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 10 Feb 2010 18:33:03 -0500 Subject: [PATCH] self-compiler: handles try/catch/finally blocks --- lib/coffee_script/nodes.js | 22 +++++- lib/coffee_script/nodes.rb | 142 ++++++++++++++++++------------------ lib/coffee_script/parser.js | 114 ++++++++++++++--------------- src/nodes.coffee | 20 ++++- src/parser.coffee | 105 +++++++++++++------------- 5 files changed, 220 insertions(+), 183 deletions(-) diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index 3572e2ec..bcab2c88 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -1,5 +1,5 @@ (function(){ - var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement; + var AccessorNode, ArrayNode, AssignNode, CallNode, ClosureNode, CodeNode, CommentNode, Expressions, ExtendsNode, IDENTIFIER, IndexNode, LiteralNode, Node, ObjectNode, OpNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThisNode, TryNode, ValueNode, WhileNode, any, compact, del, dup, flatten, inherit, merge, statement; var __hasProp = Object.prototype.hasOwnProperty; process.mixin(require('./scope')); // The abstract base class for all CoffeeScript nodes. @@ -1114,4 +1114,24 @@ return parts.join(''); } })); + // A try/catch/finally block. + TryNode = (exports.TryNode = inherit(Node, { + constructor: function constructor(attempt, error, recovery, ensure) { + this.children = [(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]; + this.error = error; + return this; + }, + compile_node: function compile_node(o) { + var catch_part, error_part, finally_part; + o.indent = this.idt(1); + o.top = true; + error_part = this.error ? ' (' + this.error.compile(o) + ') ' : ' '; + catch_part = (this.recovery || '') && ' catch' + error_part + '{\n' + this.recovery.compile(o) + '\n' + this.idt() + '}'; + finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o, { + returns: null + })) + '\n' + this.idt() + '}'; + return this.idt() + 'try {\n' + this.attempt.compile(o) + '\n' + this.idt() + '}' + catch_part + finally_part; + } + })); + statement(TryNode); })(); \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 7a46131c..fe591213 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -800,6 +800,77 @@ module CoffeeScript end end + # A try/catch/finally block. + class TryNode < Node + children :try, :recovery, :finally + attr_reader :error + statement + + def initialize(try, error, recovery, finally=nil) + @try, @error, @recovery, @finally = try, error, recovery, finally + end + + def compile_node(o) + o[:indent] = idt(1) + o[:top] = true + error_part = @error ? " (#{@error}) " : ' ' + catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}" + finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}" + write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}") + end + end + + # Throw an exception. + class ThrowNode < Node + children :expression + statement_only + + def initialize(expression) + @expression = expression + end + + def compile_node(o) + write("#{idt}throw #{@expression.compile(o)};") + end + end + + # Check an expression for existence (meaning not null or undefined). + class ExistenceNode < Node + children :expression + + def self.compile_test(o, variable) + first, second = variable, variable + first, second = *variable.compile_reference(o) if variable.is_a?(CallNode) + "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)" + end + + def initialize(expression) + @expression = expression + end + + def compile_node(o) + write(ExistenceNode.compile_test(o, @expression)) + end + end + + # An extra set of parentheses, supplied by the script source. + # You can't wrap parentheses around bits that get compiled into JS statements, + # unfortunately. + class ParentheticalNode < Node + children :expressions + + def initialize(expressions, line=nil) + @expressions = expressions.unwrap + @line = line + end + + def compile_node(o) + compiled = @expressions.compile(o) + compiled = compiled[0...-1] if compiled[-1..-1] == ';' + write("(#{compiled})") + end + end + # The replacement for the for loop is an array comprehension (that compiles) # into a for loop. Also acts as an expression, able to return the result # of the comprehenion. Unlike Python array comprehensions, it's able to pass @@ -873,77 +944,6 @@ module CoffeeScript end end - # A try/catch/finally block. - class TryNode < Node - children :try, :recovery, :finally - attr_reader :error - statement - - def initialize(try, error, recovery, finally=nil) - @try, @error, @recovery, @finally = try, error, recovery, finally - end - - def compile_node(o) - o[:indent] = idt(1) - o[:top] = true - error_part = @error ? " (#{@error}) " : ' ' - catch_part = @recovery && " catch#{error_part}{\n#{@recovery.compile(o)}\n#{idt}}" - finally_part = @finally && " finally {\n#{@finally.compile(o.merge(:return => nil))}\n#{idt}}" - write("#{idt}try {\n#{@try.compile(o)}\n#{idt}}#{catch_part}#{finally_part}") - end - end - - # Throw an exception. - class ThrowNode < Node - children :expression - statement_only - - def initialize(expression) - @expression = expression - end - - def compile_node(o) - write("#{idt}throw #{@expression.compile(o)};") - end - end - - # Check an expression for existence (meaning not null or undefined). - class ExistenceNode < Node - children :expression - - def self.compile_test(o, variable) - first, second = variable, variable - first, second = *variable.compile_reference(o) if variable.is_a?(CallNode) - "(typeof #{first.compile(o)} !== \"undefined\" && #{second.compile(o)} !== null)" - end - - def initialize(expression) - @expression = expression - end - - def compile_node(o) - write(ExistenceNode.compile_test(o, @expression)) - end - end - - # An extra set of parentheses, supplied by the script source. - # You can't wrap parentheses around bits that get compiled into JS statements, - # unfortunately. - class ParentheticalNode < Node - children :expressions - - def initialize(expressions, line=nil) - @expressions = expressions.unwrap - @line = line - end - - def compile_node(o) - compiled = @expressions.compile(o) - compiled = compiled[0...-1] if compiled[-1..-1] == ';' - write("(#{compiled})") - end - end - # If/else statements. Switch/whens get compiled into these. Acts as an # expression by pushing down requested returns to the expression bodies. # Single-expression IfNodes are compiled into ternary operators if possible, diff --git a/lib/coffee_script/parser.js b/lib/coffee_script/parser.js index 55343029..7ff8c3df 100644 --- a/lib/coffee_script/parser.js +++ b/lib/coffee_script/parser.js @@ -17,7 +17,7 @@ } }; // Precedence =========================================================== - operators = [["left", '?'], ["right", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']]; + operators = [["left", '?'], ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'], ["left", '*', '/', '%'], ["left", '+', '-'], ["left", '<<', '>>', '>>>'], ["left", '&', '|', '^'], ["left", '<=', '<', '>', '>='], ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'], ["right", '==', '!=', 'IS', 'ISNT'], ["left", '&&', '||', 'AND', 'OR'], ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='], ["left", '.'], ["right", 'INDENT'], ["left", 'OUTDENT'], ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'], ["right", 'THROW', 'FOR', 'NEW', 'SUPER'], ["left", 'EXTENDS'], ["right", 'ASSIGN', 'RETURN'], ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE']]; // Grammar ============================================================== grammar = { // All parsing will end in this rule, being the trunk of the AST. @@ -111,55 +111,45 @@ // https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: [o("! Expression", function() { return new OpNode('!', $2); - }), o("!! Expression", function() { - return new OpNode('!!', $2); - }), o("- Expression", (function() { + }), + // o "!! Expression", -> new OpNode('!!', $2) + o("- Expression", (function() { return new OpNode('-', $2); }), { prec: 'UMINUS' - }), o("+ Expression", (function() { - return new OpNode('+', $2); - }), { - prec: 'UPLUS' - }), o("NOT Expression", function() { + }), + // o "+ Expression", (-> new OpNode('+', $2)), {prec: 'UPLUS'} + o("NOT Expression", function() { return new OpNode('not', $2); - }), o("~ Expression", function() { - return new OpNode('~', $2); - }), o("-- Expression", function() { - return new OpNode('--', $2); - }), o("++ Expression", function() { - return new OpNode('++', $2); - }), o("DELETE Expression", function() { + }), + // o "~ Expression", -> new OpNode('~', $2) + // o "-- Expression", -> new OpNode('--', $2) + // o "++ Expression", -> new OpNode('++', $2) + o("DELETE Expression", function() { return new OpNode('delete', $2); }), o("TYPEOF Expression", function() { return new OpNode('typeof', $2); - }), o("Expression --", function() { - return new OpNode('--', $1, null, true); - }), o("Expression ++", function() { - return new OpNode('++', $1, null, true); - }), o("Expression * Expression", function() { + }), + // o "Expression --", -> new OpNode('--', $1, null, true) + // o "Expression ++", -> new OpNode('++', $1, null, true) + o("Expression * Expression", function() { return new OpNode('*', $1, $3); }), o("Expression / Expression", function() { return new OpNode('/', $1, $3); - }), o("Expression % Expression", function() { - return new OpNode('%', $1, $3); - }), o("Expression + Expression", function() { + }), + // o "Expression % Expression", -> new OpNode('%', $1, $3) + o("Expression + Expression", function() { return new OpNode('+', $1, $3); }), o("Expression - Expression", function() { return new OpNode('-', $1, $3); - }), o("Expression << Expression", function() { - return new OpNode('<<', $1, $3); - }), o("Expression >> Expression", function() { - return new OpNode('>>', $1, $3); - }), o("Expression >>> Expression", function() { - return new OpNode('>>>', $1, $3); - }), o("Expression & Expression", function() { - return new OpNode('&', $1, $3); - }), o("Expression | Expression", function() { - return new OpNode('|', $1, $3); - }), o("Expression ^ Expression", function() { - return new OpNode('^', $1, $3); - }), o("Expression <= Expression", function() { + }), + // o "Expression << Expression", -> new OpNode('<<', $1, $3) + // o "Expression >> Expression", -> new OpNode('>>', $1, $3) + // o "Expression >>> Expression", -> new OpNode('>>>', $1, $3) + // o "Expression & Expression", -> new OpNode('&', $1, $3) + // o "Expression | Expression", -> new OpNode('|', $1, $3) + // o "Expression ^ Expression", -> new OpNode('^', $1, $3) + o("Expression <= Expression", function() { return new OpNode('<=', $1, $3); }), o("Expression < Expression", function() { return new OpNode('<', $1, $3); @@ -167,30 +157,40 @@ return new OpNode('>', $1, $3); }), o("Expression >= Expression", function() { return new OpNode('>=', $1, $3); - }), o("Expression == Expression", function() { - return new OpNode('==', $1, $3); }), - // o "Expression != Expression", -> new OpNode($2, $1, $3) - // o "Expression IS Expression", -> new OpNode($2, $1, $3) - // o "Expression ISNT Expression", -> new OpNode($2, $1, $3) - // - // o "Expression && Expression", -> new OpNode($2, $1, $3) - // o "Expression || Expression", -> new OpNode($2, $1, $3) - // o "Expression AND Expression", -> new OpNode($2, $1, $3) - // o "Expression OR Expression", -> new OpNode($2, $1, $3) - // o "Expression ? Expression", -> new OpNode($2, $1, $3) - // - // o "Expression -= Expression", -> new OpNode($2, $1, $3) - // o "Expression += Expression", -> new OpNode($2, $1, $3) + // o "Expression == Expression", -> new OpNode('==', $1, $3) + // o "Expression != Expression", -> new OpNode($2, $1, $3) + o("Expression IS Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression ISNT Expression", function() { + return new OpNode($2, $1, $3); + }), + // o "Expression && Expression", -> new OpNode($2, $1, $3) + // o "Expression || Expression", -> new OpNode($2, $1, $3) + o("Expression AND Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression OR Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression ? Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression -= Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression += Expression", function() { + return new OpNode($2, $1, $3); + }), // o "Expression /= Expression", -> new OpNode($2, $1, $3) // o "Expression *= Expression", -> new OpNode($2, $1, $3) // o "Expression %= Expression", -> new OpNode($2, $1, $3) - // o "Expression ||= Expression", -> new OpNode($2, $1, $3) - // o "Expression &&= Expression", -> new OpNode($2, $1, $3) - // o "Expression ?= Expression", -> new OpNode($2, $1, $3) - // - // o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3) - // o "Expression IN Expression", -> new OpNode($2, $1, $3) + o("Expression ||= Expression", function() { + return new OpNode($2, $1, $3); + }), o("Expression &&= Expression", function() { + return new OpNode($2, $1, $3); + }), + // o "Expression ?= Expression", -> new OpNode($2, $1, $3) + o("Expression INSTANCEOF Expression", function() { + return new OpNode($2, $1, $3); + }), + // o "Expression IN Expression", -> new OpNode($2, $1, $3) ], // The existence operator. Existence: [o("Expression ?", function() { diff --git a/src/nodes.coffee b/src/nodes.coffee index 6c452430..ce97b3db 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -805,7 +805,25 @@ OpNode: exports.OpNode: inherit Node, { } - +# A try/catch/finally block. +TryNode: exports.TryNode: inherit Node, { + + constructor: (attempt, error, recovery, ensure) -> + @children: [@attempt: attempt, @recovery: recovery, @ensure: ensure] + @error: error + this + + compile_node: (o) -> + o.indent: @idt(1) + o.top: true + error_part: if @error then ' (' + @error.compile(o) + ') ' else ' ' + catch_part: (@recovery or '') and ' catch' + error_part + '{\n' + @recovery.compile(o) + '\n' + @idt() + '}' + finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + '\n' + @idt() + '}' + @idt() + 'try {\n' + @attempt.compile(o) + '\n' + @idt() + '}' + catch_part + finally_part + +} + +statement TryNode diff --git a/src/parser.coffee b/src/parser.coffee index 13f96ee6..072b85fa 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -17,25 +17,25 @@ o: (pattern_string, func, options) -> # Precedence =========================================================== operators: [ - ["left", '?'] - ["right", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'] - ["left", '*', '/', '%'] - ["left", '+', '-'] - ["left", '<<', '>>', '>>>'] - ["left", '&', '|', '^'] - ["left", '<=', '<', '>', '>='] - ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'] - ["right", '==', '!=', 'IS', 'ISNT'] - ["left", '&&', '||', 'AND', 'OR'] - ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='] - ["left", '.'] - ["right", 'INDENT'] - ["left", 'OUTDENT'] - ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'] - ["right", 'THROW', 'FOR', 'NEW', 'SUPER'] - ["left", 'EXTENDS'] - ["right", 'ASSIGN', 'RETURN'] - ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE'] + ["left", '?'] + ["nonassoc", 'UMINUS', 'UPLUS', 'NOT', '!', '!!', '~', '++', '--'] + ["left", '*', '/', '%'] + ["left", '+', '-'] + ["left", '<<', '>>', '>>>'] + ["left", '&', '|', '^'] + ["left", '<=', '<', '>', '>='] + ["right", 'DELETE', 'INSTANCEOF', 'TYPEOF'] + ["right", '==', '!=', 'IS', 'ISNT'] + ["left", '&&', '||', 'AND', 'OR'] + ["right", '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?='] + ["left", '.'] + ["right", 'INDENT'] + ["left", 'OUTDENT'] + ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY'] + ["right", 'THROW', 'FOR', 'NEW', 'SUPER'] + ["left", 'EXTENDS'] + ["right", 'ASSIGN', 'RETURN'] + ["right", '->', '=>', 'UNLESS', 'IF', 'ELSE', 'WHILE'] ] # Grammar ============================================================== @@ -138,60 +138,59 @@ grammar: { # https://www.cs.auckland.ac.nz/references/ruby/ProgrammingRuby/language.html Operation: [ o "! Expression", -> new OpNode('!', $2) - o "!! Expression", -> new OpNode('!!', $2) + # o "!! Expression", -> new OpNode('!!', $2) o "- Expression", (-> new OpNode('-', $2)), {prec: 'UMINUS'} - o "+ Expression", (-> new OpNode('+', $2)), {prec: 'UPLUS'} + # o "+ Expression", (-> new OpNode('+', $2)), {prec: 'UPLUS'} o "NOT Expression", -> new OpNode('not', $2) - o "~ Expression", -> new OpNode('~', $2) - o "-- Expression", -> new OpNode('--', $2) - o "++ Expression", -> new OpNode('++', $2) + # o "~ Expression", -> new OpNode('~', $2) + # o "-- Expression", -> new OpNode('--', $2) + # o "++ Expression", -> new OpNode('++', $2) o "DELETE Expression", -> new OpNode('delete', $2) o "TYPEOF Expression", -> new OpNode('typeof', $2) - o "Expression --", -> new OpNode('--', $1, null, true) - o "Expression ++", -> new OpNode('++', $1, null, true) + # o "Expression --", -> new OpNode('--', $1, null, true) + # o "Expression ++", -> new OpNode('++', $1, null, true) o "Expression * Expression", -> new OpNode('*', $1, $3) o "Expression / Expression", -> new OpNode('/', $1, $3) - o "Expression % Expression", -> new OpNode('%', $1, $3) + # o "Expression % Expression", -> new OpNode('%', $1, $3) o "Expression + Expression", -> new OpNode('+', $1, $3) o "Expression - Expression", -> new OpNode('-', $1, $3) - o "Expression << Expression", -> new OpNode('<<', $1, $3) - o "Expression >> Expression", -> new OpNode('>>', $1, $3) - o "Expression >>> Expression", -> new OpNode('>>>', $1, $3) - - o "Expression & Expression", -> new OpNode('&', $1, $3) - o "Expression | Expression", -> new OpNode('|', $1, $3) - o "Expression ^ Expression", -> new OpNode('^', $1, $3) + # o "Expression << Expression", -> new OpNode('<<', $1, $3) + # o "Expression >> Expression", -> new OpNode('>>', $1, $3) + # o "Expression >>> Expression", -> new OpNode('>>>', $1, $3) + # o "Expression & Expression", -> new OpNode('&', $1, $3) + # o "Expression | Expression", -> new OpNode('|', $1, $3) + # o "Expression ^ Expression", -> new OpNode('^', $1, $3) o "Expression <= Expression", -> new OpNode('<=', $1, $3) o "Expression < Expression", -> new OpNode('<', $1, $3) o "Expression > Expression", -> new OpNode('>', $1, $3) o "Expression >= Expression", -> new OpNode('>=', $1, $3) - o "Expression == Expression", -> new OpNode('==', $1, $3) - # o "Expression != Expression", -> new OpNode($2, $1, $3) - # o "Expression IS Expression", -> new OpNode($2, $1, $3) - # o "Expression ISNT Expression", -> new OpNode($2, $1, $3) - # - # o "Expression && Expression", -> new OpNode($2, $1, $3) - # o "Expression || Expression", -> new OpNode($2, $1, $3) - # o "Expression AND Expression", -> new OpNode($2, $1, $3) - # o "Expression OR Expression", -> new OpNode($2, $1, $3) - # o "Expression ? Expression", -> new OpNode($2, $1, $3) - # - # o "Expression -= Expression", -> new OpNode($2, $1, $3) - # o "Expression += Expression", -> new OpNode($2, $1, $3) + # o "Expression == Expression", -> new OpNode('==', $1, $3) + # o "Expression != Expression", -> new OpNode($2, $1, $3) + o "Expression IS Expression", -> new OpNode($2, $1, $3) + o "Expression ISNT Expression", -> new OpNode($2, $1, $3) + + # o "Expression && Expression", -> new OpNode($2, $1, $3) + # o "Expression || Expression", -> new OpNode($2, $1, $3) + o "Expression AND Expression", -> new OpNode($2, $1, $3) + o "Expression OR Expression", -> new OpNode($2, $1, $3) + o "Expression ? Expression", -> new OpNode($2, $1, $3) + + o "Expression -= Expression", -> new OpNode($2, $1, $3) + o "Expression += Expression", -> new OpNode($2, $1, $3) # o "Expression /= Expression", -> new OpNode($2, $1, $3) # o "Expression *= Expression", -> new OpNode($2, $1, $3) # o "Expression %= Expression", -> new OpNode($2, $1, $3) - # o "Expression ||= Expression", -> new OpNode($2, $1, $3) - # o "Expression &&= Expression", -> new OpNode($2, $1, $3) - # o "Expression ?= Expression", -> new OpNode($2, $1, $3) - # - # o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3) - # o "Expression IN Expression", -> new OpNode($2, $1, $3) + o "Expression ||= Expression", -> new OpNode($2, $1, $3) + o "Expression &&= Expression", -> new OpNode($2, $1, $3) + # o "Expression ?= Expression", -> new OpNode($2, $1, $3) + + o "Expression INSTANCEOF Expression", -> new OpNode($2, $1, $3) + # o "Expression IN Expression", -> new OpNode($2, $1, $3) ] # The existence operator.