Handle backslashes at the end of heredocs

This commit is contained in:
xixixao 2013-11-26 19:29:13 +00:00
parent 873ed071d4
commit 42aa8d256c
3 changed files with 46 additions and 24 deletions

View File

@ -173,7 +173,7 @@
return 0; return 0;
} }
string = match[0]; string = match[0];
this.token('STRING', this.removeNewlines(string), 0, string.length); this.token('STRING', this.escapeLines(string), 0, string.length);
break; break;
case '"': case '"':
if (!(string = this.balancedString(this.chunk, '"'))) { if (!(string = this.balancedString(this.chunk, '"'))) {
@ -185,7 +185,7 @@
lexedLength: string.length lexedLength: string.length
}); });
} else { } else {
this.token('STRING', this.removeNewlines(string), 0, string.length); this.token('STRING', this.escapeLines(string), 0, string.length);
} }
break; break;
default: default:
@ -198,13 +198,14 @@
}; };
Lexer.prototype.heredocToken = function() { Lexer.prototype.heredocToken = function() {
var doc, heredoc, match, quote; var doc, heredoc, match, quote, trimmed;
if (!(match = HEREDOC.exec(this.chunk))) { if (!(match = HEREDOC.exec(this.chunk))) {
return 0; return 0;
} }
heredoc = match[0]; heredoc = match[0];
quote = heredoc.charAt(0); quote = heredoc.charAt(0);
doc = this.sanitizeHeredoc(match[2], { trimmed = match[2].replace(/(([^\\]|\\\\)\s*)\n[^\n\S]*$/, '$1');
doc = this.sanitizeHeredoc(trimmed, {
quote: quote, quote: quote,
indent: null indent: null
}); });
@ -762,10 +763,6 @@
return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS'); return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS');
}; };
Lexer.prototype.removeNewlines = function(str) {
return this.escapeLines(str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1'));
};
Lexer.prototype.escapeLines = function(str, heredoc) { Lexer.prototype.escapeLines = function(str, heredoc) {
str = str.replace(/\\[^\S\n]*(\n|\\)\s*/g, function(escaped, character) { str = str.replace(/\\[^\S\n]*(\n|\\)\s*/g, function(escaped, character) {
if (character === '\n') { if (character === '\n') {
@ -777,7 +774,7 @@
if (heredoc) { if (heredoc) {
return str.replace(MULTILINER, '\\n'); return str.replace(MULTILINER, '\\n');
} else { } else {
return str.replace(/\s*\n\s*/g, ' '); return str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1').replace(/\s*\n\s*/g, ' ');
} }
}; };
@ -855,13 +852,13 @@
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i; NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
HEREDOC = /^("""|''')([\s\S]*?)(?:\n[^\n\S]*)?\1/; HEREDOC = /^("""|''')(([\s\S]*?([^\\]|\\\\))?)\1/;
OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3})/; OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3})/;
WHITESPACE = /^[^\n\S]+/; WHITESPACE = /^[^\n\S]+/;
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/; COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/;
CODE = /^[-=]>/; CODE = /^[-=]>/;

View File

@ -190,13 +190,13 @@ exports.Lexer = class Lexer
when "'" when "'"
return 0 unless match = SIMPLESTR.exec @chunk return 0 unless match = SIMPLESTR.exec @chunk
string = match[0] string = match[0]
@token 'STRING', @removeNewlines(string), 0, string.length @token 'STRING', @escapeLines(string), 0, string.length
when '"' when '"'
return 0 unless string = @balancedString @chunk, '"' return 0 unless string = @balancedString @chunk, '"'
if 0 < string.indexOf '#{', 1 if 0 < string.indexOf '#{', 1
@interpolateString string[1...-1], strOffset: 1, lexedLength: string.length @interpolateString string[1...-1], strOffset: 1, lexedLength: string.length
else else
@token 'STRING', @removeNewlines(string), 0, string.length @token 'STRING', @escapeLines(string), 0, string.length
else else
return 0 return 0
if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string
@ -209,7 +209,9 @@ exports.Lexer = class Lexer
return 0 unless match = HEREDOC.exec @chunk return 0 unless match = HEREDOC.exec @chunk
heredoc = match[0] heredoc = match[0]
quote = heredoc.charAt 0 quote = heredoc.charAt 0
doc = @sanitizeHeredoc match[2], quote: quote, indent: null # Trim last newline if it's not escaped
trimmed = match[2].replace /(([^\\]|\\\\)\s*)\n[^\n\S]*$/, '$1'
doc = @sanitizeHeredoc trimmed, quote: quote, indent: null
if quote is '"' and 0 <= doc.indexOf '#{' if quote is '"' and 0 <= doc.indexOf '#{'
@interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length @interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length
else else
@ -684,11 +686,6 @@ exports.Lexer = class Lexer
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION' @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
'COMPARE', 'LOGIC', 'THROW', 'EXTENDS'] 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
# Remove newlines from beginning and end of string literals.
# `str` includes quotes.
removeNewlines: (str) ->
@escapeLines str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1')
# Converts newlines for string literals. # Converts newlines for string literals.
escapeLines: (str, heredoc) -> escapeLines: (str, heredoc) ->
# Ignore escaped backslashes and remove escaped newlines # Ignore escaped backslashes and remove escaped newlines
@ -697,7 +694,10 @@ exports.Lexer = class Lexer
if heredoc if heredoc
str.replace MULTILINER, '\\n' str.replace MULTILINER, '\\n'
else else
str.replace /\s*\n\s*/g, ' ' # Trim leading and trailing whitespace, string includes quotes
str.replace(/^(.)\s*\n\s*/, '$1')
.replace(/\s*\n\s*(.)$/, '$1')
.replace(/\s*\n\s*/g, ' ')
# Constructs a string token by escaping quotes and newlines. # Constructs a string token by escaping quotes and newlines.
makeString: (body, quote, heredoc) -> makeString: (body, quote, heredoc) ->
@ -779,7 +779,7 @@ NUMBER = ///
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal ^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
///i ///i
HEREDOC = /// ^ ("""|''') ([\s\S]*?) (?:\n[^\n\S]*)? \1 /// HEREDOC = /// ^ ("""|''') (( [\s\S]*? ([^\\]|\\\\) )?) \1 ///
OPERATOR = /// ^ ( OPERATOR = /// ^ (
?: [-=]> # function ?: [-=]> # function
@ -793,7 +793,7 @@ OPERATOR = /// ^ (
WHITESPACE = /^[^\n\S]+/ WHITESPACE = /^[^\n\S]+/
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/ COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/
CODE = /^[-=]>/ CODE = /^[-=]>/

View File

@ -63,6 +63,15 @@ test "#3229, multiline strings", ->
eq ' \ eq ' \
ok', ' ok' ok', ' ok'
# #1273, empty strings.
eq '\
', ''
eq '
', ''
eq '
', ''
eq ' ', ' '
# Same behavior in interpolated strings. # Same behavior in interpolated strings.
eq "interpolation #{1} eq "interpolation #{1}
follows #{2} \ follows #{2} \
@ -79,6 +88,10 @@ test "#3229, multiline strings", ->
next line', 'escaped backslash at EOL\\ next line' next line', 'escaped backslash at EOL\\ next line'
eq '\\ eq '\\
next line', '\\ next line' next line', '\\ next line'
eq '\\
', '\\'
eq '\\\\\\
', '\\\\\\'
eq "#{1}\\ eq "#{1}\\
after interpolation", '1\\ after interpolation' after interpolation", '1\\ after interpolation'
eq 'escaped backslash before slash\\ \ eq 'escaped backslash before slash\\ \
@ -120,11 +133,14 @@ test "#3249, escape newlines in heredocs with backslashes", ->
normal indentation normal indentation
""", 'Set whitespace <- this is ignorednone\n normal indentation' """, 'Set whitespace <- this is ignorednone\n normal indentation'
# Changed from #647 # Changed from #647, trailing backslash.
eq ''' eq '''
Hello, World\ Hello, World\
''', 'Hello, World' ''', 'Hello, World'
eq '''
\\
''', '\\'
# Backslash at the beginning of a literal string. # Backslash at the beginning of a literal string.
eq '''\ eq '''\
@ -151,6 +167,9 @@ test "#3249, escape newlines in heredocs with backslashes", ->
escaped backslash at EOL\\ escaped backslash at EOL\\
next line next line
''', 'escaped backslash at EOL\\\n next line' ''', 'escaped backslash at EOL\\\n next line'
eq '''\\
''', '\\\n'
# Backslashes at beginning of lines. # Backslashes at beginning of lines.
eq '''first line eq '''first line
@ -158,7 +177,7 @@ test "#3249, escape newlines in heredocs with backslashes", ->
eq """first line\ eq """first line\
\ backslash at BOL""", 'first line\ backslash at BOL' \ backslash at BOL""", 'first line\ backslash at BOL'
# Edge case. # Edge cases.
eq '''lone eq '''lone
\ \
@ -166,6 +185,8 @@ test "#3249, escape newlines in heredocs with backslashes", ->
backslash''', 'lone\n\n backslash' backslash''', 'lone\n\n backslash'
eq '''\
''', ''
#647 #647
eq "''Hello, World\\''", ''' eq "''Hello, World\\''", '''
@ -175,6 +196,10 @@ eq '""Hello, World\\""', """
"\"Hello, World\\\"" "\"Hello, World\\\""
""" """
test "#1273, escaping quotes at the end of heredocs.", ->
# """\""" no longer compiles
eq """\\""", '\\'
a = """ a = """
basic heredoc basic heredoc
on two lines on two lines