Skip to content

Commit de80a09

Browse files
makenowjustRX14
authored andcommittedAug 10, 2017
Parser: prevent to finish a macro body when {% if/for/begin %} is nested (#4801)
Fixed #4769 This introduced `control_nest` counter variable to keep macro control (if/for/begin) nesting information. When the parser find `end` inside macro body, it checks `control_nest` and finishes a macro body if and only if `control_nest` is `0` (and `nest` which is usual block nesting information is `0` also). And formatter is fixed also.
1 parent 020796f commit de80a09

File tree

6 files changed

+31
-14
lines changed

6 files changed

+31
-14
lines changed
 

‎spec/compiler/formatter/formatter_spec.cr

+1
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ describe Crystal::Formatter do
549549
assert_format "macro foo\n \\{\nend"
550550
assert_format "macro foo\n {% if 1 %} 2 {% elsif 3 %} 4 {% else %} 5 {% end %}\nend"
551551
assert_format "macro [](x)\nend"
552+
assert_format "macro foo\n {% if true %}if true{% end %}\n {% if true %}end{% end %}\nend"
552553

553554
assert_format "def foo\na = bar do\n1\nend\nend", "def foo\n a = bar do\n 1\n end\nend"
554555
assert_format "def foo\nend\ndef bar\nend", "def foo\nend\n\ndef bar\nend"

‎spec/compiler/parser/parser_spec.cr

+1
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ describe "Parser" do
770770

771771
it_parses "{% for x in y %}body{% end %}", MacroFor.new(["x".var], "y".var, "body".macro_literal)
772772
it_parses "{% if x %}body{% end %}", MacroIf.new("x".var, "body".macro_literal)
773+
it_parses "{% begin %}{% if true %}if true{% end %}\n{% if true %}end{% end %}{% end %}", MacroIf.new(true.bool, [MacroIf.new(true.bool, "if true".macro_literal), "\n".macro_literal, MacroIf.new(true.bool, "end".macro_literal)] of ASTNode)
773774
it_parses "{{ foo }}", MacroExpression.new("foo".var)
774775

775776
it_parses "macro foo;%var;end", Macro.new("foo", [] of Arg, Expressions.from([MacroVar.new("var"), MacroLiteral.new(";")] of ASTNode))

‎src/compiler/crystal/syntax/lexer.cr

+9-8
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,7 @@ module Crystal
19141914

19151915
def next_macro_token(macro_state, skip_whitespace)
19161916
nest = macro_state.nest
1917+
control_nest = macro_state.control_nest
19171918
whitespace = macro_state.whitespace
19181919
delimiter_state = macro_state.delimiter_state
19191920
beginning_of_line = macro_state.beginning_of_line
@@ -1964,7 +1965,7 @@ module Crystal
19641965

19651966
@token.type = :MACRO_LITERAL
19661967
@token.value = string_range(start)
1967-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
1968+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
19681969
set_token_raw_from_start(start)
19691970
return @token
19701971
end
@@ -1975,7 +1976,7 @@ module Crystal
19751976
next_char
19761977
@token.type = :MACRO_LITERAL
19771978
@token.value = "%"
1978-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
1979+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
19791980
@token.raw = "%"
19801981
return @token
19811982
end
@@ -1986,13 +1987,13 @@ module Crystal
19861987
beginning_of_line = false
19871988
next_char
19881989
@token.type = :MACRO_EXPRESSION_START
1989-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
1990+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
19901991
return @token
19911992
when '%'
19921993
beginning_of_line = false
19931994
next_char
19941995
@token.type = :MACRO_CONTROL_START
1995-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
1996+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
19961997
return @token
19971998
else
19981999
# Make sure to decrease the '}' count if inside an interpolation
@@ -2024,7 +2025,7 @@ module Crystal
20242025
end
20252026
@token.type = :MACRO_LITERAL
20262027
@token.value = string_range(start)
2027-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
2028+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
20282029
set_token_raw_from_start(start)
20292030
return @token
20302031
end
@@ -2042,7 +2043,7 @@ module Crystal
20422043
beginning_of_line = false
20432044
@token.type = :MACRO_VAR
20442045
@token.value = string_range_from_pool(start)
2045-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
2046+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
20462047
return @token
20472048
end
20482049
end
@@ -2052,7 +2053,7 @@ module Crystal
20522053
case next_char
20532054
when 'd'
20542055
if whitespace && !ident_part_or_end?(peek_next_char)
2055-
if nest == 0
2056+
if nest == 0 && control_nest == 0
20562057
next_char
20572058
@token.type = :MACRO_END
20582059
@token.macro_state = Token::MacroState.default
@@ -2192,7 +2193,7 @@ module Crystal
21922193

21932194
@token.type = :MACRO_LITERAL
21942195
@token.value = string_range(start)
2195-
@token.macro_state = Token::MacroState.new(whitespace, nest, delimiter_state, beginning_of_line, yields, comment)
2196+
@token.macro_state = Token::MacroState.new(whitespace, nest, control_nest, delimiter_state, beginning_of_line, yields, comment)
21962197
set_token_raw_from_start(start)
21972198

21982199
@token

‎src/compiler/crystal/syntax/parser.cr

+8
Original file line numberDiff line numberDiff line change
@@ -2896,7 +2896,9 @@ module Crystal
28962896

28972897
check :"%}"
28982898

2899+
macro_state.control_nest += 1
28992900
body, end_location = parse_macro_body(start_line, start_column, macro_state)
2901+
macro_state.control_nest -= 1
29002902

29012903
check_ident :end
29022904
next_token_skip_space
@@ -2920,7 +2922,9 @@ module Crystal
29202922
next_token_skip_space
29212923
check :"%}"
29222924

2925+
macro_state.control_nest += 1
29232926
body, end_location = parse_macro_body(start_line, start_column, macro_state)
2927+
macro_state.control_nest -= 1
29242928

29252929
check_ident :end
29262930
next_token_skip_space
@@ -2953,15 +2957,19 @@ module Crystal
29532957

29542958
check :"%}"
29552959

2960+
macro_state.control_nest += 1
29562961
a_then, end_location = parse_macro_body(start_line, start_column, macro_state)
2962+
macro_state.control_nest -= 1
29572963

29582964
if @token.type == :IDENT
29592965
case @token.value
29602966
when :else
29612967
next_token_skip_space
29622968
check :"%}"
29632969

2970+
macro_state.control_nest += 1
29642971
a_else, end_location = parse_macro_body(start_line, start_column, macro_state)
2972+
macro_state.control_nest -= 1
29652973

29662974
if check_end
29672975
check_ident :end

‎src/compiler/crystal/syntax/token.cr

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ module Crystal
1818
record MacroState,
1919
whitespace : Bool,
2020
nest : Int32,
21+
control_nest : Int32,
2122
delimiter_state : DelimiterState?,
2223
beginning_of_line : Bool,
2324
yields : Bool,
2425
comment : Bool do
2526
def self.default
26-
MacroState.new(true, 0, nil, true, false, false)
27+
MacroState.new(true, 0, 0, nil, true, false, false)
2728
end
2829

2930
setter whitespace
31+
setter control_nest
3032
end
3133

3234
record DelimiterState,

‎src/compiler/crystal/tools/formatter.cr

+9-5
Original file line numberDiff line numberDiff line change
@@ -1761,7 +1761,6 @@ module Crystal
17611761
end
17621762
write "{% "
17631763

1764-
macro_state = @macro_state
17651764
next_token_skip_space_or_newline
17661765

17671766
if @token.keyword?(:begin)
@@ -1780,7 +1779,7 @@ module Crystal
17801779
outside_macro { indent(@column, node.cond) }
17811780
end
17821781

1783-
format_macro_if_epilogue node, macro_state
1782+
format_macro_if_epilogue(node, @macro_state)
17841783
end
17851784

17861785
def format_macro_if_epilogue(node, macro_state, check_end = true)
@@ -1789,14 +1788,14 @@ module Crystal
17891788
write " %}"
17901789

17911790
@macro_state = macro_state
1791+
@macro_state.control_nest += 1
17921792
check_macro_whitespace
17931793
next_macro_token
17941794

17951795
inside_macro { no_indent node.then }
17961796

17971797
unless node.else.is_a?(Nop)
17981798
check :MACRO_CONTROL_START
1799-
macro_state = @macro_state
18001799
next_token_skip_space_or_newline
18011800

18021801
if @token.keyword?(:elsif)
@@ -1811,16 +1810,19 @@ module Crystal
18111810
check :"%}"
18121811

18131812
write "{% else %}"
1813+
1814+
@macro_state = macro_state
1815+
@macro_state.control_nest += 1
18141816
check_macro_whitespace
18151817
next_macro_token
18161818

18171819
inside_macro { no_indent node.else }
18181820
end
18191821
end
18201822

1823+
@macro_state = macro_state
18211824
if check_end
18221825
check :MACRO_CONTROL_START
1823-
macro_state = @macro_state
18241826
next_token_skip_space_or_newline
18251827

18261828
check_end
@@ -1842,6 +1844,7 @@ module Crystal
18421844

18431845
def visit(node : MacroFor)
18441846
reset_macro_state
1847+
old_macro_state = @macro_state
18451848

18461849
if inside_macro?
18471850
check :MACRO_CONTROL_START
@@ -1876,13 +1879,14 @@ module Crystal
18761879
check :"%}"
18771880
write " %}"
18781881

1882+
@macro_state.control_nest += 1
18791883
check_macro_whitespace
18801884
next_macro_token
18811885

18821886
inside_macro { no_indent node.body }
1887+
@macro_state = old_macro_state
18831888

18841889
check :MACRO_CONTROL_START
1885-
macro_state = @macro_state
18861890
next_token_skip_space_or_newline
18871891

18881892
check_end

0 commit comments

Comments
 (0)
Please sign in to comment.