From fba165408ce77addc98bf2db455669c4378d34d4 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sat, 18 Dec 2010 13:20:14 -0500 Subject: [PATCH] #923 ... correct interpolation. --- lib/lexer.js | 32 +++++++++++++++----------------- src/lexer.coffee | 27 ++++++++++++++++----------- test/test_strings.coffee | 9 +++++++++ 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index 8982974b..ec8ca92b 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -128,7 +128,7 @@ this.token('STRING', (string = match[0]).replace(MULTILINER, '\\\n')); break; case '"': - if (!(string = this.balancedString(this.chunk, [['"', '"'], ['#{', '}']]))) { + if (!(string = this.balancedString(this.chunk, '"', '"'))) { return 0; } if (0 < string.indexOf('#{', 1)) { @@ -447,32 +447,30 @@ Lexer.prototype.assignmentError = function() { throw SyntaxError("Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned"); }; - Lexer.prototype.balancedString = function(str, delimited, options) { - var i, open, pair, stack, _i, _len, _ref; - if (options == null) { - options = {}; - } - stack = [delimited[0]]; + Lexer.prototype.balancedString = function(str, start, end) { + var i, letter, prev, stack, _ref; + stack = [end]; for (i = 1, _ref = str.length; (1 <= _ref ? i < _ref : i > _ref); (1 <= _ref ? i += 1 : i -= 1)) { - switch (str.charAt(i)) { + switch (letter = str.charAt(i)) { case '\\': i++; continue; - case stack[stack.length - 1][1]: + case end: stack.pop(); if (!stack.length) { return str.slice(0, i + 1); } + end = stack[stack.length - 1]; continue; } - for (_i = 0, _len = delimited.length; _i < _len; _i++) { - pair = delimited[_i]; - if ((open = pair[0]) === str.substr(i, open.length)) { - stack.push(pair); - i += open.length - 1; - break; - } + if (end === '}' && (letter === '"' || letter === "'")) { + stack.push(end = letter); + } else if (end === '}' && letter === '{') { + stack.push(end = '}'); + } else if (end === '"' && prev === '#' && letter === '{') { + stack.push(end = '}'); } + prev = letter; } throw new Error("unterminated " + (stack.pop()[0]) + " on line " + (this.line + 1)); }; @@ -490,7 +488,7 @@ i += 1; continue; } - if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = this.balancedString(str.slice(i + 1), [['{', '}']])))) { + if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = this.balancedString(str.slice(i + 1), '#{', '}')))) { continue; } if (pi < i) { diff --git a/src/lexer.coffee b/src/lexer.coffee index 5ba012b8..9516a5fc 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -139,7 +139,7 @@ exports.Lexer = class Lexer return 0 unless match = SIMPLESTR.exec @chunk @token 'STRING', (string = match[0]).replace MULTILINER, '\\\n' when '"' - return 0 unless string = @balancedString @chunk, [['"', '"'], ['#{', '}']] + return 0 unless string = @balancedString @chunk, '"', '"' if 0 < string.indexOf '#{', 1 @interpolateString string.slice 1, -1 else @@ -387,21 +387,26 @@ exports.Lexer = class Lexer # a series of delimiters, all of which must be nested correctly within the # contents of the string. This method allows us to have strings within # interpolations within strings, ad infinitum. - balancedString: (str, delimited, options = {}) -> - stack = [delimited[0]] + balancedString: (str, start, end) -> + stack = [end] for i in [1...str.length] - switch str.charAt i + switch letter = str.charAt i when '\\' i++ continue - when stack[stack.length - 1][1] + when end stack.pop() - return str.slice 0, i + 1 unless stack.length + unless stack.length + return str.slice 0, i + 1 + end = stack[stack.length - 1] continue - for pair in delimited when (open = pair[0]) is str.substr i, open.length - stack.push pair - i += open.length - 1 - break + if end is '}' and letter in ['"', "'"] + stack.push end = letter + else if end is '}' and letter is '{' + stack.push end = '}' + else if end is '"' and prev is '#' and letter is '{' + stack.push end = '}' + prev = letter throw new Error "unterminated #{ stack.pop()[0] } on line #{ @line + 1 }" @@ -423,7 +428,7 @@ exports.Lexer = class Lexer i += 1 continue unless letter is '#' and str.charAt(i+1) is '{' and - (expr = @balancedString str.slice(i+1), [['{', '}']]) + (expr = @balancedString str.slice(i + 1), '#{', '}') continue tokens.push ['NEOSTRING', str.slice(pi, i)] if pi < i inner = expr.slice(1, -1) diff --git a/test/test_strings.coffee b/test/test_strings.coffee index cbbe29ac..63e2a451 100644 --- a/test/test_strings.coffee +++ b/test/test_strings.coffee @@ -107,3 +107,12 @@ eq 'multiline nested "interpolations" work', """multiline #{ "\"interpolations\"" }" } work""" + + +# Issue #923: Tricky interpolation. +eq "#{ "{" }", "{" + +eq "#{ '#{}}' } }", '#{}} }' + +eq "#{"'#{ ({a: "b#{1}"}['a']) }'"}", "'b1'" +