diff --git a/spec/compiler/parser/parser_spec.cr b/spec/compiler/parser/parser_spec.cr index a6aa70a37395..53fbedbcd7c1 100644 --- a/spec/compiler/parser/parser_spec.cr +++ b/spec/compiler/parser/parser_spec.cr @@ -1118,7 +1118,7 @@ module Crystal it_parses "puts {{**1}}", Call.new(nil, "puts", MacroExpression.new(DoubleSplat.new(1.int32))) it_parses "{{a = 1 if 2}}", MacroExpression.new(If.new(2.int32, Assign.new("a".var, 1.int32))) it_parses "{% a = 1 %}", MacroExpression.new(Assign.new("a".var, 1.int32), output: false) - it_parses "{%\n a = 1\n%}", MacroExpression.new(Assign.new("a".var, 1.int32), output: false, multiline: true) + it_parses "{%\n a = 1\n%}", MacroExpression.new(Assign.new("a".var, 1.int32), output: false) it_parses "{% a = 1 if 2 %}", MacroExpression.new(If.new(2.int32, Assign.new("a".var, 1.int32)), output: false) it_parses "{% if 1; 2; end %}", MacroExpression.new(If.new(1.int32, 2.int32), output: false) it_parses "{%\nif 1; 2; end\n%}", MacroExpression.new(If.new(1.int32, 2.int32), output: false) @@ -1128,8 +1128,8 @@ module Crystal it_parses "{% unless 1; 2; else 3; end %}", MacroExpression.new(Unless.new(1.int32, 2.int32, 3.int32), output: false) it_parses "{% unless 1\n x\nend %}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false) it_parses "{% x unless 1 %}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false) - it_parses "{%\n x unless 1\n%}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false, multiline: true) - it_parses "{%\n 1\n 2\n 3\n%}", MacroExpression.new(Expressions.new([1.int32, 2.int32, 3.int32] of ASTNode), output: false, multiline: true) + it_parses "{%\n x unless 1\n%}", MacroExpression.new(Unless.new(1.int32, "x".var), output: false) + it_parses "{%\n 1\n 2\n 3\n%}", MacroExpression.new(Expressions.new([1.int32, 2.int32, 3.int32] of ASTNode), output: false) assert_syntax_error "{% unless 1; 2; elsif 3; 4; end %}" assert_syntax_error "{% unless 1 %} 2 {% elsif 3 %} 3 {% end %}" diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 66d18c308845..9ccd8dda1f69 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -2187,21 +2187,13 @@ module Crystal end # A macro expression, - # surrounded by {{ ... }}` (output = true, multiline = false) - # or by `{% ... %}` (output = false, multiline = false) - # - # ``` - # # (output = false, multiline = true) - # {% - # ... - # %} - # ``` + # surrounded by {{ ... }} (output = true) + # or by {% ... %} (output = false) class MacroExpression < ASTNode property exp : ASTNode property? output : Bool - property? multiline : Bool - def initialize(@exp : ASTNode, @output = true, @multiline : Bool = false) + def initialize(@exp : ASTNode, @output = true) end def accept_children(visitor) @@ -2209,10 +2201,10 @@ module Crystal end def clone_without_location - MacroExpression.new(@exp.clone, @output, @multiline) + MacroExpression.new(@exp.clone, @output) end - def_equals_and_hash exp, output?, multiline? + def_equals_and_hash exp, output? end # Free text that is part of a macro diff --git a/src/compiler/crystal/syntax/parser.cr b/src/compiler/crystal/syntax/parser.cr index 3cf31410b41b..569bbd4d9409 100644 --- a/src/compiler/crystal/syntax/parser.cr +++ b/src/compiler/crystal/syntax/parser.cr @@ -3353,13 +3353,7 @@ module Crystal def parse_macro_control(start_location, macro_state = Token::MacroState.default) location = @token.location - next_token_skip_space - multiline = false - - if @token.type.newline? - multiline = true - next_token_skip_space_or_newline - end + next_token_skip_space_or_newline case @token.value when Keyword::FOR @@ -3446,7 +3440,7 @@ module Crystal exps = parse_expressions @in_macro_expression = false - MacroExpression.new(exps, output: false, multiline: multiline).at(location).at_end(token_end_location) + MacroExpression.new(exps, output: false).at(location).at_end(token_end_location) end def parse_macro_if(start_location, macro_state, check_end = true, is_unless = false) diff --git a/src/compiler/crystal/syntax/to_s.cr b/src/compiler/crystal/syntax/to_s.cr index 3da5b72fab4a..d30e2dd52d7f 100644 --- a/src/compiler/crystal/syntax/to_s.cr +++ b/src/compiler/crystal/syntax/to_s.cr @@ -717,11 +717,11 @@ module Crystal end newline - @indent += 1 - inside_macro do - accept node.body + with_indent do + inside_macro do + accept node.body + end end - @indent -= 1 # newline append_indent @@ -730,30 +730,34 @@ module Crystal end def visit(node : MacroExpression) - @str << (node.output? ? "{{ " : node.multiline? ? "{%" : "{% ") + # The node is considered multiline if its starting location is on a different line than its expression. + is_multiline = (start_loc = node.location) && (end_loc = node.exp.location) && end_loc.line_number > start_loc.line_number + + @str << (node.output? ? "{{ " : is_multiline ? "{%" : "{% ") - if node.multiline? + if is_multiline newline @indent += 1 end outside_macro do - # If the MacroExpression consists of a single node we need to manually handle appending indent and trailing newline if #multiline? + # If the MacroExpression consists of a single node we need to manually handle appending indent and trailing newline if *is_multiline* # Otherwise, the Expressions logic handles that for us - if node.multiline? && !node.exp.is_a?(Expressions) + if is_multiline && !node.exp.is_a?(Expressions) append_indent end node.exp.accept self end - if node.multiline? + # If the opening tag has a newline after it, force trailing tag to have one as well + if is_multiline @indent -= 1 append_indent newline if !node.exp.is_a? Expressions end - @str << (node.output? ? " }}" : node.multiline? ? "%}" : " %}") + @str << (node.output? ? " }}" : is_multiline ? "%}" : " %}") false end @@ -810,11 +814,11 @@ module Crystal def visit(node : MacroVerbatim) @str << "{% verbatim do %}" - @indent += 1 - inside_macro do - node.exp.accept self + with_indent do + inside_macro do + node.exp.accept self + end end - @indent -= 1 @str << "{% end %}" false