Skip to content

Commit 1818e74

Browse files
committed
Fixes #2525, #1187, #1208, #1758, and many more -- allow looping over an array downwards
1 parent d72daca commit 1818e74

File tree

3 files changed

+90
-19
lines changed

3 files changed

+90
-19
lines changed

lib/coffee-script/nodes.js

+26-7
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@
25662566
For.prototype.children = ['body', 'source', 'guard', 'step'];
25672567

25682568
For.prototype.compileNode = function(o) {
2569-
var body, defPart, forPart, forVarPart, guardPart, idt1, index, ivar, kvar, kvarAssign, lastJumps, lvar, name, namePart, ref, resultPart, returnResult, rvar, scope, source, step, stepPart, stepVar, svar, varPart, _ref2, _ref3;
2569+
var body, compare, compareDown, declare, declareDown, defPart, down, forPart, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, lastJumps, lvar, name, namePart, ref, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart, _ref2, _ref3;
25702570
body = Block.wrap([this.body]);
25712571
lastJumps = (_ref2 = last(body.expressions)) != null ? _ref2.jumps() : void 0;
25722572
if (lastJumps && lastJumps instanceof Return) {
@@ -2590,6 +2590,7 @@
25902590
kvarAssign = kvar !== ivar ? "" + kvar + " = " : "";
25912591
if (this.step && !this.range) {
25922592
_ref3 = this.step.cache(o, LEVEL_LIST), step = _ref3[0], stepVar = _ref3[1];
2593+
stepNum = stepVar.match(SIMPLENUM);
25932594
}
25942595
if (this.pattern) {
25952596
name = ivar;
@@ -2607,20 +2608,38 @@
26072608
} else {
26082609
svar = this.source.compile(o, LEVEL_LIST);
26092610
if ((name || this.own) && !IDENTIFIER.test(svar)) {
2610-
defPart = "" + this.tab + (ref = scope.freeVariable('ref')) + " = " + svar + ";\n";
2611+
defPart += "" + this.tab + (ref = scope.freeVariable('ref')) + " = " + svar + ";\n";
26112612
svar = ref;
26122613
}
26132614
if (name && !this.pattern) {
26142615
namePart = "" + name + " = " + svar + "[" + kvar + "]";
26152616
}
26162617
if (!this.object) {
2617-
lvar = scope.freeVariable('len');
2618-
forVarPart = "" + kvarAssign + ivar + " = 0, " + lvar + " = " + svar + ".length";
26192618
if (step !== stepVar) {
2620-
forVarPart += ", " + step;
2619+
defPart += "" + this.tab + step + ";\n";
26212620
}
2622-
stepPart = "" + kvarAssign + (this.step ? "" + ivar + " += " + stepVar : (kvar !== ivar ? "++" + ivar : "" + ivar + "++"));
2623-
forPart = "" + forVarPart + "; " + ivar + " < " + lvar + "; " + stepPart;
2621+
if (!(this.step && stepNum && (down = +stepNum < 0))) {
2622+
lvar = scope.freeVariable('len');
2623+
}
2624+
declare = "" + kvarAssign + ivar + " = 0, " + lvar + " = " + svar + ".length";
2625+
declareDown = "" + kvarAssign + ivar + " = " + svar + ".length - 1";
2626+
compare = "" + ivar + " < " + lvar;
2627+
compareDown = "" + ivar + " >= 0";
2628+
if (this.step) {
2629+
if (stepNum) {
2630+
if (down) {
2631+
compare = compareDown;
2632+
declare = declareDown;
2633+
}
2634+
} else {
2635+
compare = "" + stepVar + " > 0 ? " + compare + " : " + compareDown;
2636+
declare = "(" + stepVar + " > 0 ? (" + declare + ") : " + declareDown + ")";
2637+
}
2638+
increment = "" + ivar + " += " + stepVar;
2639+
} else {
2640+
increment = "" + (kvar !== ivar ? "++" + ivar : "" + ivar + "++");
2641+
}
2642+
forPart = "" + declare + "; " + compare + "; " + kvarAssign + increment;
26242643
}
26252644
}
26262645
if (this.returns) {

src/nodes.coffee

+21-8
Original file line numberDiff line numberDiff line change
@@ -1741,9 +1741,9 @@ exports.For = class For extends While
17411741
ivar = (@object and index) or scope.freeVariable 'i'
17421742
kvar = (@range and name) or index or ivar
17431743
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
1744-
# the `_by` variable is created twice in `Range`s if we don't prevent it from being declared here
17451744
if @step and not @range
17461745
[step, stepVar] = @step.cache o, LEVEL_LIST
1746+
stepNum = stepVar.match SIMPLENUM
17471747
name = ivar if @pattern
17481748
varPart = ''
17491749
guardPart = ''
@@ -1754,16 +1754,29 @@ exports.For = class For extends While
17541754
else
17551755
svar = @source.compile o, LEVEL_LIST
17561756
if (name or @own) and not IDENTIFIER.test svar
1757-
defPart = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
1757+
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
17581758
svar = ref
17591759
if name and not @pattern
17601760
namePart = "#{name} = #{svar}[#{kvar}]"
1761-
unless @object
1762-
lvar = scope.freeVariable 'len'
1763-
forVarPart = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
1764-
forVarPart += ", #{step}" if step isnt stepVar
1765-
stepPart = "#{kvarAssign}#{if @step then "#{ivar} += #{stepVar}" else (if kvar isnt ivar then "++#{ivar}" else "#{ivar}++")}"
1766-
forPart = "#{forVarPart}; #{ivar} < #{lvar}; #{stepPart}"
1761+
if not @object
1762+
defPart += "#{@tab}#{step};\n" if step isnt stepVar
1763+
lvar = scope.freeVariable 'len' unless @step and stepNum and down = (+stepNum < 0)
1764+
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
1765+
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
1766+
compare = "#{ivar} < #{lvar}"
1767+
compareDown = "#{ivar} >= 0"
1768+
if @step
1769+
if stepNum
1770+
if down
1771+
compare = compareDown
1772+
declare = declareDown
1773+
else
1774+
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
1775+
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
1776+
increment = "#{ivar} += #{stepVar}"
1777+
else
1778+
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
1779+
forPart = "#{declare}; #{compare}; #{kvarAssign}#{increment}"
17671780
if @returns
17681781
resultPart = "#{@tab}#{rvar} = [];\n"
17691782
returnResult = "\n#{@tab}return #{rvar};"

test/comprehensions.coffee

+43-4
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ test "Optimized range comprehensions.", ->
244244

245245
exxes = ('x' for [0...10])
246246
ok exxes.join(' ') is 'x x x x x x x x x x'
247-
248-
247+
248+
249249
test "Loop variables should be able to reference outer variables", ->
250250
outer = 1
251251
do ->
@@ -411,7 +411,8 @@ test "#1326: `by` value is uncached", ->
411411
rangeCompileSimple = []
412412

413413
#exercises For.compile
414-
for v,i in a by f() then forCompile.push i
414+
for v, i in a by f()
415+
forCompile.push i
415416

416417
#exercises Range.compileSimple
417418
rangeCompileSimple = (i for i in [0..2] by g())
@@ -491,7 +492,7 @@ test "#2007: Return object literal from comprehension", ->
491492
eq 2, y.length
492493
eq 1, y[0].x
493494
eq 0, y[1].x
494-
495+
495496
test "#2274: Allow @values as loop variables", ->
496497
obj = {
497498
item: null
@@ -502,3 +503,41 @@ test "#2274: Allow @values as loop variables", ->
502503
eq obj.item, null
503504
obj.method()
504505
eq obj.item, 3
506+
507+
test "#2525, #1187, #1208, #1758, looping over an array forwards", ->
508+
list = [0, 1, 2, 3, 4]
509+
510+
ident = (x) -> x
511+
512+
arrayEq (i for i in list), list
513+
514+
arrayEq (index for i, index in list), list
515+
516+
arrayEq (i for i in list by 1), list
517+
518+
arrayEq (i for i in list by ident 1), list
519+
520+
arrayEq (i for i in list by ident(1) * 2), [0, 2, 4]
521+
522+
arrayEq (index for i, index in list by ident(1) * 2), [0, 2, 4]
523+
524+
test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
525+
list = [0, 1, 2, 3, 4]
526+
backwards = [4, 3, 2, 1, 0]
527+
528+
ident = (x) -> x
529+
530+
arrayEq (i for i in list by -1), backwards
531+
532+
arrayEq (index for i, index in list by -1), backwards
533+
534+
arrayEq (i for i in list by ident -1), backwards
535+
536+
arrayEq (i for i in list by ident(-1) * 2), [4, 2, 0]
537+
538+
arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]
539+
540+
541+
542+
543+

0 commit comments

Comments
 (0)