From d8465ce76729fc28976dca6c315b00685af19262 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 15 Sep 2010 23:46:01 -0400 Subject: [PATCH] First draft of real switch statements for CoffeeScript switch statements. --- lib/grammar.js | 18 ++++++------- lib/lexer.js | 12 ++++++--- lib/nodes.js | 63 +++++++++++++++++++++++++++++++++++++++++++++- lib/parser.js | 18 ++++++------- src/grammar.coffee | 18 ++++++------- src/nodes.coffee | 37 +++++++++++++++++++++++++++ 6 files changed, 128 insertions(+), 38 deletions(-) diff --git a/lib/grammar.js b/lib/grammar.js index cd978192..e38b65ba 100644 --- a/lib/grammar.js +++ b/lib/grammar.js @@ -497,29 +497,25 @@ ], Switch: [ o("SWITCH Expression INDENT Whens OUTDENT", function() { - return $4.switchesOver($2); + return new SwitchNode($2, $4); }), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() { - return $4.switchesOver($2).addElse($6, true); + return new SwitchNode($2, $4, $6); }), o("SWITCH INDENT Whens OUTDENT", function() { - return $3; + return new SwitchNode(null, $3); }), o("SWITCH INDENT Whens ELSE Block OUTDENT", function() { - return $3.addElse($5, true); + return new SwitchNode(null, $3, $5); }) ], Whens: [ o("When"), o("Whens When", function() { - return $1.addElse($2); + return $1.concat($2); }) ], When: [ o("LEADING_WHEN SimpleArgs Block", function() { - return new IfNode($2, $3, { - statement: true - }); + return [[$2, $3]]; }), o("LEADING_WHEN SimpleArgs Block TERMINATOR", function() { - return new IfNode($2, $3, { - statement: true - }); + return [[$2, $3]]; }) ], IfBlock: [ diff --git a/lib/lexer.js b/lib/lexer.js index df99ca89..0f6829ad 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -416,7 +416,7 @@ return doc.replace(MULTILINER, "\\n").replace(new RegExp(options.quote, 'g'), "\\" + (options.quote)); }; Lexer.prototype.tagParameters = function() { - var _d, i, tok; + var i, tok; if (this.tag() !== ')') { return null; } @@ -427,11 +427,15 @@ if (!tok) { return null; } - if ((_d = tok[0]) === 'IDENTIFIER') { + switch (tok[0]) { + case 'IDENTIFIER': tok[0] = 'PARAM'; - } else if (_d === ')') { + break; + case ')': tok[0] = 'PARAM_END'; - } else if (_d === '(' || _d === 'CALL_START') { + break; + case '(': + case 'CALL_START': return (tok[0] = 'PARAM_START'); } } diff --git a/lib/nodes.js b/lib/nodes.js index 3e5502c4..9ed48fb2 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,5 +1,5 @@ (function() { - var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParamNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, ends, flatten, helpers, include, indexOf, literal, merge, starts, utility; + var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParamNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, SwitchNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, ends, flatten, helpers, include, indexOf, literal, merge, starts, utility; var __extends = function(child, parent) { var ctor = function(){}; ctor.prototype = parent.prototype; @@ -1728,6 +1728,67 @@ }; return ForNode; })(); + exports.SwitchNode = (function() { + SwitchNode = function(_b, _c, _d) { + this.otherwise = _d; + this.cases = _c; + this.subject = _b; + SwitchNode.__super__.constructor.call(this); + this.tags.subjectless = !this.subject; + this.subject || (this.subject = literal('true')); + return this; + }; + __extends(SwitchNode, BaseNode); + SwitchNode.prototype["class"] = 'SwitchNode'; + SwitchNode.prototype.children = ['subject', 'cases', 'otherwise']; + SwitchNode.prototype.isStatement = function() { + return true; + }; + SwitchNode.prototype.makeReturn = function() { + var _b, _c, _d, pair; + _c = this.cases; + for (_b = 0, _d = _c.length; _b < _d; _b++) { + pair = _c[_b]; + pair[1].makeReturn(); + } + if (this.otherwise) { + this.otherwise.makeReturn(); + } + return this; + }; + SwitchNode.prototype.compileNode = function(o) { + var _b, _c, _d, _e, _f, _g, _h, block, code, condition, conditions, exprs, idt, pair; + idt = (o.indent = this.idt(1)); + o.top = true; + code = ("" + (this.tab) + "switch (" + (this.subject.compile(o)) + ") {"); + _c = this.cases; + for (_b = 0, _d = _c.length; _b < _d; _b++) { + pair = _c[_b]; + _e = pair; + conditions = _e[0]; + block = _e[1]; + exprs = block.expressions; + _g = flatten([conditions]); + for (_f = 0, _h = _g.length; _f < _h; _f++) { + condition = _g[_f]; + if (this.tags.subjectless) { + condition = new OpNode('!!', new ParentheticalNode(condition)); + } + code += ("\n" + (this.tab) + "case " + (condition.compile(o)) + ":"); + } + code += ("\n" + (block.compile(o))); + if (!(exprs[exprs.length - 1] instanceof ReturnNode)) { + code += ("\n" + (idt) + "break;"); + } + } + if (this.otherwise) { + code += ("\n" + (this.tab) + "default:\n" + (this.otherwise.compile(o))); + } + code += ("\n" + (this.tab) + "}"); + return code; + }; + return SwitchNode; + })(); exports.IfNode = (function() { IfNode = function(_b, _c, _d) { this.tags = _d; diff --git a/lib/parser.js b/lib/parser.js index 209b0643..215fcea7 100755 --- a/lib/parser.js +++ b/lib/parser.js @@ -409,25 +409,21 @@ case 177:this.$ = { guard: $$[$0-6+6-1] }; break; -case 178:this.$ = $$[$0-5+4-1].switchesOver($$[$0-5+2-1]); +case 178:this.$ = new SwitchNode($$[$0-5+2-1], $$[$0-5+4-1]); break; -case 179:this.$ = $$[$0-7+4-1].switchesOver($$[$0-7+2-1]).addElse($$[$0-7+6-1], true); +case 179:this.$ = new SwitchNode($$[$0-7+2-1], $$[$0-7+4-1], $$[$0-7+6-1]); break; -case 180:this.$ = $$[$0-4+3-1]; +case 180:this.$ = new SwitchNode(null, $$[$0-4+3-1]); break; -case 181:this.$ = $$[$0-6+3-1].addElse($$[$0-6+5-1], true); +case 181:this.$ = new SwitchNode(null, $$[$0-6+3-1], $$[$0-6+5-1]); break; case 182:this.$ = $$[$0-1+1-1]; break; -case 183:this.$ = $$[$0-2+1-1].addElse($$[$0-2+2-1]); +case 183:this.$ = $$[$0-2+1-1].concat($$[$0-2+2-1]); break; -case 184:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1], { - statement: true - }); +case 184:this.$ = [[$$[$0-3+2-1], $$[$0-3+3-1]]]; break; -case 185:this.$ = new IfNode($$[$0-4+2-1], $$[$0-4+3-1], { - statement: true - }); +case 185:this.$ = [[$$[$0-4+2-1], $$[$0-4+3-1]]]; break; case 186:this.$ = new IfNode($$[$0-3+2-1], $$[$0-3+3-1]); break; diff --git a/src/grammar.coffee b/src/grammar.coffee index f32b5ca2..b29f8845 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -485,26 +485,22 @@ grammar = o "IN Expression BY Expression WHEN Expression", -> source: $2, step: $4, guard: $6 ] - # 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.switchesOver $2 - o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.switchesOver($2).addElse $6, true - o "SWITCH INDENT Whens OUTDENT", -> $3 - o "SWITCH INDENT Whens ELSE Block OUTDENT", -> $3.addElse $5, true + o "SWITCH Expression INDENT Whens OUTDENT", -> new SwitchNode $2, $4 + o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> new SwitchNode $2, $4, $6 + o "SWITCH INDENT Whens OUTDENT", -> new SwitchNode null, $3 + o "SWITCH INDENT Whens ELSE Block OUTDENT", -> new SwitchNode null, $3, $5 ] - # 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.addElse $2 + o "Whens When", -> $1.concat $2 ] # An individual **When** clause, with action. When: [ - o "LEADING_WHEN SimpleArgs Block", -> new IfNode $2, $3, statement: true - o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode $2, $3, statement: true + o "LEADING_WHEN SimpleArgs Block", -> [[$2, $3]] + o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> [[$2, $3]] ] # The most basic form of *if* is a condition and an action. The following diff --git a/src/nodes.coffee b/src/nodes.coffee index b7c266a3..981d372f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1422,6 +1422,43 @@ exports.ForNode = class ForNode extends BaseNode vars = if range then name else "#{name}, #{ivar}" "#{sourcePart}for (#{forPart}) {#{guardPart}\n#{varPart}#{body}\n#{@tab}}#{returnResult}" +#### SwitchNode + +# A JavaScript *switch* statement. Converts into a returnable expression on-demand. +exports.SwitchNode = class SwitchNode extends BaseNode + + class: 'SwitchNode' + children: ['subject', 'cases', 'otherwise'] + + isStatement: -> yes + + constructor: (@subject, @cases, @otherwise) -> + super() + @tags.subjectless = !@subject + @subject or= literal 'true' + + makeReturn: -> + pair[1].makeReturn() for pair in @cases + @otherwise.makeReturn() if @otherwise + this + + compileNode: (o) -> + idt = o.indent = @idt 1 + o.top = yes + code = "#{ @tab }switch (#{ @subject.compile o }) {" + for pair in @cases + [conditions, block] = pair + exprs = block.expressions + for condition in flatten [conditions] + condition = new OpNode '!!', new ParentheticalNode condition if @tags.subjectless + code += "\n#{ @tab }case #{ condition.compile o }:" + code += "\n#{ block.compile o }" + code += "\n#{ idt }break;" unless exprs[exprs.length - 1] instanceof ReturnNode + if @otherwise + code += "\n#{ @tab }default:\n#{ @otherwise.compile o }" + code += "\n#{ @tab }}" + code + #### IfNode # *If/else* statements. Our *switch/when* will be compiled into this. Acts as an