Skip to content

Commit

Permalink
Fix parsing an empty heredoc
Browse files Browse the repository at this point in the history
  • Loading branch information
makenowjust authored and asterite committed Jan 18, 2018
1 parent 84288b7 commit 6a574f2
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 51 deletions.
4 changes: 4 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,10 @@ describe "Parser" do
it_parses "<<-SOME\n Sa\n Se\n SOME", "Sa\nSe".string_interpolation
it_parses "<<-HERE\n \#{1} \#{2}\n HERE", StringInterpolation.new([1.int32, " ".string, 2.int32] of ASTNode)
it_parses "<<-HERE\n \#{1} \\n \#{2}\n HERE", StringInterpolation.new([1.int32, " \n ".string, 2.int32] of ASTNode)
it_parses "<<-HERE\nHERE", "".string_interpolation
it_parses "<<-HERE1; <<-HERE2\nHERE1\nHERE2", ["".string_interpolation, "".string_interpolation] of ASTNode
it_parses "<<-HERE1; <<-HERE2\nhere1\nHERE1\nHERE2", ["here1".string_interpolation, "".string_interpolation] of ASTNode
it_parses "<<-HERE1; <<-HERE2\nHERE1\nhere2\nHERE2", ["".string_interpolation, "here2".string_interpolation] of ASTNode
assert_syntax_error "<<-HERE\n One\nwrong\n Zero\n HERE", "heredoc line must have an indent greater or equal than 2", 3, 1
assert_syntax_error "<<-HERE\n One\n wrong\n Zero\n HERE", "heredoc line must have an indent greater or equal than 2", 3, 1
assert_syntax_error "<<-HERE\n One\n \#{1}\n Zero\n HERE", "heredoc line must have an indent greater or equal than 2", 3, 1
Expand Down
119 changes: 68 additions & 51 deletions src/compiler/crystal/syntax/lexer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,14 @@ module Crystal
string_nest = delimiter_state.nest
string_open_count = delimiter_state.open_count

# For empty heredocs:
if @token.type == :NEWLINE && delimiter_state.kind == :heredoc
if check_heredoc_end delimiter_state
set_token_raw_from_start start
return @token
end
end

case current_char
when '\0'
raise_unterminated_quoted delimiter_state
Expand Down Expand Up @@ -1877,63 +1885,16 @@ module Crystal
@token.column_number = @column_number

if delimiter_state.kind == :heredoc
string_end = string_end.to_s
old_pos = current_pos
old_column = @column_number

while current_char == ' ' || current_char == '\t'
next_char
end

indent = @column_number - 1

if string_end.starts_with?(current_char)
reached_end = false

string_end.each_char do |c|
unless c == current_char
reached_end = false
break
end
next_char
reached_end = true
end

if reached_end &&
(current_char == '\n' || current_char == '\0' ||
(current_char == '\r' && peek_next_char == '\n' && next_char))
@token.type = :DELIMITER_END
@token.delimiter_state = delimiter_state.with_heredoc_indent(indent)
else
@reader.pos = old_pos
@column_number = old_column
@token.column_number = @column_number
next_string_token delimiter_state
@token.value = (is_slash_r ? "\r\n" : '\n') + @token.value.to_s
end
else
@reader.pos = old_pos
@column_number = old_column
@token.column_number = @column_number
@token.type = :STRING
@token.value = is_slash_r ? "\r\n" : "\n"
unless check_heredoc_end delimiter_state
next_string_token_noescape delimiter_state
@token.value = string_range(start)
end
else
@token.type = :STRING
@token.value = is_slash_r ? "\r\n" : "\n"
end
else
while current_char != string_end &&
current_char != string_nest &&
current_char != '\0' &&
current_char != '\\' &&
current_char != '#' &&
current_char != '\r' &&
current_char != '\n'
next_char
end

@token.type = :STRING
next_string_token_noescape delimiter_state
@token.value = string_range(start)
end

Expand All @@ -1942,6 +1903,62 @@ module Crystal
@token
end

def next_string_token_noescape(delimiter_state)
string_end = delimiter_state.end
string_nest = delimiter_state.nest

while current_char != string_end &&
current_char != string_nest &&
current_char != '\0' &&
current_char != '\\' &&
current_char != '#' &&
current_char != '\r' &&
current_char != '\n'
next_char
end

@token.type = :STRING
end

def check_heredoc_end(delimiter_state)
string_end = delimiter_state.end.to_s
old_pos = current_pos
old_column = @column_number

while current_char == ' ' || current_char == '\t'
next_char
end

indent = @column_number - 1

if string_end.starts_with?(current_char)
reached_end = false

string_end.each_char do |c|
unless c == current_char
reached_end = false
break
end
next_char
reached_end = true
end

if reached_end &&
(current_char == '\n' || current_char == '\0' ||
(current_char == '\r' && peek_next_char == '\n' && next_char))
@token.type = :DELIMITER_END
@token.delimiter_state = delimiter_state.with_heredoc_indent(indent)
return true
end
end

@reader.pos = old_pos
@column_number = old_column
@token.column_number = @column_number

false
end

def raise_unterminated_quoted(delimiter_state)
msg = case delimiter_state.kind
when :command then "Unterminated command literal"
Expand Down

0 comments on commit 6a574f2

Please sign in to comment.