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.
- checksums.yaml +4 -4
- data/README.md +116 -0
- data/lib/tasks/twig_parity.rake +278 -0
- data/lib/twig/auto_hash.rb +7 -1
- data/lib/twig/callable.rb +28 -1
- data/lib/twig/compiler.rb +35 -3
- data/lib/twig/environment.rb +198 -41
- data/lib/twig/error/base.rb +81 -16
- data/lib/twig/error/loader.rb +8 -0
- data/lib/twig/error/logic.rb +8 -0
- data/lib/twig/error/runtime.rb +8 -0
- data/lib/twig/expression_parser/base.rb +30 -0
- data/lib/twig/expression_parser/expression_parsers.rb +57 -0
- data/lib/twig/expression_parser/infix/arrow.rb +31 -0
- data/lib/twig/expression_parser/infix/binary.rb +34 -0
- data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
- data/lib/twig/expression_parser/infix/dot.rb +72 -0
- data/lib/twig/expression_parser/infix/filter.rb +43 -0
- data/lib/twig/expression_parser/infix/function.rb +67 -0
- data/lib/twig/expression_parser/infix/is.rb +53 -0
- data/lib/twig/expression_parser/infix/is_not.rb +19 -0
- data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
- data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
- data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
- data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
- data/lib/twig/expression_parser/prefix/literal.rb +244 -0
- data/lib/twig/expression_parser/prefix/unary.rb +29 -0
- data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
- data/lib/twig/extension/base.rb +26 -4
- data/lib/twig/extension/core.rb +1076 -48
- data/lib/twig/extension/debug.rb +25 -0
- data/lib/twig/extension/escaper.rb +73 -0
- data/lib/twig/extension/rails.rb +10 -57
- data/lib/twig/extension/string_loader.rb +19 -0
- data/lib/twig/extension_set.rb +117 -20
- data/lib/twig/file_extension_escaping_strategy.rb +35 -0
- data/lib/twig/lexer.rb +225 -81
- data/lib/twig/loader/array.rb +25 -8
- data/lib/twig/loader/chain.rb +93 -0
- data/lib/twig/loader/filesystem.rb +106 -7
- data/lib/twig/node/auto_escape.rb +18 -0
- data/lib/twig/node/base.rb +58 -2
- data/lib/twig/node/block.rb +2 -0
- data/lib/twig/node/block_reference.rb +5 -1
- data/lib/twig/node/body.rb +7 -0
- data/lib/twig/node/cache.rb +50 -0
- data/lib/twig/node/capture.rb +22 -0
- data/lib/twig/node/deprecated.rb +53 -0
- data/lib/twig/node/do.rb +19 -0
- data/lib/twig/node/embed.rb +43 -0
- data/lib/twig/node/expression/array.rb +29 -20
- data/lib/twig/node/expression/arrow_function.rb +55 -0
- data/lib/twig/node/expression/assign_name.rb +1 -1
- data/lib/twig/node/expression/binary/and.rb +17 -0
- data/lib/twig/node/expression/binary/base.rb +6 -4
- data/lib/twig/node/expression/binary/boolean.rb +24 -0
- data/lib/twig/node/expression/binary/concat.rb +20 -0
- data/lib/twig/node/expression/binary/elvis.rb +35 -0
- data/lib/twig/node/expression/binary/ends_with.rb +24 -0
- data/lib/twig/node/expression/binary/floor_div.rb +21 -0
- data/lib/twig/node/expression/binary/has_every.rb +20 -0
- data/lib/twig/node/expression/binary/has_some.rb +20 -0
- data/lib/twig/node/expression/binary/in.rb +20 -0
- data/lib/twig/node/expression/binary/matches.rb +24 -0
- data/lib/twig/node/expression/binary/not_in.rb +20 -0
- data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
- data/lib/twig/node/expression/binary/or.rb +15 -0
- data/lib/twig/node/expression/binary/starts_with.rb +24 -0
- data/lib/twig/node/expression/binary/xor.rb +17 -0
- data/lib/twig/node/expression/block_reference.rb +62 -0
- data/lib/twig/node/expression/call.rb +126 -6
- data/lib/twig/node/expression/constant.rb +3 -1
- data/lib/twig/node/expression/filter/default.rb +37 -0
- data/lib/twig/node/expression/filter/raw.rb +31 -0
- data/lib/twig/node/expression/filter.rb +2 -2
- data/lib/twig/node/expression/function.rb +37 -0
- data/lib/twig/node/expression/get_attribute.rb +51 -7
- data/lib/twig/node/expression/hash.rb +75 -0
- data/lib/twig/node/expression/helper_method.rb +6 -18
- data/lib/twig/node/expression/macro_reference.rb +43 -0
- data/lib/twig/node/expression/name.rb +42 -8
- data/lib/twig/node/expression/operator_escape.rb +13 -0
- data/lib/twig/node/expression/parent.rb +28 -0
- data/lib/twig/node/expression/support_defined_test.rb +23 -0
- data/lib/twig/node/expression/ternary.rb +7 -1
- data/lib/twig/node/expression/test/base.rb +26 -0
- data/lib/twig/node/expression/test/constant.rb +35 -0
- data/lib/twig/node/expression/test/defined.rb +33 -0
- data/lib/twig/node/expression/test/divisible_by.rb +23 -0
- data/lib/twig/node/expression/test/even.rb +21 -0
- data/lib/twig/node/expression/test/iterable.rb +21 -0
- data/lib/twig/node/expression/test/mapping.rb +21 -0
- data/lib/twig/node/expression/test/null.rb +21 -0
- data/lib/twig/node/expression/test/odd.rb +21 -0
- data/lib/twig/node/expression/test/same_as.rb +23 -0
- data/lib/twig/node/expression/test/sequence.rb +21 -0
- data/lib/twig/node/expression/unary/base.rb +3 -1
- data/lib/twig/node/expression/unary/not.rb +18 -0
- data/lib/twig/node/expression/unary/spread.rb +18 -0
- data/lib/twig/node/expression/unary/string_cast.rb +18 -0
- data/lib/twig/node/expression/variable/assign_template.rb +35 -0
- data/lib/twig/node/expression/variable/local.rb +35 -0
- data/lib/twig/node/expression/variable/template.rb +54 -0
- data/lib/twig/node/for.rb +38 -8
- data/lib/twig/node/for_loop.rb +0 -22
- data/lib/twig/node/if.rb +4 -1
- data/lib/twig/node/import.rb +32 -0
- data/lib/twig/node/include.rb +38 -8
- data/lib/twig/node/macro.rb +79 -0
- data/lib/twig/node/module.rb +278 -23
- data/lib/twig/node/output.rb +7 -0
- data/lib/twig/node/print.rb +4 -1
- data/lib/twig/node/set.rb +72 -0
- data/lib/twig/node/text.rb +4 -1
- data/lib/twig/node/with.rb +50 -0
- data/lib/twig/node/yield.rb +6 -1
- data/lib/twig/node_traverser.rb +50 -0
- data/lib/twig/node_visitor/base.rb +30 -0
- data/lib/twig/node_visitor/escaper.rb +165 -0
- data/lib/twig/node_visitor/safe_analysis.rb +127 -0
- data/lib/twig/node_visitor/spreader.rb +39 -0
- data/lib/twig/output_buffer.rb +14 -12
- data/lib/twig/parser.rb +281 -8
- data/lib/twig/rails/config.rb +33 -0
- data/lib/twig/rails/engine.rb +44 -0
- data/lib/twig/rails/renderer.rb +41 -0
- data/lib/twig/runtime/argument_spreader.rb +46 -0
- data/lib/twig/runtime/context.rb +154 -0
- data/lib/twig/runtime/enumerable_hash.rb +51 -0
- data/lib/twig/runtime/escaper.rb +155 -0
- data/lib/twig/runtime/loop_context.rb +81 -0
- data/lib/twig/runtime/loop_iterator.rb +60 -0
- data/lib/twig/runtime/spread.rb +21 -0
- data/lib/twig/runtime_loader/base.rb +12 -0
- data/lib/twig/runtime_loader/factory.rb +23 -0
- data/lib/twig/template.rb +267 -14
- data/lib/twig/template_wrapper.rb +42 -0
- data/lib/twig/token.rb +28 -2
- data/lib/twig/token_parser/apply.rb +48 -0
- data/lib/twig/token_parser/auto_escape.rb +45 -0
- data/lib/twig/token_parser/base.rb +26 -0
- data/lib/twig/token_parser/block.rb +4 -4
- data/lib/twig/token_parser/cache.rb +31 -0
- data/lib/twig/token_parser/deprecated.rb +40 -0
- data/lib/twig/token_parser/do.rb +19 -0
- data/lib/twig/token_parser/embed.rb +62 -0
- data/lib/twig/token_parser/extends.rb +4 -3
- data/lib/twig/token_parser/for.rb +14 -9
- data/lib/twig/token_parser/from.rb +57 -0
- data/lib/twig/token_parser/guard.rb +65 -0
- data/lib/twig/token_parser/if.rb +9 -9
- data/lib/twig/token_parser/import.rb +29 -0
- data/lib/twig/token_parser/include.rb +2 -2
- data/lib/twig/token_parser/macro.rb +109 -0
- data/lib/twig/token_parser/set.rb +76 -0
- data/lib/twig/token_parser/use.rb +54 -0
- data/lib/twig/token_parser/with.rb +36 -0
- data/lib/twig/token_parser/yield.rb +7 -7
- data/lib/twig/token_stream.rb +23 -3
- data/lib/twig/twig_filter.rb +20 -0
- data/lib/twig/twig_function.rb +37 -0
- data/lib/twig/twig_test.rb +31 -0
- data/lib/twig/util/callable_arguments_extractor.rb +227 -0
- data/lib/twig_ruby.rb +21 -2
- metadata +148 -6
- data/lib/twig/context.rb +0 -64
- data/lib/twig/expression_parser.rb +0 -517
- 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
|