Skip to content

Commit a9726d3

Browse files
authored
Fix nested sigil delimiter parsing inside macros (#16266)
Sigils record their opening and closing delimiters so that the delimiters themselves could be nested in the sigil body. Sigils inside macros are no exception: ```crystal macro foo %w(four (five) six) # okay, second element is "(five)" end ``` However, it turns out the lexer uses the sigil name itself as the start delimiter, instead of its next character. This leads to some rather strange behavior: ```crystal # okay, second `(` does not start a nested region # errors on macro expansion instead macro foo %w(four (five) %w(six) end ``` ```crystal # error, `w` inside `two` starts a nested region but only one `)` found macro foo %w(one two three) end ``` ```crystal # okay, every `w` is "matched" by a closing `)` # errors on expansion macro foo %w(www)))) end ``` This PR ensures the correct start delimiters are used.
1 parent 6d3ed28 commit a9726d3

File tree

2 files changed

+10
-6
lines changed

2 files changed

+10
-6
lines changed

spec/compiler/parser/parser_spec.cr

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,10 @@ module Crystal
14441444
it_parses "macro foo;unless var;true;end;end", Macro.new("foo", [] of Arg, Expressions.from(["unless var;true;".macro_literal, "end;".macro_literal] of ASTNode))
14451445
end
14461446

1447+
{'i', 'q', 'r', 'w', 'x', 'Q'}.each do |ch|
1448+
it_parses "macro foo;%#{ch}[#{ch}];end", Macro.new("foo", [] of Arg, "%#{ch}[#{ch}];".macro_literal)
1449+
end
1450+
14471451
it_parses "a = 1; pointerof(a)", [Assign.new("a".var, 1.int32), PointerOf.new("a".var)]
14481452
it_parses "pointerof(@a)", PointerOf.new("@a".instance_var)
14491453
it_parses "a = 1; pointerof(a)", [Assign.new("a".var, 1.int32), PointerOf.new("a".var)]

src/compiler/crystal/syntax/lexer.cr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,27 +1788,27 @@ module Crystal
17881788
char = next_char
17891789
if char == 'q' && peek_next_char.in?('(', '<', '[', '{', '|')
17901790
next_char
1791-
delimiter_state = Token::DelimiterState.new(:string, char, closing_char, 1)
1791+
delimiter_state = Token::DelimiterState.new(:string, current_char, closing_char, 1)
17921792
next_char
17931793
elsif char == 'Q' && peek_next_char.in?('(', '<', '[', '{', '|')
17941794
next_char
1795-
delimiter_state = Token::DelimiterState.new(:string, char, closing_char, 1)
1795+
delimiter_state = Token::DelimiterState.new(:string, current_char, closing_char, 1)
17961796
next_char
17971797
elsif char == 'i' && peek_next_char.in?('(', '<', '[', '{', '|')
17981798
next_char
1799-
delimiter_state = Token::DelimiterState.new(:symbol_array, char, closing_char, 1)
1799+
delimiter_state = Token::DelimiterState.new(:symbol_array, current_char, closing_char, 1)
18001800
next_char
18011801
elsif char == 'w' && peek_next_char.in?('(', '<', '[', '{', '|')
18021802
next_char
1803-
delimiter_state = Token::DelimiterState.new(:string_array, char, closing_char, 1)
1803+
delimiter_state = Token::DelimiterState.new(:string_array, current_char, closing_char, 1)
18041804
next_char
18051805
elsif char == 'x' && peek_next_char.in?('(', '<', '[', '{', '|')
18061806
next_char
1807-
delimiter_state = Token::DelimiterState.new(:command, char, closing_char, 1)
1807+
delimiter_state = Token::DelimiterState.new(:command, current_char, closing_char, 1)
18081808
next_char
18091809
elsif char == 'r' && peek_next_char.in?('(', '<', '[', '{', '|')
18101810
next_char
1811-
delimiter_state = Token::DelimiterState.new(:regex, char, closing_char, 1)
1811+
delimiter_state = Token::DelimiterState.new(:regex, current_char, closing_char, 1)
18121812
next_char
18131813
else
18141814
start = current_pos

0 commit comments

Comments
 (0)