From c8e0f8b1490c6322eec271c1906030e08e57bfe0 Mon Sep 17 00:00:00 2001 From: gfxmonk Date: Tue, 27 Apr 2010 23:32:45 +1000 Subject: [PATCH] Cleaned up IfNodes - renamed rewrite_condition() to switches_over(), and @switcher to @switch_subject - removed unused else_body constructor parameter, as well as unnecessary push() method - ensure both @body and @else_body are always Expressions (previously they could be either Expressions or IfNode) --- lib/grammar.js | 20 +++++----- lib/nodes.js | 93 +++++++++++++++++++++++++--------------------- lib/parser.js | 20 +++++----- src/grammar.coffee | 20 +++++----- src/nodes.coffee | 78 ++++++++++++++++++++------------------ 5 files changed, 122 insertions(+), 109 deletions(-) diff --git a/lib/grammar.js b/lib/grammar.js index 9c0de6ca..b68d0708 100644 --- a/lib/grammar.js +++ b/lib/grammar.js @@ -570,26 +570,26 @@ // switch/case/default by compiling into an if-else chain. Switch: [ o("SWITCH Expression INDENT Whens OUTDENT", function() { - return $4.rewrite_condition($2); + return $4.switches_over($2); }), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() { - return $4.rewrite_condition($2).add_else($6, true); + return $4.switches_over($2).add_else($6, true); }) ], // The inner list of whens is left recursive. At code-generation time, the // IfNode will rewrite them into a proper chain. Whens: [ o("When"), o("Whens When", function() { - return $1.push($2); + return $1.add_else($2); }) ], // An individual **When** clause, with action. When: [ o("LEADING_WHEN SimpleArgs Block", function() { - return new IfNode($2, $3, null, { + return new IfNode($2, $3, { statement: true }); }), o("LEADING_WHEN SimpleArgs Block TERMINATOR", function() { - return new IfNode($2, $3, null, { + return new IfNode($2, $3, { statement: true }); }), o("Comment TERMINATOR When", function() { @@ -604,7 +604,7 @@ o("IF Expression Block", function() { return new IfNode($2, $3); }), o("UNLESS Expression Block", function() { - return new IfNode($2, $3, null, { + return new IfNode($2, $3, { invert: true }); }), o("IfStart ElsIf", function() { @@ -627,20 +627,20 @@ // *if* and *unless*. If: [ o("IfBlock"), o("Statement IF Expression", function() { - return new IfNode($3, Expressions.wrap([$1]), null, { + return new IfNode($3, Expressions.wrap([$1]), { statement: true }); }), o("Expression IF Expression", function() { - return new IfNode($3, Expressions.wrap([$1]), null, { + return new IfNode($3, Expressions.wrap([$1]), { statement: true }); }), o("Statement UNLESS Expression", function() { - return new IfNode($3, Expressions.wrap([$1]), null, { + return new IfNode($3, Expressions.wrap([$1]), { statement: true, invert: true }); }), o("Expression UNLESS Expression", function() { - return new IfNode($3, Expressions.wrap([$1]), null, { + return new IfNode($3, Expressions.wrap([$1]), { statement: true, invert: true }); diff --git a/lib/nodes.js b/lib/nodes.js index 371541f8..b488b60a 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1532,8 +1532,10 @@ indent: this.idt(1), top: true })) + "\n"; - } else if (name) { - var_part = ("" + body_dent + name + " = " + svar + "[" + ivar + "];\n"); + } else { + if (name) { + var_part = ("" + body_dent + name + " = " + svar + "[" + ivar + "];\n"); + } } if (!(this.object)) { lvar = scope.free_variable(); @@ -1570,11 +1572,11 @@ // Single-expression **IfNodes** are compiled into ternary operators if possible, // because ternaries are already proper expressions, and don't need conversion. exports.IfNode = (function() { - IfNode = function IfNode(condition, body, else_body, tags) { + IfNode = function IfNode(condition, body, tags) { this.condition = condition; - this.body = body && body.unwrap(); - this.else_body = else_body && else_body.unwrap(); - this.children = compact(flatten([this.condition, this.body, this.else_body])); + this.body = body; + this.else_body = null; + this.populate_children(); this.tags = tags || {}; if (this.condition instanceof Array) { this.multiple = true; @@ -1582,16 +1584,19 @@ if (this.tags.invert) { this.condition = new OpNode('!', new ParentheticalNode(this.condition)); } + this.is_chain = false; return this; }; __extends(IfNode, BaseNode); - // Add a new *else* clause to this **IfNode**, or push it down to the bottom - // of the chain recursively. - IfNode.prototype.push = function push(else_body) { - var eb; - eb = else_body.unwrap(); - this.else_body ? this.else_body.push(eb) : (this.else_body = eb); - return this; + IfNode.prototype.populate_children = function populate_children() { + this.children = compact(flatten([this.condition, this.body, this.else_body])); + return this.children; + }; + IfNode.prototype.body_node = function body_node() { + return this.body == undefined ? undefined : this.body.unwrap(); + }; + IfNode.prototype.else_body_node = function else_body_node() { + return this.else_body == undefined ? undefined : this.else_body.unwrap(); }; IfNode.prototype.force_statement = function force_statement() { this.tags.statement = true; @@ -1599,57 +1604,55 @@ }; // Tag a chain of **IfNodes** with their object(s) to switch on for equality // tests. `rewrite_switch` will perform the actual change at compile time. - IfNode.prototype.rewrite_condition = function rewrite_condition(expression) { - this.switcher = expression; + IfNode.prototype.switches_over = function switches_over(expression) { + this.switch_subject = expression; return this; }; // Rewrite a chain of **IfNodes** with their switch condition for equality. // Ensure that the switch expression isn't evaluated more than once. IfNode.prototype.rewrite_switch = function rewrite_switch(o) { var _b, _c, _d, assigner, cond, i, variable; - assigner = this.switcher; - if (!(this.switcher.unwrap() instanceof LiteralNode)) { + assigner = this.switch_subject; + if (!((this.switch_subject.unwrap() instanceof LiteralNode))) { variable = literal(o.scope.free_variable()); - assigner = new AssignNode(variable, this.switcher); - this.switcher = variable; + assigner = new AssignNode(variable, this.switch_subject); + this.children.push(assigner); + this.switch_subject = variable; } this.condition = (function() { if (this.multiple) { _b = []; _c = this.condition; for (i = 0, _d = _c.length; i < _d; i++) { cond = _c[i]; - _b.push(new OpNode('==', (i === 0 ? assigner : this.switcher), cond)); + _b.push(new OpNode('==', (i === 0 ? assigner : this.switch_subject), cond)); } return _b; } else { return new OpNode('==', assigner, this.condition); } }).call(this); - if (this.is_chain()) { - this.else_body.rewrite_condition(this.switcher); + if (this.is_chain) { + this.else_body_node().switches_over(this.switch_subject); } + // prevent this rewrite from happening again + this.switch_subject = undefined; return this; }; // Rewrite a chain of **IfNodes** to add a default case as the final *else*. - IfNode.prototype.add_else = function add_else(exprs, statement) { - if (this.is_chain()) { - this.else_body.add_else(exprs, statement); + IfNode.prototype.add_else = function add_else(else_body, statement) { + if (this.is_chain) { + this.else_body_node().add_else(else_body, statement); } else { - if (!(statement)) { - exprs = exprs.unwrap(); - } - this.children.push((this.else_body = exprs)); + this.is_chain = else_body instanceof IfNode; + this.else_body = this.ensure_expressions(else_body); + this.populate_children(); } return this; }; - // If the `else_body` is an **IfNode** itself, then we've got an *if-else* chain. - IfNode.prototype.is_chain = function is_chain() { - return this.chain = this.chain || this.else_body && this.else_body instanceof IfNode; - }; // The **IfNode** only compiles into a statement if either of its bodies needs // to be a statement. Otherwise a ternary is safe. IfNode.prototype.is_statement = function is_statement() { - return this.statement = this.statement || !!(this.comment || this.tags.statement || this.body.is_statement() || (this.else_body && this.else_body.is_statement())); + return this.statement = this.statement || !!(this.comment || this.tags.statement || this.body_node().is_statement() || (this.else_body && this.else_body_node().is_statement())); }; IfNode.prototype.compile_condition = function compile_condition(o) { var _b, _c, _d, _e, cond; @@ -1670,15 +1673,21 @@ } }; IfNode.prototype.make_return = function make_return() { - this.body = this.body && this.body.make_return(); - this.else_body = this.else_body && this.else_body.make_return(); + this.body = this.body && this.ensure_expressions(this.body.make_return()); + this.else_body = this.else_body && this.ensure_expressions(this.else_body.make_return()); return this; }; + IfNode.prototype.ensure_expressions = function ensure_expressions(node) { + if (!(node instanceof Expressions)) { + node = new Expressions([node]); + } + return node; + }; // Compile the **IfNode** as a regular *if-else* statement. Flattened chains // force inner *else* bodies into statement form. IfNode.prototype.compile_statement = function compile_statement(o) { var body, child, com_dent, cond_o, else_part, if_dent, if_part, prefix; - if (this.switcher) { + if (this.switch_subject) { this.rewrite_switch(o); } child = del(o, 'chain_child'); @@ -1688,22 +1697,22 @@ if_dent = child ? '' : this.idt(); com_dent = child ? this.idt() : ''; prefix = this.comment ? ("" + (this.comment.compile(cond_o)) + "\n" + com_dent) : ''; - body = Expressions.wrap([this.body]).compile(o); + body = this.body.compile(o); if_part = ("" + prefix + (if_dent) + "if (" + (this.compile_condition(cond_o)) + ") {\n" + body + "\n" + this.tab + "}"); if (!(this.else_body)) { return if_part; } - else_part = this.is_chain() ? ' else ' + this.else_body.compile(merge(o, { + else_part = this.is_chain ? ' else ' + this.else_body_node().compile(merge(o, { indent: this.idt(), chain_child: true - })) : (" else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}"); + })) : (" else {\n" + (this.else_body.compile(o)) + "\n" + this.tab + "}"); return "" + if_part + else_part; }; // Compile the IfNode as a ternary operator. IfNode.prototype.compile_ternary = function compile_ternary(o) { var else_part, if_part; - if_part = this.condition.compile(o) + ' ? ' + this.body.compile(o); - else_part = this.else_body ? this.else_body.compile(o) : 'null'; + if_part = this.condition.compile(o) + ' ? ' + this.body_node().compile(o); + else_part = this.else_body ? this.else_body_node().compile(o) : 'null'; return "" + if_part + " : " + else_part; }; return IfNode; diff --git a/lib/parser.js b/lib/parser.js index 317c65f7..7b5c52dd 100755 --- a/lib/parser.js +++ b/lib/parser.js @@ -365,19 +365,19 @@ case 160:this.$ = { guard: $$[$0-6+6-1] }; break; -case 161:this.$ = $$[$0-5+4-1].rewrite_condition($$[$0-5+2-1]); +case 161:this.$ = $$[$0-5+4-1].switches_over($$[$0-5+2-1]); break; -case 162:this.$ = $$[$0-7+4-1].rewrite_condition($$[$0-7+2-1]).add_else($$[$0-7+6-1], true); +case 162:this.$ = $$[$0-7+4-1].switches_over($$[$0-7+2-1]).add_else($$[$0-7+6-1], true); break; case 163:this.$ = $$[$0-1+1-1]; break; -case 164:this.$ = $$[$0-2+1-1].push($$[$0-2+2-1]); +case 164:this.$ = $$[$0-2+1-1].add_else($$[$0-2+2-1]); break; -case 165:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1], null, { +case 165:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1], { statement: true }); break; -case 166:this.$ = new IfNode($$[$0-4+2-1], $$[$0-4+3-1], null, { +case 166:this.$ = new IfNode($$[$0-4+2-1], $$[$0-4+3-1], { statement: true }); break; @@ -388,7 +388,7 @@ case 167:this.$ = (function () { break; case 168:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1]); break; -case 169:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1], null, { +case 169:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1], { invert: true }); break; @@ -402,20 +402,20 @@ case 173:this.$ = (new IfNode($$[$0-4+3-1], $$[$0-4+4-1])).force_statement(); break; case 174:this.$ = $$[$0-1+1-1]; break; -case 175:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), null, { +case 175:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), { statement: true }); break; -case 176:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), null, { +case 176:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), { statement: true }); break; -case 177:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), null, { +case 177:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), { statement: true, invert: true }); break; -case 178:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), null, { +case 178:this.$ = new IfNode($$[$0-3+3-1], Expressions.wrap([$$[$0-3+1-1]]), { statement: true, invert: true }); diff --git a/src/grammar.coffee b/src/grammar.coffee index ad4d29bd..f897e67c 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -467,21 +467,21 @@ grammar: { # The CoffeeScript switch/when/else block replaces the JavaScript # switch/case/default by compiling into an if-else chain. Switch: [ - o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition $2 - o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else $6, true + o "SWITCH Expression INDENT Whens OUTDENT", -> $4.switches_over $2 + o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.switches_over($2).add_else $6, true ] # The inner list of whens is left recursive. At code-generation time, the # IfNode will rewrite them into a proper chain. Whens: [ o "When" - o "Whens When", -> $1.push $2 + o "Whens When", -> $1.add_else $2 ] # An individual **When** clause, with action. When: [ - o "LEADING_WHEN SimpleArgs Block", -> new IfNode $2, $3, null, {statement: true} - o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, null, {statement: true} + o "LEADING_WHEN SimpleArgs Block", -> new IfNode $2, $3, {statement: true} + o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, {statement: true} o "Comment TERMINATOR When", -> $3.comment: $1; $3 ] @@ -490,7 +490,7 @@ grammar: { # ambiguity. IfStart: [ o "IF Expression Block", -> new IfNode $2, $3 - o "UNLESS Expression Block", -> new IfNode $2, $3, null, {invert: true} + o "UNLESS Expression Block", -> new IfNode $2, $3, {invert: true} o "IfStart ElsIf", -> $1.add_else $2 ] @@ -509,10 +509,10 @@ grammar: { # *if* and *unless*. If: [ o "IfBlock" - o "Statement IF Expression", -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true} - o "Expression IF Expression", -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true} - o "Statement UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true, invert: true} - o "Expression UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true, invert: true} + o "Statement IF Expression", -> new IfNode $3, Expressions.wrap([$1]), {statement: true} + o "Expression IF Expression", -> new IfNode $3, Expressions.wrap([$1]), {statement: true} + o "Statement UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), {statement: true, invert: true} + o "Expression UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), {statement: true, invert: true} ] # Arithmetic and logical operators, working on one or more operands. diff --git a/src/nodes.coffee b/src/nodes.coffee index 4c3cc544..42279996 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1156,21 +1156,21 @@ statement ForNode # because ternaries are already proper expressions, and don't need conversion. exports.IfNode: class IfNode extends BaseNode - constructor: (condition, body, else_body, tags) -> + constructor: (condition, body, tags) -> @condition: condition - @body: body and body.unwrap() - @else_body: else_body and else_body.unwrap() - @children: compact flatten [@condition, @body, @else_body] + @body: body + @else_body: null + @populate_children() @tags: tags or {} @multiple: true if @condition instanceof Array @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert + @is_chain: false - # Add a new *else* clause to this **IfNode**, or push it down to the bottom - # of the chain recursively. - push: (else_body) -> - eb: else_body.unwrap() - if @else_body then @else_body.push(eb) else @else_body: eb - this + populate_children: -> + @children: compact flatten [@condition, @body, @else_body] + + body_node: -> @body?.unwrap() + else_body_node: -> @else_body?.unwrap() force_statement: -> @tags.statement: true @@ -1178,43 +1178,43 @@ exports.IfNode: class IfNode extends BaseNode # Tag a chain of **IfNodes** with their object(s) to switch on for equality # tests. `rewrite_switch` will perform the actual change at compile time. - rewrite_condition: (expression) -> - @switcher: expression + switches_over: (expression) -> + @switch_subject: expression this # Rewrite a chain of **IfNodes** with their switch condition for equality. # Ensure that the switch expression isn't evaluated more than once. rewrite_switch: (o) -> - assigner: @switcher - unless @switcher.unwrap() instanceof LiteralNode + assigner: @switch_subject + unless (@switch_subject.unwrap() instanceof LiteralNode) variable: literal(o.scope.free_variable()) - assigner: new AssignNode(variable, @switcher) - @switcher: variable + assigner: new AssignNode(variable, @switch_subject) + @children.push(assigner) + @switch_subject: variable @condition: if @multiple for cond, i in @condition - new OpNode('==', (if i is 0 then assigner else @switcher), cond) + new OpNode('==', (if i is 0 then assigner else @switch_subject), cond) else new OpNode('==', assigner, @condition) - @else_body.rewrite_condition(@switcher) if @is_chain() + @else_body_node().switches_over(@switch_subject) if @is_chain + # prevent this rewrite from happening again + @switch_subject: undefined this # Rewrite a chain of **IfNodes** to add a default case as the final *else*. - add_else: (exprs, statement) -> - if @is_chain() - @else_body.add_else exprs, statement + add_else: (else_body, statement) -> + if @is_chain + @else_body_node().add_else else_body, statement else - exprs: exprs.unwrap() unless statement - @children.push @else_body: exprs + @is_chain: else_body instanceof IfNode + @else_body: @ensure_expressions else_body + @populate_children() this - # If the `else_body` is an **IfNode** itself, then we've got an *if-else* chain. - is_chain: -> - @chain: or @else_body and @else_body instanceof IfNode - # The **IfNode** only compiles into a statement if either of its bodies needs # to be a statement. Otherwise a ternary is safe. is_statement: -> - @statement: or !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement())) + @statement: or !!(@comment or @tags.statement or @body_node().is_statement() or (@else_body and @else_body_node().is_statement())) compile_condition: (o) -> (cond.compile(o) for cond in flatten([@condition])).join(' || ') @@ -1223,14 +1223,18 @@ exports.IfNode: class IfNode extends BaseNode if @is_statement() then @compile_statement(o) else @compile_ternary(o) make_return: -> - @body: and @body.make_return() - @else_body: and @else_body.make_return() + @body: and @ensure_expressions(@body.make_return()) + @else_body: and @ensure_expressions(@else_body.make_return()) this + ensure_expressions: (node) -> + node: new Expressions([node]) unless node instanceof Expressions + node + # Compile the **IfNode** as a regular *if-else* statement. Flattened chains # force inner *else* bodies into statement form. compile_statement: (o) -> - @rewrite_switch(o) if @switcher + @rewrite_switch(o) if @switch_subject child: del o, 'chain_child' cond_o: merge o o.indent: @idt 1 @@ -1238,19 +1242,19 @@ exports.IfNode: class IfNode extends BaseNode if_dent: if child then '' else @idt() com_dent: if child then @idt() else '' prefix: if @comment then "${ @comment.compile(cond_o) }\n$com_dent" else '' - body: Expressions.wrap([@body]).compile(o) + body: @body.compile(o) if_part: "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n$@tab}" return if_part unless @else_body - else_part: if @is_chain() - ' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true})) + else_part: if @is_chain + ' else ' + @else_body_node().compile(merge(o, {indent: @idt(), chain_child: true})) else - " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}" + " else {\n${ @else_body.compile(o) }\n$@tab}" "$if_part$else_part" # Compile the IfNode as a ternary operator. compile_ternary: (o) -> - if_part: @condition.compile(o) + ' ? ' + @body.compile(o) - else_part: if @else_body then @else_body.compile(o) else 'null' + if_part: @condition.compile(o) + ' ? ' + @body_node().compile(o) + else_part: if @else_body then @else_body_node().compile(o) else 'null' "$if_part : $else_part" # Faux-Nodes