Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions spec/compiler/parser/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
19 changes: 19 additions & 0 deletions spec/compiler/parser/to_s_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
24 changes: 24 additions & 0 deletions src/compiler/crystal/syntax/ast.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions src/compiler/crystal/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading