From 5005cb606f510802895c56a7cbca5cf40b3dcae9 Mon Sep 17 00:00:00 2001 From: Trevor Burnham Date: Tue, 19 Oct 2010 14:09:16 -0400 Subject: [PATCH] Fixing inconsistencies for index variables See the tests added to test_comprehensions.coffee. Previously, after `for i in [1..3]`, i was 4. Also, index variables were never set to any value in comprehensions containing both a closure and a break or return. --- lib/browser.js | 2 + lib/cake.js | 4 ++ lib/command.js | 8 ++++ lib/grammar.js | 6 +++ lib/helpers.js | 8 ++++ lib/index.js | 2 + lib/lexer.js | 8 ++++ lib/nodes.js | 66 +++++++++++++++++++++++++++++++-- lib/optparse.js | 12 ++++++ lib/rewriter.js | 10 +++++ lib/scope.js | 8 ++++ src/nodes.coffee | 10 ++++- test/test_comprehensions.coffee | 11 +++++- 13 files changed, 148 insertions(+), 7 deletions(-) diff --git a/lib/browser.js b/lib/browser.js index c35d5feb..e958a538 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -36,6 +36,8 @@ })(); script = _j; } + + return null; }; if (window.addEventListener) { diff --git a/lib/cake.js b/lib/cake.js index 770a648b..4bddc356 100755 --- a/lib/cake.js +++ b/lib/cake.js @@ -51,6 +51,8 @@ arg = _ref[_i]; _result.push(invoke(arg)); } + + return _result; }); }; @@ -64,6 +66,8 @@ desc = task.description ? ("# " + (task.description)) : ''; puts("cake " + name + spaces + " " + desc); } + + return switches.length ? puts(oparse.help()) : undefined; }; missingTask = function(task) { diff --git a/lib/command.js b/lib/command.js index 58a27745..d53c62b3 100644 --- a/lib/command.js +++ b/lib/command.js @@ -70,6 +70,8 @@ file = files[_k]; _result2.push(compile(path.join(source, file))); } + + return _result2; }); } else if (topLevel || path.extname(source) === '.coffee') { @@ -86,6 +88,8 @@ })(); source = _j; } + + return _result; }; compileScript = function(file, input, base) { @@ -97,6 +101,8 @@ req = _ref2[_i]; require(helpers.starts(req, '.') ? fs.realpathSync(req) : req); } + + } try { t = (task = { @@ -197,6 +203,8 @@ return "[" + tag + " " + value + "]"; })()); } + + return _result; })(); return puts(strings.join(' ')); diff --git a/lib/grammar.js b/lib/grammar.js index e038a740..3cc01e98 100644 --- a/lib/grammar.js +++ b/lib/grammar.js @@ -604,15 +604,21 @@ tokens.push(token); } } + + if (name === 'Root') { alt[1] = ("return " + (alt[1])); } return alt; })()); } + + return _result; })(); } + + exports.parser = new Parser({ tokens: tokens.join(' '), bnf: grammar, diff --git a/lib/helpers.js b/lib/helpers.js index 5983bf6d..31eb8e3c 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -10,6 +10,8 @@ return index; } } + + return -1; })); exports.include = function(list, value) { @@ -32,6 +34,8 @@ _result.push(item); } } + + return _result; }; exports.count = function(string, letter) { @@ -51,6 +55,8 @@ val = properties[key]; object[key] = val; } + + return object; }); exports.flatten = (flatten = function(array) { @@ -64,6 +70,8 @@ flattened.push(element); } } + + return flattened; }); exports.del = function(obj, key) { diff --git a/lib/index.js b/lib/index.js index dc5ab13c..ec88a8c0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,4 +6,6 @@ val = _ref[key]; (exports[key] = val); } + + }).call(this); diff --git a/lib/lexer.js b/lib/lexer.js index ce25616b..c8c5f0bb 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -226,6 +226,8 @@ } tokens.push(['+', '+']); } + + tokens.pop(); if ((((_ref4 = tokens[0]) != null) ? _ref4[0] : undefined) !== 'STRING') { this.tokens.push(['STRING', '""'], ['+', '+']); @@ -480,6 +482,8 @@ break; } } + + } if (!levels.length) { break; @@ -550,6 +554,8 @@ this.token('STRING', this.makeString(value, '"', heredoc)); } } + + if (interpolated) { this.token(')', ')'); } @@ -600,6 +606,8 @@ })) { COFFEE_KEYWORDS.push(op); } + + RESERVED = ['case', 'default', 'do', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice']; JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED); IDENTIFIER = /^([$A-Za-z_][$\w]*)([^\n\S]*:(?!:))?/; diff --git a/lib/nodes.js b/lib/nodes.js index 097780a6..7df413f1 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -58,6 +58,8 @@ node = pair[i]; (pair[i] = node.compile(o)); } + + } return pair; }; @@ -106,6 +108,8 @@ child = _ref2[_i]; _result.push(child.toString(idt + TAB)); } + + return _result; }).call(this).join(''); klass = override || this.constructor.name + (this.soakNode ? '?' : ''); @@ -126,8 +130,12 @@ return; } } + + } } + + return _result; }; Base.prototype.collectChildren = function() { @@ -208,6 +216,8 @@ node = _ref2[_i]; _result.push(this.compileExpression(node, merge(o))); } + + return _result; }).call(this).join('\n'); }; @@ -411,6 +421,8 @@ prop = props[_i]; (code += prop.compile(o)); } + + return code; }; Value.prototype.unfoldSoak = function(o) { @@ -436,6 +448,8 @@ return ifn; } } + + return null; }; return Value; @@ -542,6 +556,8 @@ } ifn = If.unfoldSoak(o, call, 'variable'); } + + return ifn; }; Call.prototype.compileNode = function(o) { @@ -556,12 +572,16 @@ return this.compileSplat(o); } } + + args = (function() { _result = []; for (_j = 0, _len2 = (_ref4 = this.args).length; _j < _len2; _j++) { arg = _ref4[_j]; _result.push((arg.parenthetical = true) && arg.compile(o)); } + + return _result; }).call(this).join(', '); return this.isSuper ? this.compileSuper(args, o) : ("" + (this.prefix()) + (this.variable.compile(o)) + "(" + args + ")"); @@ -792,6 +812,8 @@ _result.push(prop); } } + + return _result; }).call(this); lastNoncom = last(nonComments); @@ -810,6 +832,8 @@ return indent + prop.compile(o) + join; }).call(this)); } + + return _result; }).call(this); props = props.join(''); @@ -824,6 +848,8 @@ return true; } } + + return false; }; return ObjectLiteral; @@ -852,12 +878,16 @@ return this.compileSplatLiteral(o); } } + + objects = []; for (i = 0, _len2 = (_ref3 = this.objects).length; i < _len2; i++) { obj = _ref3[i]; code = obj.compile(o); objects.push(obj instanceof Comment ? ("\n" + code + "\n" + (o.indent)) : (i === this.objects.length - 1 ? code : code + ', ')); } + + objects = objects.join(''); return 0 < objects.indexOf('\n') ? ("[\n" + (o.indent) + objects + "\n" + (this.tab) + "]") : ("[" + objects + "]"); }; @@ -869,6 +899,8 @@ return true; } } + + return false; }; return ArrayLiteral; @@ -957,6 +989,8 @@ } props.push(prop); } + + constructor.className = className.match(/[\w\d\$_]+$/); if (me) { constructor.body.unshift(new Literal("" + me + " = this")); @@ -1082,6 +1116,8 @@ } assigns.push(new Assign(obj, val).compile(otop)); } + + if (!top) { assigns.push(valVar); } @@ -1159,6 +1195,8 @@ } } } + + o.scope.startLevel(); params = (function() { _result = []; @@ -1166,6 +1204,8 @@ param = params[_i]; _result.push(param.compile(o)); } + + return _result; })(); if (!(empty || this.noReturn)) { @@ -1175,6 +1215,8 @@ param = params[_i]; (o.scope.parameter(param)); } + + comm = this.comment ? this.comment.compile(o) + '\n' : ''; if (this.className) { o.indent = this.idt(2); @@ -1262,6 +1304,8 @@ pos = this.trailings.length - idx; o.scope.assign(trailing.compile(o), "arguments[" + variadic + " ? " + len + " - " + pos + " : " + (this.index + idx) + "]"); } + + } return "" + name + " = " + (utility('slice')) + ".call(arguments, " + (this.index) + end + ")"; }; @@ -1291,6 +1335,8 @@ } args[++end] = i === 0 ? code : (".concat(" + code + ")"); } + + return args.join(''); }; return Splat; @@ -1492,6 +1538,8 @@ item = _ref2[i]; _result.push("" + (i ? this.obj2 : this.obj1) + " === " + (item.compile(o))); } + + return _result; }).call(this); return "(" + (tests.join(' || ')) + ")"; @@ -1657,7 +1705,7 @@ return ''; }; For.prototype.compileNode = function(o) { - var body, codeInBody, forPart, guardPart, idt1, index, ivar, lastLine, lvar, name, namePart, nvar, range, ref, resultPart, returnResult, rvar, scope, source, sourcePart, stepPart, svar, topLevel, varPart, vars; + var body, codeInBody, forPart, guardPart, idt1, index, ivar, lastLine, lvar, name, namePart, nvar, range, ref, resultPart, returnResult, rvar, scope, source, sourcePart, stepPart, svar, topLevel, unstepPart, varPart, vars; topLevel = del(o, 'top') && !this.returns; range = this.source instanceof Value && this.source.base instanceof Range && !this.source.properties.length; source = range ? this.source.base : this.source; @@ -1689,6 +1737,7 @@ } varPart = ''; guardPart = ''; + unstepPart = ''; body = Expressions.wrap([this.body]); idt1 = this.idt(1); if (range) { @@ -1724,10 +1773,12 @@ if (this.guard) { body = Expressions.wrap([new If(this.guard, body)]); } - if (codeInBody && !body.containsPureStatement()) { + if (codeInBody) { if (range) { body.unshift(new Literal("var " + name + " = " + ivar)); } + } + if (codeInBody && !body.containsPureStatement()) { if (namePart) { body.unshift(new Literal("var " + namePart)); } @@ -1754,6 +1805,9 @@ if (namePart) { varPart = ("" + idt1 + namePart + ";\n"); } + if (forPart && name === ivar) { + unstepPart = this.step ? ("\n" + name + " -= " + (this.step.compile(o)) + ";") : ("\n" + name + "--;"); + } } if (this.object) { forPart = ("" + ivar + " in " + sourcePart); @@ -1766,7 +1820,7 @@ top: true })); vars = range ? name : ("" + name + ", " + ivar); - return "" + resultPart + (this.tab) + "for (" + forPart + ") {" + guardPart + "\n" + varPart + body + "\n" + (this.tab) + "}" + returnResult; + return "" + resultPart + (this.tab) + "for (" + forPart + ") {" + guardPart + "\n" + varPart + body + "\n" + (this.tab) + "}\n" + unstepPart + "\n" + returnResult; }; return For; })(); @@ -1792,6 +1846,8 @@ pair = _ref2[_i]; pair[1].makeReturn(); } + + if (this.otherwise) { this.otherwise.makeReturn(); } @@ -1813,11 +1869,15 @@ } code += ("\n" + (this.idt(1)) + "case " + (condition.compile(o)) + ":"); } + + code += ("\n" + (block.compile(o))); if (!(last(exprs) instanceof Return)) { code += ("\n" + idt + "break;"); } } + + if (this.otherwise) { code += ("\n" + (this.idt(1)) + "default:\n" + (this.otherwise.compile(o))); } diff --git a/lib/optparse.js b/lib/optparse.js index 4a9ae283..4bb7dc2e 100755 --- a/lib/optparse.js +++ b/lib/optparse.js @@ -28,6 +28,8 @@ break; } } + + if (isOption && !matchedRule) { throw new Error("unrecognized option: " + arg); } @@ -36,6 +38,8 @@ break; } } + + return options; }; OptionParser.prototype.help = function() { @@ -51,6 +55,8 @@ letPart = rule.shortFlag ? rule.shortFlag + ', ' : ' '; lines.push(' ' + letPart + rule.longFlag + spaces + rule.description); } + + return "\n" + (lines.join('\n')) + "\n"; }; return OptionParser; @@ -71,6 +77,8 @@ return buildRule.apply(buildRule, tuple); })()); } + + return _result; }; buildRule = function(shortFlag, longFlag, description, options) { @@ -98,10 +106,14 @@ l = _ref[_j]; result.push('-' + l); } + + } else { result.push(arg); } } + + return result; }; }).call(this); diff --git a/lib/rewriter.js b/lib/rewriter.js index 6999fc88..424cd06a 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -90,6 +90,8 @@ break; } } + + return i ? this.tokens.splice(0, i) : undefined; }; exports.Rewriter.prototype.removeMidExpressionNewlines = function() { @@ -296,6 +298,8 @@ throw Error("too many " + (token[1]) + " on line " + (token[2] + 1)); } } + + return 1; }); unclosed = (function() { @@ -306,6 +310,8 @@ _result.push(key); } } + + return _result; })(); if (unclosed.length) { @@ -319,6 +325,8 @@ for (key in INVERSES) { (debt[key] = 0); } + + return this.scanTokens(function(token, i, tokens) { var inv, match, mtag, oppos, tag, val; if (include(EXPRESSION_START, tag = token[0])) { @@ -366,6 +374,8 @@ EXPRESSION_START.push(INVERSES[rite] = left); EXPRESSION_END.push(INVERSES[left] = rite); } + + EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END); IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']; IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'UNLESS', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'UNARY', '@', '->', '=>', '[', '(', '{', '--', '++']; diff --git a/lib/scope.js b/lib/scope.js index b0f2a648..90833293 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -35,6 +35,8 @@ _result.push(vars[name] = 'reuse'); } } + + return _result; }; Scope.prototype.find = function(name, options) { @@ -53,6 +55,8 @@ return true; } } + + return false; }; Scope.prototype.parameter = function(name) { @@ -108,6 +112,8 @@ _result.push(key); } } + + return _result; }).call(this).sort(); }; @@ -121,6 +127,8 @@ _result.push("" + key + " = " + (val.value)); } } + + return _result; }; Scope.prototype.compiledDeclarations = function() { diff --git a/src/nodes.coffee b/src/nodes.coffee index c2561aa6..4fef55c4 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -1428,6 +1428,7 @@ exports.For = class For extends Base nvar = scope.freeVariable 'i' if name and not range and codeInBody varPart = '' guardPart = '' + unstepPart = '' body = Expressions.wrap([@body]) idt1 = @idt 1 if range @@ -1452,8 +1453,9 @@ exports.For = class For extends Base body = Push.wrap(rvar, body) unless topLevel if @guard body = Expressions.wrap([new If(@guard, body)]) - if codeInBody and not body.containsPureStatement() + if codeInBody body.unshift new Literal "var #{name} = #{ivar}" if range + if codeInBody and not body.containsPureStatement() body.unshift new Literal "var #{namePart}" if namePart body.unshift new Literal "var #{index} = #{ivar}" if index lastLine = body.expressions.pop() @@ -1466,6 +1468,8 @@ exports.For = class For extends Base body.push new Assign new Literal(name), new Literal nvar or ivar if name else varPart = "#{idt1}#{namePart};\n" if namePart + if forPart and name is ivar + unstepPart = if @step then "\n#{name} -= #{ @step.compile(o) };" else "\n#{name}--;" if @object forPart = "#{ivar} in #{sourcePart}" guardPart = "\n#{idt1}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" unless @raw @@ -1474,7 +1478,9 @@ exports.For = class For extends Base """ #{resultPart}#{@tab}for (#{forPart}) {#{guardPart} #{varPart}#{body} - #{@tab}}#{returnResult} + #{@tab}} + #{unstepPart} + #{returnResult} """ #### Switch diff --git a/test/test_comprehensions.coffee b/test/test_comprehensions.coffee index d122256f..2aff0bff 100644 --- a/test/test_comprehensions.coffee +++ b/test/test_comprehensions.coffee @@ -23,6 +23,8 @@ negs = negs[0..2] result = nums.concat(negs).join(', ') ok result is '3, 6, 9, -20, -19, -18' +ok i is 3 +ok x is -10 # With range comprehensions, you can loop in steps. @@ -70,17 +72,22 @@ ok obj.one() is "I'm one" ok obj.two() is "I'm two" ok obj.three() is "I'm three" +i = 0 +for i in [1..3] + -> 'func' + break if false +ok i is 3 + # Ensure that local variables are closed over for range comprehensions. funcs = for i in [1..3] -> -i ok (func() for func in funcs).join(' ') is '-1 -2 -3' +ok i is 3 # Ensure that closing over local variables doesn't break scoping laws. -ok i is 3 - for i in [0] count = 0 i = 50