From a75b15fa1d1d63da654476201f68dbfffe7e7f67 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 30 Oct 2016 20:51:07 -0700 Subject: [PATCH 1/5] Refactor the big `if` block in the lexer to be as minimal a change from `master` as we can get away with --- lib/coffee-script/lexer.js | 64 ++++++++++++++++++-------------------- src/lexer.coffee | 63 ++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 65 deletions(-) diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index a15f9eb79e..dfcdacebc0 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -105,42 +105,40 @@ } ref4 = this.tokens, prev = ref4[ref4.length - 1]; tag = colon || (prev != null) && (((ref5 = prev[0]) === '.' || ref5 === '?.' || ref5 === '::' || ref5 === '?::') || !prev.spaced && prev[0] === '@') ? 'PROPERTY' : 'IDENTIFIER'; - if (tag === 'IDENTIFIER') { - if (this.seenFor && id === 'from') { - tag = 'FORFROM'; - this.seenFor = false; - } else if ((indexOf.call(JS_KEYWORDS, id) >= 0 || indexOf.call(COFFEE_KEYWORDS, id) >= 0) && !(this.exportSpecifierList && indexOf.call(COFFEE_KEYWORDS, id) >= 0)) { - tag = id.toUpperCase(); - if (tag === 'WHEN' && (ref6 = this.tag(), indexOf.call(LINE_BREAK, ref6) >= 0)) { - tag = 'LEADING_WHEN'; - } else if (tag === 'FOR') { - this.seenFor = true; - } else if (tag === 'UNLESS') { - tag = 'IF'; - } else if (tag === 'IMPORT') { - this.seenImport = true; - } else if (tag === 'EXPORT') { - this.seenExport = true; - } else if (indexOf.call(UNARY, tag) >= 0) { - tag = 'UNARY'; - } else if (indexOf.call(RELATION, tag) >= 0) { - if (tag !== 'INSTANCEOF' && this.seenFor) { - tag = 'FOR' + tag; - this.seenFor = false; - } else { - tag = 'RELATION'; - if (this.value() === '!') { - poppedToken = this.tokens.pop(); - id = '!' + id; - } + if (tag === 'IDENTIFIER' && (indexOf.call(JS_KEYWORDS, id) >= 0 || indexOf.call(COFFEE_KEYWORDS, id) >= 0) && !(this.exportSpecifierList && indexOf.call(COFFEE_KEYWORDS, id) >= 0)) { + tag = id.toUpperCase(); + if (tag === 'WHEN' && (ref6 = this.tag(), indexOf.call(LINE_BREAK, ref6) >= 0)) { + tag = 'LEADING_WHEN'; + } else if (tag === 'FOR') { + this.seenFor = true; + } else if (tag === 'UNLESS') { + tag = 'IF'; + } else if (tag === 'IMPORT') { + this.seenImport = true; + } else if (tag === 'EXPORT') { + this.seenExport = true; + } else if (indexOf.call(UNARY, tag) >= 0) { + tag = 'UNARY'; + } else if (indexOf.call(RELATION, tag) >= 0) { + if (tag !== 'INSTANCEOF' && this.seenFor) { + tag = 'FOR' + tag; + this.seenFor = false; + } else { + tag = 'RELATION'; + if (this.value() === '!') { + poppedToken = this.tokens.pop(); + id = '!' + id; } } } - if (indexOf.call(RESERVED, id) >= 0) { - this.error("reserved word '" + id + "'", { - length: id.length - }); - } + } else if (id === 'from' && this.seenFor) { + tag = 'FORFROM'; + this.seenFor = false; + } + if (tag === 'IDENTIFIER' && indexOf.call(RESERVED, id) >= 0) { + this.error("reserved word '" + id + "'", { + length: id.length + }); } if (tag !== 'PROPERTY') { if (indexOf.call(COFFEE_ALIASES, id) >= 0) { diff --git a/src/lexer.coffee b/src/lexer.coffee index 8c454601ff..19ce9b8546 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -141,38 +141,37 @@ exports.Lexer = class Lexer else 'IDENTIFIER' - if tag is 'IDENTIFIER' - if @seenFor and id is 'from' - tag = 'FORFROM' - @seenFor = no - else if (id in JS_KEYWORDS or id in COFFEE_KEYWORDS) and - not (@exportSpecifierList and id in COFFEE_KEYWORDS) - tag = id.toUpperCase() - if tag is 'WHEN' and @tag() in LINE_BREAK - tag = 'LEADING_WHEN' - else if tag is 'FOR' - @seenFor = yes - else if tag is 'UNLESS' - tag = 'IF' - else if tag is 'IMPORT' - @seenImport = yes - else if tag is 'EXPORT' - @seenExport = yes - else if tag in UNARY - tag = 'UNARY' - else if tag in RELATION - if tag isnt 'INSTANCEOF' and @seenFor - tag = 'FOR' + tag - @seenFor = no - else - tag = 'RELATION' - if @value() is '!' - poppedToken = @tokens.pop() - id = '!' + id - - if id in RESERVED - @error "reserved word '#{id}'", length: id.length - + if tag is 'IDENTIFIER' and (id in JS_KEYWORDS or id in COFFEE_KEYWORDS) and + not (@exportSpecifierList and id in COFFEE_KEYWORDS) + tag = id.toUpperCase() + if tag is 'WHEN' and @tag() in LINE_BREAK + tag = 'LEADING_WHEN' + else if tag is 'FOR' + @seenFor = yes + else if tag is 'UNLESS' + tag = 'IF' + else if tag is 'IMPORT' + @seenImport = yes + else if tag is 'EXPORT' + @seenExport = yes + else if tag in UNARY + tag = 'UNARY' + else if tag in RELATION + if tag isnt 'INSTANCEOF' and @seenFor + tag = 'FOR' + tag + @seenFor = no + else + tag = 'RELATION' + if @value() is '!' + poppedToken = @tokens.pop() + id = '!' + id + else if id is 'from' and @seenFor + tag = 'FORFROM' + @seenFor = no + + if tag is 'IDENTIFIER' and id in RESERVED + @error "reserved word '#{id}'", length: id.length + unless tag is 'PROPERTY' if id in COFFEE_ALIASES alias = id From 910cc1f062197e3e0ac22add2045d4c7fe399d29 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Sun, 30 Oct 2016 21:05:57 -0700 Subject: [PATCH 2/5] Cleanup to make more idiomatic, remove trailing whitespace, minor performance improvements --- lib/coffee-script/nodes.js | 5 ++--- src/nodes.coffee | 26 +++++++++++++------------- test/arrays.coffee | 3 +-- test/compilation.coffee | 2 +- test/generators.coffee | 17 +++++++---------- test/ranges.coffee | 3 +-- 6 files changed, 25 insertions(+), 31 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index c7c85e8386..e3f9a5d1bf 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -3495,14 +3495,13 @@ if (this.own) { guardPart = "\n" + idt1 + "if (!" + (utility('hasProp', o)) + ".call(" + svar + ", " + kvar + ")) continue;"; } - } - if (this.from) { + } else if (this.from) { forPartFragments = [this.makeCode(kvar + " of " + svar)]; } bodyFragments = body.compileToFragments(merge(o, { indent: idt1 }), LEVEL_TOP); - if (bodyFragments && (bodyFragments.length > 0)) { + if (bodyFragments && bodyFragments.length > 0) { bodyFragments = [].concat(this.makeCode("\n"), bodyFragments, this.makeCode("\n")); } return [].concat(defPartFragments, this.makeCode("" + (resultPart || '') + this.tab + "for ("), forPartFragments, this.makeCode(") {" + guardPart + varPart), bodyFragments, this.makeCode(this.tab + "}" + (returnResult || ''))); diff --git a/src/nodes.coffee b/src/nodes.coffee index 1a2e1b8d4c..b6e9e29331 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2251,16 +2251,16 @@ exports.For = class For extends While @object = !!source.object @from = !!source.from @index.error 'cannot use index with for-from' if @from and @index - + [@name, @index] = [@index, @name] if @object - + @index.error 'index cannot be a pattern matching expression' if @index instanceof Value @range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from @pattern = @name instanceof Value @index.error 'indexes do not apply to range loops' if @range and @index @name.error 'cannot pattern match over range loops' if @range and @pattern @name.error 'cannot use own with for-in' if @own and not @object - + @returns = false children: ['body', 'source', 'guard', 'step'] @@ -2298,14 +2298,14 @@ exports.For = class For extends While forPartFragments = source.compileToFragments merge o, {index: ivar, name, @step, isComplex: isComplexOrAssignable} else - svar = @source.compile o, LEVEL_LIST + svar = @source.compile o, LEVEL_LIST if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral - defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n" - svar = ref - if not @from + defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n" + svar = ref + unless @from if name and not @pattern - namePart = "#{name} = #{svar}[#{kvar}]" - if not @object + namePart = "#{name} = #{svar}[#{kvar}]" + unless @object defPart += "#{@tab}#{step};\n" if step isnt stepVar down = stepNum < 0 lvar = scope.freeVariable 'len' unless @step and stepNum? and down @@ -2342,12 +2342,12 @@ exports.For = class For extends While defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body) varPart = "\n#{idt1}#{namePart};" if namePart if @object - forPartFragments = [@makeCode("#{kvar} in #{svar}")] + forPartFragments = [@makeCode("#{kvar} in #{svar}")] guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own - if @from - forPartFragments = [@makeCode("#{kvar} of #{svar}")] + else if @from + forPartFragments = [@makeCode("#{kvar} of #{svar}")] bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP - if bodyFragments and (bodyFragments.length > 0) + if bodyFragments and bodyFragments.length > 0 bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n") [].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("), forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments, diff --git a/test/arrays.coffee b/test/arrays.coffee index 6ecd371249..bffd512787 100644 --- a/test/arrays.coffee +++ b/test/arrays.coffee @@ -136,11 +136,10 @@ test "for-from loops over Array", -> eq 50, c test "for-from comprehensions over Array", -> - array1 = (x + 10 for x from [10, 20, 30]) ok array1.join(' ') is '20 30 40' - array2 = (x for x from [30, 41, 57] when x %% 3 == 0) + array2 = (x for x from [30, 41, 57] when x %% 3 is 0) ok array2.join(' ') is '30 57' array1 = (b + 5 for [a, b] from [[20, 30], [40, 50]]) diff --git a/test/compilation.coffee b/test/compilation.coffee index 0f95d6c4fb..5284e5b534 100644 --- a/test/compilation.coffee +++ b/test/compilation.coffee @@ -12,7 +12,7 @@ test "ensure that carriage returns don't break compilation on Windows", -> test "#3089 - don't mutate passed in options to compile", -> opts = {} CoffeeScript.compile '1 + 1', opts - ok !opts.scope + ok !opts.scope test "--bare", -> eq -1, CoffeeScript.compile('x = y', bare: on).indexOf 'function' diff --git a/test/generators.coffee b/test/generators.coffee index 0cfaadca34..7ae3ae9c6c 100644 --- a/test/generators.coffee +++ b/test/generators.coffee @@ -251,43 +251,40 @@ test "yield handles 'this' correctly", -> throws -> y.next new Error "boom" - test "for-from loops over generators", -> array1 = [50, 30, 70, 20] gen = -> yield from array1 - + array2 = [] array3 = [] array4 = [] - + iterator = gen() for x from iterator array2.push(x) break if x is 30 - + for x from iterator array3.push(x) - + for x from iterator array4.push(x) - + arrayEq array2, [50, 30] # Different JS engines have different opinions on the value of array3: # https://github.com/jashkenas/coffeescript/pull/4306#issuecomment-257066877 # As a temporary measure, either result is accepted. - ok array3.length == 0 or array3.join(',') == '70,20' + ok array3.length is 0 or array3.join(',') is '70,20' arrayEq array4, [] test "for-from comprehensions over generators", -> - gen = -> yield from [30, 41, 51, 60] iterator = gen() - array1 = (x for x from iterator when x %% 2 == 1) + array1 = (x for x from iterator when x %% 2 is 1) array2 = (x for x from iterator) ok array1.join(' ') is '41 51' ok array2.length is 0 - diff --git a/test/ranges.coffee b/test/ranges.coffee index f04d10522a..8de444a83d 100644 --- a/test/ranges.coffee +++ b/test/ranges.coffee @@ -79,8 +79,7 @@ test "for-from loops over ranges", -> array1 = [] for x from [20..30] array1.push(x) - if x == 25 - break + break if x is 25 arrayEq array1, [20, 21, 22, 23, 24, 25] test "for-from comprehensions over ranges", -> From c5afb4e2fd5138a5f74bbafb78741f300aa74361 Mon Sep 17 00:00:00 2001 From: Alan Pierce Date: Sun, 30 Oct 2016 19:28:47 -0700 Subject: [PATCH 3/5] Include generated } tokens when fixing closing token positions This is an upstream port of https://github.com/decaffeinate/coffeescript/pull/10 See that PR for links to the issues that this fixes. Just like OUTDENT and CALL_END tokens, close-curly-brace tokens can be generated without having a real location, and if that position overlaps with a later token, it can cause the AST to have bad location data. Just like the other two token types, we now give `}` tokens the position of the previous real token, which makes all AST nodes have reasonable locations. --- lib/coffee-script/rewriter.js | 2 +- src/rewriter.coffee | 3 ++- test/location.coffee | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/coffee-script/rewriter.js b/lib/coffee-script/rewriter.js index 41bcd4c240..804301dfd0 100644 --- a/lib/coffee-script/rewriter.js +++ b/lib/coffee-script/rewriter.js @@ -376,7 +376,7 @@ Rewriter.prototype.fixOutdentLocationData = function() { return this.scanTokens(function(token, i, tokens) { var prevLocationData; - if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END'))) { + if (!(token[0] === 'OUTDENT' || (token.generated && token[0] === 'CALL_END') || (token.generated && token[0] === '}'))) { return 1; } prevLocationData = tokens[i - 1][2]; diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 1870943677..04ab776aff 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -375,7 +375,8 @@ class exports.Rewriter fixOutdentLocationData: -> @scanTokens (token, i, tokens) -> return 1 unless token[0] is 'OUTDENT' or - (token.generated and token[0] is 'CALL_END') + (token.generated and token[0] is 'CALL_END') or + (token.generated and token[0] is '}') prevLocationData = tokens[i - 1][2] token[2] = first_line: prevLocationData.last_line diff --git a/test/location.coffee b/test/location.coffee index 2ffe951e63..c8548e237b 100644 --- a/test/location.coffee +++ b/test/location.coffee @@ -515,6 +515,27 @@ test "Verify OUTDENT and CALL_END tokens are located at the end of the previous eq token[0], 'CALL_END' assertAtCloseCurly(token) +test "Verify generated } tokens are located at the end of the previous token", -> + source = ''' + a(b, -> + c: () -> + if d + e + ) + ''' + tokens = CoffeeScript.tokens source + [..., identifier, outdent1, outdent2, closeCurly, outdent3, callEnd, + terminator] = tokens + eq identifier[0], 'IDENTIFIER' + assertAtIdentifier = (token) -> + eq token[2].first_line, identifier[2].last_line + eq token[2].first_column, identifier[2].last_column + eq token[2].last_line, identifier[2].last_line + eq token[2].last_column, identifier[2].last_column + + for token in [outdent1, outdent2, closeCurly, outdent3] + assertAtIdentifier(token) + test "Verify real CALL_END tokens have the right position", -> source = ''' a() From 98b03b989f7eb1198550c32f45aa6bfe1f9aa408 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 7 Nov 2016 21:37:04 -0800 Subject: [PATCH 4/5] Move "own is not supported in for-from loops" test into error_messages.coffee; improve error message so that "own" is underlined --- lib/coffee-script/grammar.js | 2 ++ lib/coffee-script/nodes.js | 6 +++--- lib/coffee-script/parser.js | 2 ++ src/grammar.coffee | 4 ++-- src/nodes.coffee | 2 +- test/compilation.coffee | 3 --- test/error_messages.coffee | 7 +++++++ 7 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/coffee-script/grammar.js b/lib/coffee-script/grammar.js index 7bde42a936..da0bb7121a 100644 --- a/lib/coffee-script/grammar.js +++ b/lib/coffee-script/grammar.js @@ -573,6 +573,7 @@ }; }), o('ForStart ForSource', function() { $2.own = $1.own; + $2.ownTag = $1.ownTag; $2.name = $1[0]; $2.index = $1[1]; return $2; @@ -583,6 +584,7 @@ return $2; }), o('FOR OWN ForVariables', function() { $3.own = true; + $3.ownTag = LOC(2)(new Literal($2)); return $3; }) ], diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index ac648612e7..46262e2730 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -3348,6 +3348,9 @@ if (this.from && this.index) { this.index.error('cannot use index with for-from'); } + if (this.own && !this.object) { + source.ownTag.error("cannot use own with for-" + (this.from ? 'from' : 'in')); + } if (this.object) { ref3 = [this.index, this.name], this.name = ref3[0], this.index = ref3[1]; } @@ -3362,9 +3365,6 @@ if (this.range && this.pattern) { this.name.error('cannot pattern match over range loops'); } - if (this.own && !this.object) { - this.name.error("cannot use own with for-" + (this.from ? 'from' : 'in')); - } this.returns = false; } diff --git a/lib/coffee-script/parser.js b/lib/coffee-script/parser.js index 7bf123a4ae..8a373e446f 100755 --- a/lib/coffee-script/parser.js +++ b/lib/coffee-script/parser.js @@ -522,6 +522,7 @@ break; case 212: this.$ = yy.addLocationDataFn(_$[$0-1], _$[$0])((function () { $$[$0].own = $$[$0-1].own; + $$[$0].ownTag = $$[$0-1].ownTag; $$[$0].name = $$[$0-1][0]; $$[$0].index = $$[$0-1][1]; return $$[$0]; @@ -533,6 +534,7 @@ break; case 214: this.$ = yy.addLocationDataFn(_$[$0-2], _$[$0])((function () { $$[$0].own = true; + $$[$0].ownTag = yy.addLocationDataFn(_$[$0-1])(new yy.Literal($$[$0-1])); return $$[$0]; }())); break; diff --git a/src/grammar.coffee b/src/grammar.coffee index bbdbc2529b..0c46a511af 100644 --- a/src/grammar.coffee +++ b/src/grammar.coffee @@ -560,12 +560,12 @@ grammar = ForBody: [ o 'FOR Range', -> source: (LOC(2) new Value($2)) o 'FOR Range BY Expression', -> source: (LOC(2) new Value($2)), step: $4 - o 'ForStart ForSource', -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2 + o 'ForStart ForSource', -> $2.own = $1.own; $2.ownTag = $1.ownTag; $2.name = $1[0]; $2.index = $1[1]; $2 ] ForStart: [ o 'FOR ForVariables', -> $2 - o 'FOR OWN ForVariables', -> $3.own = yes; $3 + o 'FOR OWN ForVariables', -> $3.own = yes; $3.ownTag = (LOC(2) new Literal($2)); $3 ] # An array of all accepted values for a variable inside the loop. diff --git a/src/nodes.coffee b/src/nodes.coffee index 5129c12100..48820091e3 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2251,6 +2251,7 @@ exports.For = class For extends While @object = !!source.object @from = !!source.from @index.error 'cannot use index with for-from' if @from and @index + source.ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object [@name, @index] = [@index, @name] if @object @@ -2259,7 +2260,6 @@ exports.For = class For extends While @pattern = @name instanceof Value @index.error 'indexes do not apply to range loops' if @range and @index @name.error 'cannot pattern match over range loops' if @range and @pattern - @name.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object @returns = false children: ['body', 'source', 'guard', 'step'] diff --git a/test/compilation.coffee b/test/compilation.coffee index a50c63a17f..4bc70f776a 100644 --- a/test/compilation.coffee +++ b/test/compilation.coffee @@ -123,6 +123,3 @@ test "#3001: `own` shouldn't be allowed in a `for`-`in` loop", -> test "#2994: single-line `if` requires `then`", -> cantCompile "if b else x" - -test "own is not supported in for-from loops", -> - cantCompile "x for own x from [1, 2, 3]" diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 635b91394f..e90f7de2d8 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -1181,3 +1181,10 @@ test "indexes are not supported in for-from loops", -> x for x, i from [1, 2, 3] ^ ''' + +test "own is not supported in for-from loops", -> + assertErrorFormat "x for own x from [1, 2, 3]", ''' + [stdin]:1:7: error: cannot use own with for-from + x for own x from [1, 2, 3] + ^^^ + ''' From 1f12f362b1277c73276b8a81b9693d4bfb79929f Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 7 Nov 2016 21:52:26 -0800 Subject: [PATCH 5/5] Revert unnecessary changes, to minimize the lines of code modified by this PR --- lib/coffee-script/nodes.js | 58 +++++++++++++++++--------------------- src/nodes.coffee | 56 ++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 63 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index 46262e2730..3eb67dbd5f 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -3431,38 +3431,36 @@ defPart += "" + this.tab + (ref = scope.freeVariable('ref')) + " = " + svar + ";\n"; svar = ref; } - if (!this.from) { - if (name && !this.pattern) { - namePart = name + " = " + svar + "[" + kvar + "]"; + if (name && !this.pattern && !this.from) { + namePart = name + " = " + svar + "[" + kvar + "]"; + } + if (!this.object && !this.from) { + if (step !== stepVar) { + defPart += "" + this.tab + step + ";\n"; } - if (!this.object) { - if (step !== stepVar) { - defPart += "" + this.tab + step + ";\n"; - } - down = stepNum < 0; - if (!(this.step && (stepNum != null) && down)) { - lvar = scope.freeVariable('len'); - } - declare = "" + kvarAssign + ivar + " = 0, " + lvar + " = " + svar + ".length"; - declareDown = "" + kvarAssign + ivar + " = " + svar + ".length - 1"; - compare = ivar + " < " + lvar; - compareDown = ivar + " >= 0"; - if (this.step) { - if (stepNum != null) { - if (down) { - compare = compareDown; - declare = declareDown; - } - } else { - compare = stepVar + " > 0 ? " + compare + " : " + compareDown; - declare = "(" + stepVar + " > 0 ? (" + declare + ") : " + declareDown + ")"; + down = stepNum < 0; + if (!(this.step && (stepNum != null) && down)) { + lvar = scope.freeVariable('len'); + } + declare = "" + kvarAssign + ivar + " = 0, " + lvar + " = " + svar + ".length"; + declareDown = "" + kvarAssign + ivar + " = " + svar + ".length - 1"; + compare = ivar + " < " + lvar; + compareDown = ivar + " >= 0"; + if (this.step) { + if (stepNum != null) { + if (down) { + compare = compareDown; + declare = declareDown; } - increment = ivar + " += " + stepVar; } else { - increment = "" + (kvar !== ivar ? "++" + ivar : ivar + "++"); + compare = stepVar + " > 0 ? " + compare + " : " + compareDown; + declare = "(" + stepVar + " > 0 ? (" + declare + ") : " + declareDown + ")"; } - forPartFragments = [this.makeCode(declare + "; " + compare + "; " + kvarAssign + increment)]; + increment = ivar + " += " + stepVar; + } else { + increment = "" + (kvar !== ivar ? "++" + ivar : ivar + "++"); } + forPartFragments = [this.makeCode(declare + "; " + compare + "; " + kvarAssign + increment)]; } } if (this.returns) { @@ -3480,11 +3478,7 @@ } } if (this.pattern) { - if (this.from) { - body.expressions.unshift(new Assign(this.name, new IdentifierLiteral(kvar))); - } else { - body.expressions.unshift(new Assign(this.name, new Literal(svar + "[" + kvar + "]"))); - } + body.expressions.unshift(new Assign(this.name, this.from ? new IdentifierLiteral(kvar) : new Literal(svar + "[" + kvar + "]"))); } defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body)); if (namePart) { diff --git a/src/nodes.coffee b/src/nodes.coffee index 48820091e3..696dcbc1bc 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -2252,9 +2252,7 @@ exports.For = class For extends While @from = !!source.from @index.error 'cannot use index with for-from' if @from and @index source.ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object - [@name, @index] = [@index, @name] if @object - @index.error 'index cannot be a pattern matching expression' if @index instanceof Value @range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from @pattern = @name instanceof Value @@ -2297,33 +2295,32 @@ exports.For = class For extends While forPartFragments = source.compileToFragments merge o, {index: ivar, name, @step, isComplex: isComplexOrAssignable} else - svar = @source.compile o, LEVEL_LIST + svar = @source.compile o, LEVEL_LIST if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral - defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n" - svar = ref - unless @from - if name and not @pattern - namePart = "#{name} = #{svar}[#{kvar}]" - unless @object - defPart += "#{@tab}#{step};\n" if step isnt stepVar - down = stepNum < 0 - lvar = scope.freeVariable 'len' unless @step and stepNum? and down - declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length" - declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1" - compare = "#{ivar} < #{lvar}" - compareDown = "#{ivar} >= 0" - if @step - if stepNum? - if down - compare = compareDown - declare = declareDown - else - compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}" - declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})" - increment = "#{ivar} += #{stepVar}" + defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n" + svar = ref + if name and not @pattern and not @from + namePart = "#{name} = #{svar}[#{kvar}]" + if not @object and not @from + defPart += "#{@tab}#{step};\n" if step isnt stepVar + down = stepNum < 0 + lvar = scope.freeVariable 'len' unless @step and stepNum? and down + declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length" + declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1" + compare = "#{ivar} < #{lvar}" + compareDown = "#{ivar} >= 0" + if @step + if stepNum? + if down + compare = compareDown + declare = declareDown else - increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}" - forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")] + compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}" + declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})" + increment = "#{ivar} += #{stepVar}" + else + increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}" + forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")] if @returns resultPart = "#{@tab}#{rvar} = [];\n" returnResult = "\n#{@tab}return #{rvar};" @@ -2334,10 +2331,7 @@ exports.For = class For extends While else body = Block.wrap [new If @guard, body] if @guard if @pattern - if @from - body.expressions.unshift new Assign @name, new IdentifierLiteral kvar - else - body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]" + body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]" defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body) varPart = "\n#{idt1}#{namePart};" if namePart if @object