twig_ruby 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +116 -0
  3. data/lib/tasks/twig_parity.rake +278 -0
  4. data/lib/twig/auto_hash.rb +7 -1
  5. data/lib/twig/callable.rb +28 -1
  6. data/lib/twig/compiler.rb +35 -3
  7. data/lib/twig/environment.rb +198 -41
  8. data/lib/twig/error/base.rb +81 -16
  9. data/lib/twig/error/loader.rb +8 -0
  10. data/lib/twig/error/logic.rb +8 -0
  11. data/lib/twig/error/runtime.rb +8 -0
  12. data/lib/twig/expression_parser/base.rb +30 -0
  13. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  14. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  15. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  16. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  17. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  18. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  19. data/lib/twig/expression_parser/infix/function.rb +67 -0
  20. data/lib/twig/expression_parser/infix/is.rb +53 -0
  21. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  22. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  23. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  24. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  25. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  26. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  27. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  28. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  29. data/lib/twig/extension/base.rb +26 -4
  30. data/lib/twig/extension/core.rb +1076 -48
  31. data/lib/twig/extension/debug.rb +25 -0
  32. data/lib/twig/extension/escaper.rb +73 -0
  33. data/lib/twig/extension/rails.rb +10 -57
  34. data/lib/twig/extension/string_loader.rb +19 -0
  35. data/lib/twig/extension_set.rb +117 -20
  36. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  37. data/lib/twig/lexer.rb +225 -81
  38. data/lib/twig/loader/array.rb +25 -8
  39. data/lib/twig/loader/chain.rb +93 -0
  40. data/lib/twig/loader/filesystem.rb +106 -7
  41. data/lib/twig/node/auto_escape.rb +18 -0
  42. data/lib/twig/node/base.rb +58 -2
  43. data/lib/twig/node/block.rb +2 -0
  44. data/lib/twig/node/block_reference.rb +5 -1
  45. data/lib/twig/node/body.rb +7 -0
  46. data/lib/twig/node/cache.rb +50 -0
  47. data/lib/twig/node/capture.rb +22 -0
  48. data/lib/twig/node/deprecated.rb +53 -0
  49. data/lib/twig/node/do.rb +19 -0
  50. data/lib/twig/node/embed.rb +43 -0
  51. data/lib/twig/node/expression/array.rb +29 -20
  52. data/lib/twig/node/expression/arrow_function.rb +55 -0
  53. data/lib/twig/node/expression/assign_name.rb +1 -1
  54. data/lib/twig/node/expression/binary/and.rb +17 -0
  55. data/lib/twig/node/expression/binary/base.rb +6 -4
  56. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  57. data/lib/twig/node/expression/binary/concat.rb +20 -0
  58. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  59. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  60. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  61. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  62. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  63. data/lib/twig/node/expression/binary/in.rb +20 -0
  64. data/lib/twig/node/expression/binary/matches.rb +24 -0
  65. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  66. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  67. data/lib/twig/node/expression/binary/or.rb +15 -0
  68. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  69. data/lib/twig/node/expression/binary/xor.rb +17 -0
  70. data/lib/twig/node/expression/block_reference.rb +62 -0
  71. data/lib/twig/node/expression/call.rb +126 -6
  72. data/lib/twig/node/expression/constant.rb +3 -1
  73. data/lib/twig/node/expression/filter/default.rb +37 -0
  74. data/lib/twig/node/expression/filter/raw.rb +31 -0
  75. data/lib/twig/node/expression/filter.rb +2 -2
  76. data/lib/twig/node/expression/function.rb +37 -0
  77. data/lib/twig/node/expression/get_attribute.rb +51 -7
  78. data/lib/twig/node/expression/hash.rb +75 -0
  79. data/lib/twig/node/expression/helper_method.rb +6 -18
  80. data/lib/twig/node/expression/macro_reference.rb +43 -0
  81. data/lib/twig/node/expression/name.rb +42 -8
  82. data/lib/twig/node/expression/operator_escape.rb +13 -0
  83. data/lib/twig/node/expression/parent.rb +28 -0
  84. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  85. data/lib/twig/node/expression/ternary.rb +7 -1
  86. data/lib/twig/node/expression/test/base.rb +26 -0
  87. data/lib/twig/node/expression/test/constant.rb +35 -0
  88. data/lib/twig/node/expression/test/defined.rb +33 -0
  89. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  90. data/lib/twig/node/expression/test/even.rb +21 -0
  91. data/lib/twig/node/expression/test/iterable.rb +21 -0
  92. data/lib/twig/node/expression/test/mapping.rb +21 -0
  93. data/lib/twig/node/expression/test/null.rb +21 -0
  94. data/lib/twig/node/expression/test/odd.rb +21 -0
  95. data/lib/twig/node/expression/test/same_as.rb +23 -0
  96. data/lib/twig/node/expression/test/sequence.rb +21 -0
  97. data/lib/twig/node/expression/unary/base.rb +3 -1
  98. data/lib/twig/node/expression/unary/not.rb +18 -0
  99. data/lib/twig/node/expression/unary/spread.rb +18 -0
  100. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  101. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  102. data/lib/twig/node/expression/variable/local.rb +35 -0
  103. data/lib/twig/node/expression/variable/template.rb +54 -0
  104. data/lib/twig/node/for.rb +38 -8
  105. data/lib/twig/node/for_loop.rb +0 -22
  106. data/lib/twig/node/if.rb +4 -1
  107. data/lib/twig/node/import.rb +32 -0
  108. data/lib/twig/node/include.rb +38 -8
  109. data/lib/twig/node/macro.rb +79 -0
  110. data/lib/twig/node/module.rb +278 -23
  111. data/lib/twig/node/output.rb +7 -0
  112. data/lib/twig/node/print.rb +4 -1
  113. data/lib/twig/node/set.rb +72 -0
  114. data/lib/twig/node/text.rb +4 -1
  115. data/lib/twig/node/with.rb +50 -0
  116. data/lib/twig/node/yield.rb +6 -1
  117. data/lib/twig/node_traverser.rb +50 -0
  118. data/lib/twig/node_visitor/base.rb +30 -0
  119. data/lib/twig/node_visitor/escaper.rb +165 -0
  120. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  121. data/lib/twig/node_visitor/spreader.rb +39 -0
  122. data/lib/twig/output_buffer.rb +14 -12
  123. data/lib/twig/parser.rb +281 -8
  124. data/lib/twig/rails/config.rb +33 -0
  125. data/lib/twig/rails/engine.rb +44 -0
  126. data/lib/twig/rails/renderer.rb +41 -0
  127. data/lib/twig/runtime/argument_spreader.rb +46 -0
  128. data/lib/twig/runtime/context.rb +154 -0
  129. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  130. data/lib/twig/runtime/escaper.rb +155 -0
  131. data/lib/twig/runtime/loop_context.rb +81 -0
  132. data/lib/twig/runtime/loop_iterator.rb +60 -0
  133. data/lib/twig/runtime/spread.rb +21 -0
  134. data/lib/twig/runtime_loader/base.rb +12 -0
  135. data/lib/twig/runtime_loader/factory.rb +23 -0
  136. data/lib/twig/template.rb +267 -14
  137. data/lib/twig/template_wrapper.rb +42 -0
  138. data/lib/twig/token.rb +28 -2
  139. data/lib/twig/token_parser/apply.rb +48 -0
  140. data/lib/twig/token_parser/auto_escape.rb +45 -0
  141. data/lib/twig/token_parser/base.rb +26 -0
  142. data/lib/twig/token_parser/block.rb +4 -4
  143. data/lib/twig/token_parser/cache.rb +31 -0
  144. data/lib/twig/token_parser/deprecated.rb +40 -0
  145. data/lib/twig/token_parser/do.rb +19 -0
  146. data/lib/twig/token_parser/embed.rb +62 -0
  147. data/lib/twig/token_parser/extends.rb +4 -3
  148. data/lib/twig/token_parser/for.rb +14 -9
  149. data/lib/twig/token_parser/from.rb +57 -0
  150. data/lib/twig/token_parser/guard.rb +65 -0
  151. data/lib/twig/token_parser/if.rb +9 -9
  152. data/lib/twig/token_parser/import.rb +29 -0
  153. data/lib/twig/token_parser/include.rb +2 -2
  154. data/lib/twig/token_parser/macro.rb +109 -0
  155. data/lib/twig/token_parser/set.rb +76 -0
  156. data/lib/twig/token_parser/use.rb +54 -0
  157. data/lib/twig/token_parser/with.rb +36 -0
  158. data/lib/twig/token_parser/yield.rb +7 -7
  159. data/lib/twig/token_stream.rb +23 -3
  160. data/lib/twig/twig_filter.rb +20 -0
  161. data/lib/twig/twig_function.rb +37 -0
  162. data/lib/twig/twig_test.rb +31 -0
  163. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  164. data/lib/twig_ruby.rb +21 -2
  165. metadata +148 -6
  166. data/lib/twig/context.rb +0 -64
  167. data/lib/twig/expression_parser.rb +0 -517
  168. data/lib/twig/railtie.rb +0 -60
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class Dot < InfixExpressionParser
7
+ include ParsesArguments
8
+
9
+ def parse(parser, left, _token)
10
+ stream = parser.stream
11
+ token = stream.current
12
+ lineno = token.lineno
13
+ arguments = Node::Expression::Array.new(AutoHash.new, lineno)
14
+ type = Template::ANY_CALL
15
+
16
+ if stream.next_if(Token::OPERATOR_TYPE, '(')
17
+ attribute = parser.parse_expression
18
+ stream.expect(Token::PUNCTUATION_TYPE, ')')
19
+ else
20
+ token = stream.next
21
+
22
+ if [Token::NAME_TYPE, Token::NUMBER_TYPE].include?(token.type) ||
23
+ (token.type == Token::OPERATOR_TYPE && token.value.match(/\A#{Lexer::REGEX_NAME}/))
24
+ attribute = Node::Expression::Constant.new(token.value, token.lineno)
25
+ else
26
+ raise Error::Syntax.new(
27
+ "Expected name or number, got value \"#{token.value}\" of type #{token.type}.",
28
+ token.lineno,
29
+ stream.source
30
+ )
31
+ end
32
+ end
33
+
34
+ if stream.test(Token::OPERATOR_TYPE, '(')
35
+ type = Template::METHOD_CALL
36
+ arguments = parse_callable_arguments(parser, token.lineno)
37
+ end
38
+
39
+ if left.is_a?(Node::Expression::Name) && (
40
+ parser.imported_symbol(:template, left.attributes[:name]) ||
41
+ (left.attributes[:name] == '_self' && attribute.is_a?(Node::Expression::Constant))
42
+ )
43
+ return Node::Expression::MacroReference.new(
44
+ Node::Expression::Variable::Template.new(left.attributes[:name], left.lineno),
45
+ "macro_#{attribute.attributes[:value]}",
46
+ arguments,
47
+ left.lineno
48
+ )
49
+ end
50
+
51
+ Node::Expression::GetAttribute.new(left, attribute, arguments, type, token.lineno)
52
+ end
53
+
54
+ def name
55
+ '.'
56
+ end
57
+
58
+ def description
59
+ 'Get an attribute on a variable'
60
+ end
61
+
62
+ def precedence
63
+ 512
64
+ end
65
+
66
+ def associativity
67
+ LEFT
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class Filter < InfixExpressionParser
7
+ include ParsesArguments
8
+
9
+ def parse(parser, left, _token)
10
+ stream = parser.stream
11
+ token = stream.expect(Token::NAME_TYPE)
12
+ line = token.lineno
13
+
14
+ arguments = if stream.test(Token::OPERATOR_TYPE, '(')
15
+ parse_named_arguments(parser)
16
+ else
17
+ Node::Empty.new
18
+ end
19
+
20
+ filter = parser.filter(token.value, line)
21
+
22
+ filter.node_class.new(left, filter, arguments, token.lineno)
23
+ end
24
+
25
+ def name
26
+ '|'
27
+ end
28
+
29
+ def description
30
+ 'Twig filter call'
31
+ end
32
+
33
+ def precedence
34
+ 300
35
+ end
36
+
37
+ def associativity
38
+ LEFT
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class Function < InfixExpressionParser
7
+ include ParsesArguments
8
+
9
+ def parse(parser, left, token)
10
+ line = token.lineno
11
+
12
+ unless left.is_a?(Node::Expression::Variable::Context)
13
+ raise Error::Syntax.new(
14
+ "Function name must be an identifier. got #{left.inspect}",
15
+ line,
16
+ parser.stream.source
17
+ )
18
+ end
19
+
20
+ name = left.attributes[:name]
21
+
22
+ if (aliased = parser.imported_symbol(:function, name))
23
+ return Node::Expression::MacroReference.new(
24
+ aliased[:node].nodes[:var],
25
+ aliased[:name],
26
+ parse_callable_arguments(parser, line, parse_open_parenthesis: false),
27
+ line
28
+ )
29
+ end
30
+
31
+ args = parse_named_arguments(parser, parse_open_parenthesis: false)
32
+ function = parser.function(name, args, line)
33
+
34
+ # Helper method returned
35
+ if function.is_a?(Node::Expression::Base)
36
+ return function
37
+ end
38
+
39
+ if (callable = function.parser_callable)
40
+ fake_node = Node::Empty.new(line)
41
+ fake_node.source_context = parser.stream.source
42
+
43
+ return callable.call(parser, fake_node, args, line)
44
+ end
45
+
46
+ function.node_class.new(function, args, line)
47
+ end
48
+
49
+ def name
50
+ '('
51
+ end
52
+
53
+ def description
54
+ 'Twig function call'
55
+ end
56
+
57
+ def precedence
58
+ 512
59
+ end
60
+
61
+ def associativity
62
+ LEFT
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class Is < InfixExpressionParser
7
+ include ParsesArguments
8
+
9
+ def parse(parser, left, token)
10
+ stream = parser.stream
11
+ test = parser.test(left.lineno)
12
+
13
+ arguments = nil
14
+ if stream.test(Token::OPERATOR_TYPE, '(')
15
+ arguments = parse_named_arguments(parser)
16
+ elsif test.one_mandatory_argument?
17
+ arguments = Node::Nodes.new(AutoHash.new.add(
18
+ parser.parse_expression(precedence)
19
+ ))
20
+ end
21
+
22
+ if test.name == 'defined' && left.is_a?(Node::Expression::Variable::Context) &&
23
+ !(aliased = parser.imported_symbol(:function, left.attributes[:name])).nil?
24
+ left = Node::Expression::MacroReference.new(
25
+ aliased[:node].nodes[:var],
26
+ aliased[:name],
27
+ Node::Expression::Array.new(AutoHash.new, left.lineno),
28
+ left.lineno
29
+ )
30
+ end
31
+
32
+ test.node_class.new(left, test, arguments, parser.current_token.lineno)
33
+ end
34
+
35
+ def name
36
+ 'is'
37
+ end
38
+
39
+ def description
40
+ 'Twig tests'
41
+ end
42
+
43
+ def precedence
44
+ 100
45
+ end
46
+
47
+ def associativity
48
+ LEFT
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class IsNot < Is
7
+ include ParsesArguments
8
+
9
+ def parse(parser, left, token)
10
+ Node::Expression::Unary::Not.new(super, token.lineno)
11
+ end
12
+
13
+ def name
14
+ 'is not'
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module ParsesArguments
6
+ private
7
+
8
+ # @param [Parser] parser
9
+ # @param [Integer] lineno
10
+ def parse_callable_arguments(parser, lineno, parse_open_parenthesis: true)
11
+ arguments = Node::Expression::Hash.new({}, lineno)
12
+
13
+ parse_named_arguments(parser, parse_open_parenthesis:).nodes.each do |key, node|
14
+ arguments.add_element(node, Node::Expression::Variable::Local.new(key, lineno))
15
+ end
16
+
17
+ arguments
18
+ end
19
+
20
+ # @param [Parser] parser
21
+ def parse_named_arguments(parser, parse_open_parenthesis: true)
22
+ args = AutoHash.new
23
+ stream = parser.stream
24
+
25
+ if parse_open_parenthesis
26
+ stream.expect(Token::OPERATOR_TYPE, '(', 'A list of arguments must begin with an opening parenthesis')
27
+ end
28
+
29
+ has_spread = false
30
+
31
+ until stream.test(Token::PUNCTUATION_TYPE, ')')
32
+ unless args.empty?
33
+ stream.expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma')
34
+
35
+ # if above was trailing comma, exit early
36
+ break if stream.test(Token::PUNCTUATION_TYPE, ')')
37
+ end
38
+
39
+ if stream.next_if(Token::OPERATOR_TYPE, '...')
40
+ has_spread = true
41
+ value = Node::Expression::Unary::Spread.new(parser.parse_expression, stream.current.lineno)
42
+ elsif has_spread
43
+ raise Error::Syntax.new(
44
+ 'Normal arguments must be placed before argument unpacking.',
45
+ stream.current.lineno,
46
+ stream.source
47
+ )
48
+ else
49
+ value = parser.parse_expression
50
+ end
51
+
52
+ name = nil
53
+ if (token = stream.next_if(Token::OPERATOR_TYPE, '=')) ||
54
+ (token = stream.next_if(Token::PUNCTUATION_TYPE, ':'))
55
+ # Allow quoted kwargs - form_with("data-turbo-stream": true)
56
+ if value.is_a?(Node::Expression::Constant) && value.attributes[:value].is_a?(String)
57
+ name = value.attributes[:value]
58
+ elsif value.is_a?(Node::Expression::Name)
59
+ name = value.attributes[:name]
60
+ else
61
+ raise Error::Syntax.new(
62
+ "A parameter name must be a string, #{value.class.name} given.",
63
+ token.lineno,
64
+ stream.source
65
+ )
66
+ end
67
+
68
+ value = parser.parse_expression
69
+ end
70
+
71
+ if name.nil?
72
+ args.add(value)
73
+ else
74
+ args[name] = value
75
+ end
76
+ end
77
+
78
+ stream.expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis')
79
+
80
+ Node::Nodes.new(args)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Infix
6
+ class SquareBracket < InfixExpressionParser
7
+ def parse(parser, left, token)
8
+ stream = parser.stream
9
+ lineno = token.lineno
10
+ arguments = Node::Expression::Array.new({}, lineno)
11
+
12
+ slice = false
13
+ if stream.test(Token::PUNCTUATION_TYPE, ':')
14
+ slice = true
15
+ attribute = Node::Expression::Constant.new(0, token.lineno)
16
+ else
17
+ attribute = parser.parse_expression
18
+ end
19
+
20
+ if stream.next_if(Token::PUNCTUATION_TYPE, ':')
21
+ slice = true
22
+ end
23
+
24
+ if slice || stream.test(Token::SYMBOL_TYPE)
25
+ length = if stream.test(Token::PUNCTUATION_TYPE, ']')
26
+ Node::Expression::Constant.new(nil, token.lineno)
27
+ elsif stream.test(Token::SYMBOL_TYPE)
28
+ token = stream.next
29
+ Node::Expression::Variable::Context.new(token.value, token.lineno)
30
+ else
31
+ parser.parse_expression
32
+ end
33
+
34
+ filter = parser.filter('slice', token.lineno)
35
+ arguments = Node::Nodes.new(AutoHash.new.add(attribute, length))
36
+ filter = filter.node_class.new(left, filter, arguments, token.lineno)
37
+
38
+ stream.expect(Token::PUNCTUATION_TYPE, ']')
39
+
40
+ return filter
41
+ end
42
+
43
+ stream.expect(Token::PUNCTUATION_TYPE, ']')
44
+
45
+ Node::Expression::GetAttribute.new(left, attribute, arguments, Template::ARRAY_CALL, lineno)
46
+ end
47
+
48
+ def name
49
+ '['
50
+ end
51
+
52
+ def description
53
+ 'Array access'
54
+ end
55
+
56
+ def precedence
57
+ 512
58
+ end
59
+
60
+ def associativity
61
+ LEFT
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ class InfixExpressionParser < ExpressionParser::Base
6
+ LEFT = 'left'
7
+ RIGHT = 'right'
8
+
9
+ # @param [Parser] parser
10
+ # @param [Node::Expression::Base] left
11
+ # @param [Token] token
12
+ # @return [Node::Expression::Base]
13
+ def parse(parser, left, token)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def associativity
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def left?
22
+ associativity == LEFT
23
+ end
24
+
25
+ def right?
26
+ associativity == RIGHT
27
+ end
28
+
29
+ def type
30
+ :infix
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module ExpressionParser
5
+ module Prefix
6
+ class Grouping < PrefixExpressionParser
7
+ def parse(parser, token)
8
+ stream = parser.stream
9
+ expr = parser.parse_expression(precedence)
10
+
11
+ if stream.next_if(Token::PUNCTUATION_TYPE, ')')
12
+ unless stream.test(Token::OPERATOR_TYPE, '=>')
13
+ return expr.set_explicit_parentheses
14
+ end
15
+
16
+ return Node::Expression::Array.new(AutoHash.new.add(expr), token.lineno)
17
+ end
18
+
19
+ # determine if we are parsing arrow function arguments
20
+ unless stream.test(Token::PUNCTUATION_TYPE, ',')
21
+ stream.expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed.')
22
+ end
23
+
24
+ names = AutoHash.new.add(expr)
25
+ loop do
26
+ if stream.next_if(Token::PUNCTUATION_TYPE, ')')
27
+ break
28
+ end
29
+
30
+ stream.expect(Token::PUNCTUATION_TYPE, ',')
31
+ token = stream.expect(Token::NAME_TYPE)
32
+ names << Node::Expression::Variable::Context.new(token.value, token.lineno)
33
+ end
34
+
35
+ unless stream.test(Token::OPERATOR_TYPE, '=>')
36
+ raise Error::Syntax.new(
37
+ 'A list of variables must be followed by an arrow.',
38
+ stream.current.lineno,
39
+ stream.source
40
+ )
41
+ end
42
+
43
+ Node::Expression::Array.new(names, token.lineno)
44
+ end
45
+
46
+ def name
47
+ '('
48
+ end
49
+
50
+ def description
51
+ 'Explicit group expression (a)'
52
+ end
53
+
54
+ def precedence
55
+ 0
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end