1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Merge pull request #3261 from xixixao/issue1273

Fix #1273, Handle backslashes at the end of heredocs
This commit is contained in:
Marc Häfner 2013-11-28 07:32:20 -08:00
commit 13f205404c
4 changed files with 77 additions and 56 deletions

View file

@ -166,30 +166,25 @@
};
Lexer.prototype.stringToken = function() {
var match, octalEsc, string;
switch (this.chunk.charAt(0)) {
var octalEsc, quote, string, trimmed;
switch (quote = this.chunk.charAt(0)) {
case "'":
if (!(match = SIMPLESTR.exec(this.chunk))) {
return 0;
}
string = match[0];
this.token('STRING', this.removeNewlines(string), 0, string.length);
string = SIMPLESTR.exec(this.chunk)[0];
break;
case '"':
if (!(string = this.balancedString(this.chunk, '"'))) {
return 0;
}
if (0 < string.indexOf('#{', 1)) {
this.interpolateString(string.slice(1, -1), {
strOffset: 1,
lexedLength: string.length
});
} else {
this.token('STRING', this.removeNewlines(string), 0, string.length);
}
break;
default:
return 0;
string = this.balancedString(this.chunk, '"');
}
if (!string) {
return 0;
}
trimmed = this.removeNewlines(string.slice(1, -1));
if (quote === '"' && 0 < string.indexOf('#{', 1)) {
this.interpolateString(trimmed, {
strOffset: 1,
lexedLength: string.length
});
} else {
this.token('STRING', quote + this.escapeLines(trimmed) + quote, 0, string.length);
}
if (octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test(string)) {
this.error("octal escape sequences " + string + " are not allowed");
@ -198,13 +193,14 @@
};
Lexer.prototype.heredocToken = function() {
var doc, heredoc, match, quote;
var doc, heredoc, match, quote, trimmed;
if (!(match = HEREDOC.exec(this.chunk))) {
return 0;
}
heredoc = match[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,
indent: null
});
@ -601,10 +597,6 @@
offsetInChunk = offsetInChunk || 0;
strOffset = strOffset || 0;
lexedLength = lexedLength || str.length;
if (heredoc && str.length > 0 && str[0] === '\n') {
str = str.slice(1);
strOffset++;
}
tokens = [];
pi = 0;
i = -1;
@ -763,7 +755,7 @@
};
Lexer.prototype.removeNewlines = function(str) {
return this.escapeLines(str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1'));
return str.replace(/^\s*\n\s*/, '').replace(/([^\\]|\\\\)\s*\n\s*$/, '$1');
};
Lexer.prototype.escapeLines = function(str, heredoc) {
@ -855,13 +847,13 @@
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})/;
WHITESPACE = /^[^\n\S]+/;
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/;
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/;
CODE = /^[-=]>/;

View file

@ -186,19 +186,15 @@ exports.Lexer = class Lexer
# Matches strings, including multi-line strings. Ensures that quotation marks
# are balanced within the string's contents, and within nested interpolations.
stringToken: ->
switch @chunk.charAt 0
when "'"
return 0 unless match = SIMPLESTR.exec @chunk
string = match[0]
@token 'STRING', @removeNewlines(string), 0, string.length
when '"'
return 0 unless string = @balancedString @chunk, '"'
if 0 < string.indexOf '#{', 1
@interpolateString string[1...-1], strOffset: 1, lexedLength: string.length
else
@token 'STRING', @removeNewlines(string), 0, string.length
else
return 0
switch quote = @chunk.charAt 0
when "'" then [string] = SIMPLESTR.exec @chunk
when '"' then string = @balancedString @chunk, '"'
return 0 unless string
trimmed = @removeNewlines string[1...-1]
if quote is '"' and 0 < string.indexOf '#{', 1
@interpolateString trimmed, strOffset: 1, lexedLength: string.length
else
@token 'STRING', quote + @escapeLines(trimmed) + quote, 0, string.length
if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string
@error "octal escape sequences #{string} are not allowed"
string.length
@ -209,7 +205,9 @@ exports.Lexer = class Lexer
return 0 unless match = HEREDOC.exec @chunk
heredoc = match[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 '#{'
@interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length
else
@ -528,11 +526,6 @@ exports.Lexer = class Lexer
strOffset = strOffset || 0
lexedLength = lexedLength || str.length
# Clip leading \n from heredoc
if heredoc and str.length > 0 and str[0] == '\n'
str = str[1...]
strOffset++
# Parse the string.
tokens = []
pi = 0
@ -684,10 +677,10 @@ exports.Lexer = class Lexer
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
# Remove newlines from beginning and end of string literals.
# `str` includes quotes.
# Remove newlines from beginning and (non escaped) from end of string literals.
removeNewlines: (str) ->
@escapeLines str.replace(/^(.)\s*\n\s*/, '$1').replace(/\s*\n\s*(.)$/, '$1')
str.replace(/^\s*\n\s*/, '')
.replace(/([^\\]|\\\\)\s*\n\s*$/, '$1')
# Converts newlines for string literals.
escapeLines: (str, heredoc) ->
@ -779,7 +772,7 @@ NUMBER = ///
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
///i
HEREDOC = /// ^ ("""|''') ([\s\S]*?) (?:\n[^\n\S]*)? \1 ///
HEREDOC = /// ^ ("""|''') (( [\s\S]*? ([^\\]|\\\\) )?) \1 ///
OPERATOR = /// ^ (
?: [-=]> # function
@ -793,7 +786,7 @@ OPERATOR = /// ^ (
WHITESPACE = /^[^\n\S]+/
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/
CODE = /^[-=]>/

View file

@ -68,6 +68,9 @@ test "#1026", ->
test "#1050", ->
cantCompile "### */ ###"
test "#1273: escaping quotes at the end of heredocs", ->
cantCompile '"""\\"""' # """\"""
test "#1106: __proto__ compilation", ->
object = eq
@["__proto__"] = true

View file

@ -63,6 +63,15 @@ test "#3229, multiline strings", ->
eq ' \
ok', ' ok'
# #1273, empty strings.
eq '\
', ''
eq '
', ''
eq '
', ''
eq ' ', ' '
# Same behavior in interpolated strings.
eq "interpolation #{1}
follows #{2} \
@ -72,6 +81,9 @@ test "#3229, multiline strings", ->
'string ' + "inside
interpolation"
}", "a string inside interpolation"
eq "
#{1}
", '1'
# Handle escaped backslashes correctly.
eq '\\', `'\\'`
@ -79,6 +91,10 @@ test "#3229, multiline strings", ->
next line', 'escaped backslash at EOL\\ next line'
eq '\\
next line', '\\ next line'
eq '\\
', '\\'
eq '\\\\\\
', '\\\\\\'
eq "#{1}\\
after interpolation", '1\\ after interpolation'
eq 'escaped backslash before slash\\ \
@ -120,11 +136,14 @@ test "#3249, escape newlines in heredocs with backslashes", ->
normal indentation
""", 'Set whitespace <- this is ignorednone\n normal indentation'
# Changed from #647
# Changed from #647, trailing backslash.
eq '''
Hello, World\
''', 'Hello, World'
eq '''
\\
''', '\\'
# Backslash at the beginning of a literal string.
eq '''\
@ -139,6 +158,11 @@ test "#3249, escape newlines in heredocs with backslashes", ->
too #{3}\
!
""", 'interpolation 1\n follows 2 too 3!'
eq """
#{1} #{2}
""", '\n1 2\n'
# TODO: uncomment when #2388 is fixed
# eq """a heredoc #{
@ -151,6 +175,9 @@ test "#3249, escape newlines in heredocs with backslashes", ->
escaped backslash at EOL\\
next line
''', 'escaped backslash at EOL\\\n next line'
eq '''\\
''', '\\\n'
# Backslashes at beginning of lines.
eq '''first line
@ -158,7 +185,7 @@ test "#3249, escape newlines in heredocs with backslashes", ->
eq """first line\
\ backslash at BOL""", 'first line\ backslash at BOL'
# Edge case.
# Edge cases.
eq '''lone
\
@ -166,6 +193,8 @@ test "#3249, escape newlines in heredocs with backslashes", ->
backslash''', 'lone\n\n backslash'
eq '''\
''', ''
#647
eq "''Hello, World\\''", '''
@ -175,6 +204,10 @@ eq '""Hello, World\\""', """
"\"Hello, World\\\""
"""
test "#1273, escaping quotes at the end of heredocs.", ->
# """\""" no longer compiles
eq """\\""", '\\'
a = """
basic heredoc
on two lines