diff --git a/documentation/coffee/range_comprehensions.coffee b/documentation/coffee/range_comprehensions.coffee index 174445f3..10cd0410 100644 --- a/documentation/coffee/range_comprehensions.coffee +++ b/documentation/coffee/range_comprehensions.coffee @@ -1,4 +1,4 @@ -countdown: num for num in [10..1] by -1 +countdown: num for num in [10..1] deliverEggs: -> for i in [0...eggs.length] by 12 diff --git a/lib/cake.js b/lib/cake.js index 8acaad87..7847ed19 100755 --- a/lib/cake.js +++ b/lib/cake.js @@ -66,7 +66,7 @@ spaces = 20 - name.length; spaces = spaces > 0 ? (function() { _b = []; - for (i = 0; i <= spaces; i += 1) { + for (i = 0; (0 <= spaces ? i <= spaces : i >= spaces); (0 <= spaces ? i += 1 : i -= 1)) { _b.push(' '); } return _b; diff --git a/lib/nodes.js b/lib/nodes.js index a26f2fd9..79fd0b65 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,5 +1,5 @@ (function(){ - var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, 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, DOUBLE_PARENS, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, 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 __extends = function(child, parent) { var ctor = function(){ }; ctor.prototype = parent.prototype; @@ -558,55 +558,79 @@ this.from = from; this.to = to; this.exclusive = !!exclusive; + this.equals = this.exclusive ? '' : '='; return this; }; __extends(RangeNode, BaseNode); RangeNode.prototype['class'] = 'RangeNode'; RangeNode.prototype.children = ['from', 'to']; RangeNode.prototype.compileVariables = function(o) { - var _b, _c, parts; - _b = this.from.compileReference(o); + var _b, _c, _d, parts; + _b = this.from.compileReference(o, { + precompile: true + }); this.from = _b[0]; this.fromVar = _b[1]; - _c = this.to.compileReference(o); + _c = this.to.compileReference(o, { + precompile: true + }); this.to = _c[0]; this.toVar = _c[1]; + _d = [this.fromVar.match(SIMPLENUM), this.toVar.match(SIMPLENUM)]; + this.fromNum = _d[0]; + this.toNum = _d[1]; parts = []; if (this.from !== this.fromVar) { - parts.push(this.from.compile(o)); + parts.push(this.from); } if (this.to !== this.toVar) { - parts.push(this.to.compile(o)); + parts.push(this.to); } - return parts.length ? ("" + (parts.join('; ')) + ";") : ''; + return parts.length ? ("" + (parts.join('; ')) + "; ") : ''; }; RangeNode.prototype.compileNode = function(o) { - var equals, idx, op, step, vars; + var compare, idx, incr, intro, step, stepPart, vars; if (!(o.index)) { return this.compileArray(o); } + if (this.fromNum && this.toNum) { + return this.compileSimple(o); + } idx = del(o, 'index'); step = del(o, 'step'); - vars = ("" + idx + " = " + (this.fromVar.compile(o))); - step = step ? step.compile(o) : '1'; - equals = this.exclusive ? '' : '='; - op = starts(step, '-') ? (">" + equals) : ("<" + equals); - return "" + vars + "; " + (idx) + " " + op + " " + (this.toVar.compile(o)) + "; " + idx + " += " + step; + vars = ("" + idx + " = " + this.fromVar); + intro = ("(" + this.fromVar + " <= " + this.toVar + " ? " + idx); + compare = ("" + intro + " <" + this.equals + " " + this.toVar + " : " + idx + " >" + this.equals + " " + this.toVar + ")"); + stepPart = step ? step.compile(o) : '1'; + incr = step ? ("" + idx + " += " + stepPart) : ("" + intro + " += " + stepPart + " : " + idx + " -= " + stepPart + ")"); + return "" + vars + "; " + compare + "; " + incr; + }; + RangeNode.prototype.compileSimple = function(o) { + var _b, from, idx, step, to; + _b = [parseInt(this.fromNum, 10), parseInt(this.toNum, 10)]; + from = _b[0]; + to = _b[1]; + idx = del(o, 'index'); + step = del(o, 'step'); + step = step && ("" + idx + " += " + (step.compile(o))); + return from <= to ? ("" + idx + " = " + from + "; " + idx + " <" + this.equals + " " + to + "; " + (step || ("" + idx + "++"))) : ("" + idx + " = " + from + "; " + idx + " >" + this.equals + " " + to + "; " + (step || ("" + idx + "--"))); }; RangeNode.prototype.compileArray = function(o) { - var body, clause, equals, from, i, idt, post, pre, result, to, vars; + var body, clause, i, idt, post, pre, result, vars; idt = this.idt(1); vars = this.compileVariables(merge(o, { indent: idt })); - equals = this.exclusive ? '' : '='; - from = this.fromVar.compile(o); - to = this.toVar.compile(o); result = o.scope.freeVariable(); i = o.scope.freeVariable(); - clause = ("" + from + " <= " + to + " ?"); pre = ("\n" + (idt) + (result) + " = []; " + (vars)); - body = ("var " + i + " = " + from + "; " + clause + " " + i + " <" + equals + " " + to + " : " + i + " >" + equals + " " + to + "; " + clause + " " + i + " += 1 : " + i + " -= 1"); + if (this.fromNum && this.toNum) { + o.index = i; + body = this.compileSimple(o); + } else { + clause = ("" + this.fromVar + " <= " + this.toVar + " ?"); + body = ("var " + i + " = " + this.fromVar + "; " + clause + " " + i + " <" + this.equals + " " + this.toVar + " : " + i + " >" + this.equals + " " + this.toVar + "; " + clause + " " + i + " += 1 : " + i + " -= 1"); + } post = ("{ " + (result) + ".push(" + i + ") };\n" + (idt) + "return " + result + ";\n" + o.indent); return "(function(){" + (pre) + "\n" + (idt) + "for (" + body + ")" + post + "}).call(this)"; }; @@ -1426,9 +1450,6 @@ body = Expressions.wrap([this.body]); if (range) { sourcePart = source.compileVariables(o); - if (sourcePart) { - sourcePart += ("\n" + o.indent); - } forPart = source.compile(merge(o, { index: ivar, step: this.step @@ -1656,6 +1677,7 @@ DOUBLE_PARENS = /\(\(([^\(\)\n]*)\)\)/g; IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/; NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i; + SIMPLENUM = /^-?\d+/; IS_STRING = /^['"]/; literal = function(name) { return new LiteralNode(name); diff --git a/lib/optparse.js b/lib/optparse.js index edfb425f..3db86122 100755 --- a/lib/optparse.js +++ b/lib/optparse.js @@ -48,7 +48,7 @@ spaces = 15 - rule.longFlag.length; spaces = spaces > 0 ? (function() { _d = []; - for (i = 0; i <= spaces; i += 1) { + for (i = 0; (0 <= spaces ? i <= spaces : i >= spaces); (0 <= spaces ? i += 1 : i -= 1)) { _d.push(' '); } return _d; diff --git a/lib/rewriter.js b/lib/rewriter.js index 2ec661ce..3c46c3ec 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -131,8 +131,7 @@ var __func = function(i) { var _c, size, tmp; (_c = stack[stack.length - 1]); - - for (tmp = 0; tmp < _c; tmp += 1) { + for (tmp = 0; (0 <= _c ? tmp < _c : tmp > _c); (0 <= _c ? tmp += 1 : tmp -= 1)) { this.tokens.splice(i, 0, ['CALL_END', ')', this.tokens[i][2]]); } size = stack[stack.length - 1] + 1; diff --git a/src/nodes.coffee b/src/nodes.coffee index 691526dd..e62ce019 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -531,41 +531,57 @@ exports.RangeNode: class RangeNode extends BaseNode @from: from @to: to @exclusive: !!exclusive + @equals: if @exclusive then '' else '=' # Compiles the range's source variables -- where it starts and where it ends. # But only if they need to be cached to avoid double evaluation. compileVariables: (o) -> - [@from, @fromVar]: @from.compileReference o - [@to, @toVar]: @to.compileReference o + [@from, @fromVar]: @from.compileReference o, {precompile: yes} + [@to, @toVar]: @to.compileReference o, {precompile: yes} + [@fromNum, @toNum]: [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)] parts: [] - parts.push @from.compile o if @from isnt @fromVar - parts.push @to.compile o if @to isnt @toVar - if parts.length then "${parts.join('; ')};" else '' + parts.push @from if @from isnt @fromVar + parts.push @to if @to isnt @toVar + if parts.length then "${parts.join('; ')}; " else '' # When compiled normally, the range returns the contents of the *for loop* # needed to iterate over the values in the range. Used by comprehensions. compileNode: (o) -> - return @compileArray(o) unless o.index + return @compileArray(o) unless o.index + return @compileSimple(o) if @fromNum and @toNum idx: del o, 'index' step: del o, 'step' - vars: "$idx = ${@fromVar.compile(o)}" - step: if step then step.compile(o) else '1' - equals: if @exclusive then '' else '=' - op: if starts(step, '-') then ">$equals" else "<$equals" - "$vars; ${idx} $op ${@toVar.compile(o)}; $idx += $step" + vars: "$idx = $@fromVar" + intro: "($@fromVar <= $@toVar ? $idx" + compare: "$intro <$@equals $@toVar : $idx >$@equals $@toVar)" + stepPart: if step then step.compile(o) else '1' + incr: if step then "$idx += $stepPart" else "$intro += $stepPart : $idx -= $stepPart)" + "$vars; $compare; $incr" + + # Compile a simple range comprehension, with integers. + compileSimple: (o) -> + [from, to]: [parseInt(@fromNum, 10), parseInt(@toNum, 10)] + idx: del o, 'index' + step: del o, 'step' + step: and "$idx += ${step.compile(o)}" + if from <= to + "$idx = $from; $idx <$@equals $to; ${step or "$idx++"}" + else + "$idx = $from; $idx >$@equals $to; ${step or "$idx--"}" # When used as a value, expand the range into the equivalent array. compileArray: (o) -> idt: @idt 1 vars: @compileVariables(merge(o, {indent: idt})) - equals: if @exclusive then '' else '=' - from: @fromVar.compile o - to: @toVar.compile o result: o.scope.freeVariable() i: o.scope.freeVariable() - clause: "$from <= $to ?" pre: "\n${idt}${result} = []; ${vars}" - body: "var $i = $from; $clause $i <$equals $to : $i >$equals $to; $clause $i += 1 : $i -= 1" + if @fromNum and @toNum + o.index: i + body: @compileSimple o + else + clause: "$@fromVar <= $@toVar ?" + body: "var $i = $@fromVar; $clause $i <$@equals $@toVar : $i >$@equals $@toVar; $clause $i += 1 : $i -= 1" post: "{ ${result}.push($i) };\n${idt}return $result;\n$o.indent" "(function(){${pre}\n${idt}for ($body)$post}).call(this)" @@ -1266,7 +1282,6 @@ exports.ForNode: class ForNode extends BaseNode body: Expressions.wrap([@body]) if range sourcePart: source.compileVariables(o) - sourcePart: + "\n$o.indent" if sourcePart forPart: source.compile merge o, {index: ivar, step: @step} else svar: scope.freeVariable() @@ -1492,6 +1507,7 @@ DOUBLE_PARENS: /\(\(([^\(\)\n]*)\)\)/g # Keep these identifier regexes in sync with the Lexer. IDENTIFIER: /^[a-zA-Z\$_](\w|\$)*$/ NUMBER : /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i +SIMPLENUM : /^-?\d+/ # Is a literal value a string? IS_STRING: /^['"]/ diff --git a/test/test_comprehensions.coffee b/test/test_comprehensions.coffee index 0f252f57..5e2ec07b 100644 --- a/test/test_comprehensions.coffee +++ b/test/test_comprehensions.coffee @@ -25,14 +25,6 @@ result: nums.concat(negs).join(', ') ok result is '3, 6, 9, -20, -19, -18' -# Ensure that ranges are safe. This used to infinite loop: -j = 5 -result: for j in [j..(j+3)] - j - -ok result.join(' ') is '5 6 7 8' - - # With range comprehensions, you can loop in steps. results: x for x in [0..25] by 5 @@ -40,11 +32,17 @@ ok results.join(' ') is '0 5 10 15 20 25' # And can loop downwards, with a negative step. -results: x for x in [5..1] by -1 +results: x for x in [5..1] ok results.join(' ') is '5 4 3 2 1' ok results.join(' ') is [(10-5)..(-2+3)].join(' ') +results: x for x in [10..1] +ok results.join(' ') is [10..1].join(' ') + +results: x for x in [10...0] by -2 +ok results.join(' ') is [10, 8, 6, 4, 2].join(' ') + # Multiline array comprehension with filter. evens: for num in [1, 2, 3, 4, 5, 6] when num % 2 is 0