From fa63288f524353bcb037252d57612e240cb5489c Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Mon, 15 Feb 2010 23:05:54 -0500 Subject: [PATCH] fixed the while-loop-condition-with-implicit-function-call bug --- lib/coffee_script/rewriter.js | 38 ++++++++++++++--------- src/rewriter.coffee | 29 ++++++++++------- test/fixtures/execution/test_while.coffee | 13 +++++++- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/lib/coffee_script/rewriter.js b/lib/coffee_script/rewriter.js index 0615e706..1d3e6780 100644 --- a/lib/coffee_script/rewriter.js +++ b/lib/coffee_script/rewriter.js @@ -1,5 +1,5 @@ (function(){ - var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, _1, _2, _3, _4, _5, _6, _7, _8, pair, re; + var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_START, EXPRESSION_TAIL, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, SINGLE_CLOSERS, SINGLE_LINERS, _1, _2, _3, _4, _5, _6, _7, _8, pair, re; var __hasProp = Object.prototype.hasOwnProperty; if (!((typeof process !== "undefined" && process !== null))) { this.exports = this; @@ -32,7 +32,8 @@ EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL); // Tokens pairs that, in immediate succession, indicate an implicit call. IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END']; - IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT']; + IMPLICIT_BLOCK = ['->', '=>', '{', '[', ',']; + IMPLICIT_END = ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDENT']; IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', '@', '->', '=>', '[', '(', '{']; // The inverse mappings of token pairs we're trying to fix up. INVERSES = {}; @@ -193,25 +194,32 @@ stack = [0]; return this.scan_tokens((function(__this) { var __func = function(prev, token, post, i) { - var _10, _11, _12, _9, idx, last, size, tmp; - if (token[0] === 'INDENT') { + var _10, _11, _12, _9, idx, last, size, stack_pointer, tag, tmp; + tag = token[0]; + if (tag === 'INDENT') { stack.push(0); } - if (token[0] === 'OUTDENT') { + if (tag === 'OUTDENT') { last = stack.pop(); stack[stack.length - 1] += last; } - if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || !(typeof post !== "undefined" && post !== null))) { - idx = token[0] === 'OUTDENT' ? i + 1 : i; - _11 = 0; _12 = stack[stack.length - 1]; - for (_10=0, tmp=_11; (_11 <= _12 ? tmp < _12 : tmp > _12); (_11 <= _12 ? tmp += 1 : tmp -= 1), _10++) { - this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]); + if (IMPLICIT_END.indexOf(tag) >= 0 || !(typeof post !== "undefined" && post !== null)) { + if (tag === 'INDENT' && prev && IMPLICIT_BLOCK.indexOf(prev[0]) >= 0) { + return 1; + } + if (stack[stack.length - 1] > 0 || tag === 'INDENT') { + idx = tag === 'OUTDENT' ? i + 1 : i; + stack_pointer = tag === 'INDENT' ? 2 : 1; + _11 = 0; _12 = stack[stack.length - stack_pointer]; + for (_10=0, tmp=_11; (_11 <= _12 ? tmp < _12 : tmp > _12); (_11 <= _12 ? tmp += 1 : tmp -= 1), _10++) { + this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]); + } + size = stack[stack.length - stack_pointer] + 1; + stack[stack.length - stack_pointer] = 0; + return size; } - size = stack[stack.length - 1] + 1; - stack[stack.length - 1] = 0; - return size; } - if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(token[0]) >= 0)) { + if (!(prev && IMPLICIT_FUNC.indexOf(prev[0]) >= 0 && IMPLICIT_CALL.indexOf(tag) >= 0)) { return 1; } this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]); @@ -306,7 +314,7 @@ return _9; }).call(this); if (unclosed.length) { - throw "unclosed " + unclosed[0]; + throw new Error("unclosed " + unclosed[0]); } }; // We'd like to support syntax like this: diff --git a/src/rewriter.coffee b/src/rewriter.coffee index 5b4bb112..ece7a35b 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -20,7 +20,8 @@ EXPRESSION_CLOSE: ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_TAIL) # Tokens pairs that, in immediate succession, indicate an implicit call. IMPLICIT_FUNC: ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END'] -IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'OUTDENT'] +IMPLICIT_BLOCK:['->', '=>', '{', '[', ','] +IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'TERMINATOR', 'INDENT', 'OUTDENT'] IMPLICIT_CALL: ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'TRY', 'DELETE', 'TYPEOF', 'SWITCH', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '!', '!!', 'NOT', @@ -140,18 +141,22 @@ re::close_open_calls_and_indexes: -> re::add_implicit_parentheses: -> stack: [0] @scan_tokens (prev, token, post, i) => - stack.push(0) if token[0] is 'INDENT' - if token[0] is 'OUTDENT' + tag: token[0] + stack.push(0) if tag is 'INDENT' + if tag is 'OUTDENT' last: stack.pop() stack[stack.length - 1] += last - if stack[stack.length - 1] > 0 and (IMPLICIT_END.indexOf(token[0]) >= 0 or !post?) - idx: if token[0] is 'OUTDENT' then i + 1 else i - for tmp in [0...stack[stack.length - 1]] - @tokens.splice(idx, 0, ['CALL_END', ')', token[2]]) - size: stack[stack.length - 1] + 1 - stack[stack.length - 1]: 0 - return size - return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(token[0]) >= 0 + if IMPLICIT_END.indexOf(tag) >= 0 or !post? + return 1 if tag is 'INDENT' and prev and IMPLICIT_BLOCK.indexOf(prev[0]) >= 0 + if stack[stack.length - 1] > 0 or tag is 'INDENT' + idx: if tag is 'OUTDENT' then i + 1 else i + stack_pointer: if tag is 'INDENT' then 2 else 1 + for tmp in [0...stack[stack.length - stack_pointer]] + @tokens.splice(idx, 0, ['CALL_END', ')', token[2]]) + size: stack[stack.length - stack_pointer] + 1 + stack[stack.length - stack_pointer]: 0 + return size + return 1 unless prev and IMPLICIT_FUNC.indexOf(prev[0]) >= 0 and IMPLICIT_CALL.indexOf(tag) >= 0 @tokens.splice(i, 0, ['CALL_START', '(', token[2]]) stack[stack.length - 1] += 1 return 2 @@ -196,7 +201,7 @@ re::ensure_balance: (pairs) -> throw "too many " + token[1] if levels[open] < 0 return 1 unclosed: key for key, value of levels when value > 0 - throw "unclosed " + unclosed[0] if unclosed.length + throw new Error("unclosed " + unclosed[0]) if unclosed.length # We'd like to support syntax like this: # el.click((event) -> diff --git a/test/fixtures/execution/test_while.coffee b/test/fixtures/execution/test_while.coffee index e44a061f..0711e1ae 100644 --- a/test/fixtures/execution/test_while.coffee +++ b/test/fixtures/execution/test_while.coffee @@ -14,4 +14,15 @@ puts list.join(' ') is "8 6 4 2" i: 5 list: (i * 3 while i -= 1) -puts list.join(' ') is "12 9 6 3" \ No newline at end of file +puts list.join(' ') is "12 9 6 3" + + +i: 5 +func: (num) -> i -= num +assert: -> puts i < 5 > 0 + +results: while func 1 + assert() + i + +puts results.join(' ') is '4 3 2 1' \ No newline at end of file