diff --git a/lib/coffee-script/lexer.js b/lib/coffee-script/lexer.js index edf5c9f6f1..68dcf19fcd 100644 --- a/lib/coffee-script/lexer.js +++ b/lib/coffee-script/lexer.js @@ -22,6 +22,7 @@ this.indebt = 0; this.outdebt = 0; this.indents = []; + this.oldDebts = []; this.ends = []; this.tokens = []; this.chunkLine = opts.line || 0; @@ -347,7 +348,13 @@ this.seenFor = false; size = indent.length - 1 - indent.lastIndexOf('\n'); noNewlines = this.unfinished(); - if (size - this.indebt === this.indent) { + if (size - this.indebt === this.indent || size + this.outdebt === this.indent) { + if (size - this.indebt === this.indent) { + this.outdebt = 0; + } + if (size + this.outdebt === this.indent) { + this.indebt = 0; + } if (noNewlines) { this.suppressNewlines(); } else { @@ -361,42 +368,50 @@ this.suppressNewlines(); return indent.length; } - diff = size - this.indent + this.outdebt; + diff = size - this.indent; this.token('INDENT', diff, 0, indent.length); + if (this.indebt) { + this.oldDebts.push(this.indebt); + } else if (this.outdebt) { + this.oldDebts.push(-this.outdebt); + } else { + this.oldDebts.push(0); + } this.indents.push(diff); this.ends.push('OUTDENT'); this.outdebt = this.indebt = 0; + this.indent = size; } else { this.indebt = 0; this.outdentToken(this.indent - size, noNewlines, indent.length); } - this.indent = size; return indent.length; }; Lexer.prototype.outdentToken = function(moveOut, noNewlines, outdentLength) { - var dent, len; + var indent, oldDebt; while (moveOut > 0) { - len = this.indents.length - 1; - if (this.indents[len] === void 0) { + indent = last(this.indents); + oldDebt = last(this.oldDebts); + if (indent == null) { + break; + } + if (indent > moveOut && (!(oldDebt > 0) || indent - moveOut !== oldDebt)) { + break; + } + this.indents.pop(); + this.oldDebts.pop(); + moveOut -= indent; + this.indent -= indent; + this.pair('OUTDENT'); + this.token('OUTDENT', indent, 0, outdentLength); + if (oldDebt > 0 && indent + moveOut === oldDebt) { + this.indebt = oldDebt; moveOut = 0; - } else if (this.indents[len] === this.outdebt) { - moveOut -= this.outdebt; - this.outdebt = 0; - } else if (this.indents[len] < this.outdebt) { - this.outdebt -= this.indents[len]; - moveOut -= this.indents[len]; - } else { - dent = this.indents.pop() + this.outdebt; - moveOut -= dent; - this.outdebt = 0; - this.pair('OUTDENT'); - this.token('OUTDENT', dent, 0, outdentLength); + break; } } - if (dent) { - this.outdebt -= moveOut; - } + this.outdebt = moveOut; while (this.value() === ';') { this.tokens.pop(); } @@ -702,7 +717,7 @@ if ('OUTDENT' !== wanted) { this.error("unmatched " + tag); } - this.indent -= size = last(this.indents); + size = last(this.indents); this.outdentToken(size, true); return this.pair(tag); } diff --git a/src/lexer.coffee b/src/lexer.coffee index bfb60c4654..dc2b104c96 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -40,6 +40,7 @@ exports.Lexer = class Lexer @indebt = 0 # The over-indentation at the current level. @outdebt = 0 # The under-outdentation at the current level. @indents = [] # The stack of all current indentation levels. + @oldDebts = [] # Stack of old indebts and outdebts, to go with indents @ends = [] # The stack for pairing up tokens. @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`. @@ -316,7 +317,13 @@ exports.Lexer = class Lexer @seenFor = no size = indent.length - 1 - indent.lastIndexOf '\n' noNewlines = @unfinished() - if size - @indebt is @indent + #console.log size, @indent, @indents, @indebt, @outdebt, noNewlines + + if size - @indebt is @indent or size + @outdebt is @indent + if size - @indebt is @indent + @outdebt = 0 + if size + @outdebt is @indent + @indebt = 0 if noNewlines then @suppressNewlines() else @newlineToken 0 return indent.length @@ -325,39 +332,50 @@ exports.Lexer = class Lexer @indebt = size - @indent @suppressNewlines() return indent.length - diff = size - @indent + @outdebt + diff = size - @indent @token 'INDENT', diff, 0, indent.length + + # Keep track of old debts, so that we can get back on track later. + + if @indebt + @oldDebts.push @indebt + else if @outdebt + @oldDebts.push -@outdebt + else + @oldDebts.push 0 + @indents.push diff @ends.push 'OUTDENT' @outdebt = @indebt = 0 + @indent = size else @indebt = 0 @outdentToken @indent - size, noNewlines, indent.length - @indent = size indent.length # Record an outdent token or multiple tokens, if we happen to be moving back # inwards past several recorded indents. outdentToken: (moveOut, noNewlines, outdentLength) -> while moveOut > 0 - len = @indents.length - 1 - if @indents[len] is undefined + indent = last @indents + oldDebt = last @oldDebts + + break if not indent? + break if indent > moveOut and (not (oldDebt > 0) or indent - moveOut != oldDebt) + + @indents.pop() + @oldDebts.pop() + moveOut -= indent + @indent -= indent + @pair 'OUTDENT' + @token 'OUTDENT', indent, 0, outdentLength + + if oldDebt > 0 and indent + moveOut is oldDebt + @indebt = oldDebt moveOut = 0 - else if @indents[len] is @outdebt - moveOut -= @outdebt - @outdebt = 0 - else if @indents[len] < @outdebt - @outdebt -= @indents[len] - moveOut -= @indents[len] - else - dent = @indents.pop() + @outdebt - moveOut -= dent - @outdebt = 0 - @pair 'OUTDENT' - @token 'OUTDENT', dent, 0, outdentLength - @outdebt -= moveOut if dent + break + @outdebt = moveOut @tokens.pop() while @value() is ';' - @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines this @@ -605,7 +623,7 @@ exports.Lexer = class Lexer # el.click((event) -> # el.hide()) # - @indent -= size = last @indents + size = last @indents @outdentToken size, true return @pair tag @ends.pop() diff --git a/test/functions.coffee b/test/functions.coffee index 2905698e12..3a2a548bc9 100644 --- a/test/functions.coffee +++ b/test/functions.coffee @@ -225,3 +225,38 @@ test "#1435 Indented property access", -> rec.rec() .rec() 1 + +test "#1275, Sub-block terminator can cause parent block termination", -> + first = (x) -> x + + rec = (x, y) -> if not y then rec: rec else y() + + foo = rec() + .rec "test", -> + rec("asdads").rec( -> + false + ) + 1 + eq foo, 1 + + bar = rec() + .rec "test", -> + rec("asdads").rec( -> + false + ) + 1 + eq bar, 1 + + baz = -> + ( + 1 + ) + 2 + eq baz(), 2 + + qux = -> + ( + 1 + ) + 2 + eq qux(), 2 \ No newline at end of file