diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index 476464ed8c1b..3b982d3052fc 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1271,6 +1271,19 @@ module Crystal assert_syntax_error "{% unless 1; 2; elsif 3; 4; end %}" assert_syntax_error "{% unless 1 %} 2 {% elsif 3 %} 3 {% end %}" + it_parses "{% if 1; 2; end; %}", MacroExpression.new(If.new(1.int32, 2.int32), output: false) + it_parses "{% if 1; 2; end; 3 %}", MacroExpression.new(Expressions.new([If.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{%\nif 1; 2; end; 3\n%}", MacroExpression.new(Expressions.new([If.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{% 2 if 1; 3 %}", MacroExpression.new(Expressions.new([If.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{%\n2 if 1; 3\n%}", MacroExpression.new(Expressions.new([If.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{% if 1; 2; elsif 3; 4; else; 5; end; 6 %}", MacroExpression.new(Expressions.new([If.new(1.int32, 2.int32, If.new(3.int32, 4.int32, 5.int32)), 6.int32]), output: false) + + it_parses "{% unless 1; 2; end; %}", MacroExpression.new(Unless.new(1.int32, 2.int32), output: false) + it_parses "{% unless 1; 2; end; 3 %}", MacroExpression.new(Expressions.new([Unless.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{%\nunless 1; 2; end; 3\n%}", MacroExpression.new(Expressions.new([Unless.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{% 2 unless 1; 3 %}", MacroExpression.new(Expressions.new([Unless.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{%\n2 unless 1; 3\n%}", MacroExpression.new(Expressions.new([Unless.new(1.int32, 2.int32), 3.int32]), output: false) + it_parses "{{ 1 // 2 }}", MacroExpression.new(Expressions.from([Call.new(1.int32, "//", 2.int32)] of ASTNode)) it_parses "{{ //.options }}", MacroExpression.new(Expressions.from([Call.new(RegexLiteral.new(StringLiteral.new("")), "options")] of ASTNode)) diff --git a/spec/compiler/parser/to_s_spec.cr b/spec/compiler/parser/to_s_spec.cr index faf73fd64e00..999fd3a408fe 100644 --- a/spec/compiler/parser/to_s_spec.cr +++ b/spec/compiler/parser/to_s_spec.cr @@ -291,6 +291,25 @@ describe "ASTNode#to_s" do expect_to_s "{%\n a = 1 %}" expect_to_s "{% a = 1\n%}" + expect_to_s <<-'CRYSTAL' + {% + if 1 + 2 + end + 3 + %} + CRYSTAL + + expect_to_s <<-'CRYSTAL' + {% + if 1 + 2 + end + 3 + 4 + %} + CRYSTAL + expect_to_s <<-'CR', <<-'CR' macro finished {% verbatim do %} diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 3c3892206d21..168fac34ca69 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -150,6 +150,30 @@ module Crystal obj end + # Concatenates two AST nodes into a single `Expressions` node, removing + # `Nop`s and merging keyword-less expressions into a single node. + # + # *x* and *y* may be modified in-place if they are already `Expressions` + # nodes. + def self.concat!(x : ASTNode, y : ASTNode) : ASTNode + return x if y.is_a?(Nop) + return y if x.is_a?(Nop) + + if x.is_a?(Expressions) && x.keyword.none? + if y.is_a?(Expressions) && y.keyword.none? + x.expressions.concat(y.expressions) + else + x.expressions << y + end + x.at_end(y.end_location) + elsif y.is_a?(Expressions) && y.keyword.none? + y.expressions.unshift(x) + y.at(x.location) + else + Expressions.new([x, y] of ASTNode).at(x.location).at_end(y.end_location) + end + end + def initialize(@expressions = [] of ASTNode) end diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index fdd24576150c..eac73de656af 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3477,10 +3477,13 @@ module Crystal else node = parse_if_after_condition cond, location, true end + skip_statement_end + exps = parse_expressions @in_macro_expression = false - skip_space_or_newline check :OP_PERCENT_RCURLY - return MacroExpression.new(node, output: false).at_end(token_end_location) + + exps = Expressions.concat!(node, exps) + return MacroExpression.new(exps, output: false).at_end(token_end_location) end check :OP_PERCENT_RCURLY