twig_ruby 0.0.1 → 0.0.2

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tasks/twig_parity.rake +278 -0
  3. data/lib/twig/auto_hash.rb +7 -1
  4. data/lib/twig/callable.rb +28 -1
  5. data/lib/twig/compiler.rb +35 -3
  6. data/lib/twig/environment.rb +198 -41
  7. data/lib/twig/error/base.rb +81 -16
  8. data/lib/twig/error/loader.rb +8 -0
  9. data/lib/twig/error/logic.rb +8 -0
  10. data/lib/twig/error/runtime.rb +8 -0
  11. data/lib/twig/expression_parser/base.rb +30 -0
  12. data/lib/twig/expression_parser/expression_parsers.rb +57 -0
  13. data/lib/twig/expression_parser/infix/arrow.rb +31 -0
  14. data/lib/twig/expression_parser/infix/binary.rb +34 -0
  15. data/lib/twig/expression_parser/infix/conditional_ternary.rb +39 -0
  16. data/lib/twig/expression_parser/infix/dot.rb +72 -0
  17. data/lib/twig/expression_parser/infix/filter.rb +43 -0
  18. data/lib/twig/expression_parser/infix/function.rb +67 -0
  19. data/lib/twig/expression_parser/infix/is.rb +53 -0
  20. data/lib/twig/expression_parser/infix/is_not.rb +19 -0
  21. data/lib/twig/expression_parser/infix/parses_arguments.rb +84 -0
  22. data/lib/twig/expression_parser/infix/square_bracket.rb +66 -0
  23. data/lib/twig/expression_parser/infix_expression_parser.rb +34 -0
  24. data/lib/twig/expression_parser/prefix/grouping.rb +60 -0
  25. data/lib/twig/expression_parser/prefix/literal.rb +244 -0
  26. data/lib/twig/expression_parser/prefix/unary.rb +29 -0
  27. data/lib/twig/expression_parser/prefix_expression_parser.rb +18 -0
  28. data/lib/twig/extension/base.rb +26 -4
  29. data/lib/twig/extension/core.rb +1076 -48
  30. data/lib/twig/extension/debug.rb +25 -0
  31. data/lib/twig/extension/escaper.rb +73 -0
  32. data/lib/twig/extension/rails.rb +10 -57
  33. data/lib/twig/extension/string_loader.rb +19 -0
  34. data/lib/twig/extension_set.rb +117 -20
  35. data/lib/twig/file_extension_escaping_strategy.rb +35 -0
  36. data/lib/twig/lexer.rb +225 -81
  37. data/lib/twig/loader/array.rb +25 -8
  38. data/lib/twig/loader/chain.rb +93 -0
  39. data/lib/twig/loader/filesystem.rb +106 -7
  40. data/lib/twig/node/auto_escape.rb +18 -0
  41. data/lib/twig/node/base.rb +58 -2
  42. data/lib/twig/node/block.rb +2 -0
  43. data/lib/twig/node/block_reference.rb +5 -1
  44. data/lib/twig/node/body.rb +7 -0
  45. data/lib/twig/node/cache.rb +50 -0
  46. data/lib/twig/node/capture.rb +22 -0
  47. data/lib/twig/node/deprecated.rb +53 -0
  48. data/lib/twig/node/do.rb +19 -0
  49. data/lib/twig/node/embed.rb +43 -0
  50. data/lib/twig/node/expression/array.rb +29 -20
  51. data/lib/twig/node/expression/arrow_function.rb +55 -0
  52. data/lib/twig/node/expression/assign_name.rb +1 -1
  53. data/lib/twig/node/expression/binary/and.rb +17 -0
  54. data/lib/twig/node/expression/binary/base.rb +6 -4
  55. data/lib/twig/node/expression/binary/boolean.rb +24 -0
  56. data/lib/twig/node/expression/binary/concat.rb +20 -0
  57. data/lib/twig/node/expression/binary/elvis.rb +35 -0
  58. data/lib/twig/node/expression/binary/ends_with.rb +24 -0
  59. data/lib/twig/node/expression/binary/floor_div.rb +21 -0
  60. data/lib/twig/node/expression/binary/has_every.rb +20 -0
  61. data/lib/twig/node/expression/binary/has_some.rb +20 -0
  62. data/lib/twig/node/expression/binary/in.rb +20 -0
  63. data/lib/twig/node/expression/binary/matches.rb +24 -0
  64. data/lib/twig/node/expression/binary/not_in.rb +20 -0
  65. data/lib/twig/node/expression/binary/null_coalesce.rb +49 -0
  66. data/lib/twig/node/expression/binary/or.rb +15 -0
  67. data/lib/twig/node/expression/binary/starts_with.rb +24 -0
  68. data/lib/twig/node/expression/binary/xor.rb +17 -0
  69. data/lib/twig/node/expression/block_reference.rb +62 -0
  70. data/lib/twig/node/expression/call.rb +126 -6
  71. data/lib/twig/node/expression/constant.rb +3 -1
  72. data/lib/twig/node/expression/filter/default.rb +37 -0
  73. data/lib/twig/node/expression/filter/raw.rb +31 -0
  74. data/lib/twig/node/expression/filter.rb +2 -2
  75. data/lib/twig/node/expression/function.rb +37 -0
  76. data/lib/twig/node/expression/get_attribute.rb +51 -7
  77. data/lib/twig/node/expression/hash.rb +75 -0
  78. data/lib/twig/node/expression/helper_method.rb +6 -18
  79. data/lib/twig/node/expression/macro_reference.rb +43 -0
  80. data/lib/twig/node/expression/name.rb +42 -8
  81. data/lib/twig/node/expression/operator_escape.rb +13 -0
  82. data/lib/twig/node/expression/parent.rb +28 -0
  83. data/lib/twig/node/expression/support_defined_test.rb +23 -0
  84. data/lib/twig/node/expression/ternary.rb +7 -1
  85. data/lib/twig/node/expression/test/base.rb +26 -0
  86. data/lib/twig/node/expression/test/constant.rb +35 -0
  87. data/lib/twig/node/expression/test/defined.rb +33 -0
  88. data/lib/twig/node/expression/test/divisible_by.rb +23 -0
  89. data/lib/twig/node/expression/test/even.rb +21 -0
  90. data/lib/twig/node/expression/test/iterable.rb +21 -0
  91. data/lib/twig/node/expression/test/mapping.rb +21 -0
  92. data/lib/twig/node/expression/test/null.rb +21 -0
  93. data/lib/twig/node/expression/test/odd.rb +21 -0
  94. data/lib/twig/node/expression/test/same_as.rb +23 -0
  95. data/lib/twig/node/expression/test/sequence.rb +21 -0
  96. data/lib/twig/node/expression/unary/base.rb +3 -1
  97. data/lib/twig/node/expression/unary/not.rb +18 -0
  98. data/lib/twig/node/expression/unary/spread.rb +18 -0
  99. data/lib/twig/node/expression/unary/string_cast.rb +18 -0
  100. data/lib/twig/node/expression/variable/assign_template.rb +35 -0
  101. data/lib/twig/node/expression/variable/local.rb +35 -0
  102. data/lib/twig/node/expression/variable/template.rb +54 -0
  103. data/lib/twig/node/for.rb +38 -8
  104. data/lib/twig/node/for_loop.rb +0 -22
  105. data/lib/twig/node/if.rb +4 -1
  106. data/lib/twig/node/import.rb +32 -0
  107. data/lib/twig/node/include.rb +38 -8
  108. data/lib/twig/node/macro.rb +79 -0
  109. data/lib/twig/node/module.rb +278 -23
  110. data/lib/twig/node/output.rb +7 -0
  111. data/lib/twig/node/print.rb +4 -1
  112. data/lib/twig/node/set.rb +72 -0
  113. data/lib/twig/node/text.rb +4 -1
  114. data/lib/twig/node/with.rb +50 -0
  115. data/lib/twig/node/yield.rb +6 -1
  116. data/lib/twig/node_traverser.rb +50 -0
  117. data/lib/twig/node_visitor/base.rb +30 -0
  118. data/lib/twig/node_visitor/escaper.rb +165 -0
  119. data/lib/twig/node_visitor/safe_analysis.rb +127 -0
  120. data/lib/twig/node_visitor/spreader.rb +39 -0
  121. data/lib/twig/output_buffer.rb +14 -12
  122. data/lib/twig/parser.rb +281 -8
  123. data/lib/twig/rails/config.rb +33 -0
  124. data/lib/twig/rails/engine.rb +44 -0
  125. data/lib/twig/rails/renderer.rb +41 -0
  126. data/lib/twig/runtime/argument_spreader.rb +46 -0
  127. data/lib/twig/runtime/context.rb +154 -0
  128. data/lib/twig/runtime/enumerable_hash.rb +51 -0
  129. data/lib/twig/runtime/escaper.rb +155 -0
  130. data/lib/twig/runtime/loop_context.rb +81 -0
  131. data/lib/twig/runtime/loop_iterator.rb +60 -0
  132. data/lib/twig/runtime/spread.rb +21 -0
  133. data/lib/twig/runtime_loader/base.rb +12 -0
  134. data/lib/twig/runtime_loader/factory.rb +23 -0
  135. data/lib/twig/template.rb +267 -14
  136. data/lib/twig/template_wrapper.rb +42 -0
  137. data/lib/twig/token.rb +28 -2
  138. data/lib/twig/token_parser/apply.rb +48 -0
  139. data/lib/twig/token_parser/auto_escape.rb +45 -0
  140. data/lib/twig/token_parser/base.rb +26 -0
  141. data/lib/twig/token_parser/block.rb +4 -4
  142. data/lib/twig/token_parser/cache.rb +31 -0
  143. data/lib/twig/token_parser/deprecated.rb +40 -0
  144. data/lib/twig/token_parser/do.rb +19 -0
  145. data/lib/twig/token_parser/embed.rb +62 -0
  146. data/lib/twig/token_parser/extends.rb +4 -3
  147. data/lib/twig/token_parser/for.rb +14 -9
  148. data/lib/twig/token_parser/from.rb +57 -0
  149. data/lib/twig/token_parser/guard.rb +65 -0
  150. data/lib/twig/token_parser/if.rb +9 -9
  151. data/lib/twig/token_parser/import.rb +29 -0
  152. data/lib/twig/token_parser/include.rb +2 -2
  153. data/lib/twig/token_parser/macro.rb +109 -0
  154. data/lib/twig/token_parser/set.rb +76 -0
  155. data/lib/twig/token_parser/use.rb +54 -0
  156. data/lib/twig/token_parser/with.rb +36 -0
  157. data/lib/twig/token_parser/yield.rb +7 -7
  158. data/lib/twig/token_stream.rb +23 -3
  159. data/lib/twig/twig_filter.rb +20 -0
  160. data/lib/twig/twig_function.rb +37 -0
  161. data/lib/twig/twig_test.rb +31 -0
  162. data/lib/twig/util/callable_arguments_extractor.rb +227 -0
  163. data/lib/twig_ruby.rb +21 -2
  164. metadata +145 -6
  165. data/lib/twig/context.rb +0 -64
  166. data/lib/twig/expression_parser.rb +0 -517
  167. data/lib/twig/railtie.rb +0 -60
@@ -3,19 +3,71 @@
3
3
  module Twig
4
4
  module Loader
5
5
  class Filesystem < Loader::Base
6
+ MAIN_NAMESPACE = '__main__'
7
+
6
8
  def initialize(root_path, paths = [])
7
9
  super()
8
10
 
9
11
  @root_path = root_path.to_s
10
- @paths = paths.map(&:to_s)
12
+ @paths = {}
13
+ @cache = {}
14
+ @error_cache = {}
15
+
16
+ set_paths(paths)
17
+ end
18
+
19
+ def exists?(name)
20
+ return true if @cache.key?(name)
21
+
22
+ !find_template(name, throw: false).nil?
23
+ end
24
+
25
+ def paths(namespace = MAIN_NAMESPACE)
26
+ @paths[namespace] || []
27
+ end
28
+
29
+ def set_paths(paths, namespace = MAIN_NAMESPACE)
30
+ paths = [paths] unless paths.is_a?(::Array)
31
+
32
+ @paths[namespace] = []
33
+
34
+ paths.each do |path|
35
+ add_path(path, namespace)
36
+ end
37
+ end
38
+
39
+ def add_path(path, namespace = MAIN_NAMESPACE)
40
+ @cache = {}
41
+ @error_cache = {}
42
+ check_path = path[0] == '/' ? path : File.join(@root_path, path)
43
+
44
+ unless File.directory?(check_path)
45
+ raise Error::Loader, "The \"#{path}\" directory does not exist (#{check_path})."
46
+ end
47
+
48
+ @paths[namespace] ||= []
49
+ @paths[namespace] << path
50
+ end
51
+
52
+ def prepend_path(path, namespace = MAIN_NAMESPACE)
53
+ @cache = {}
54
+ @error_cache = {}
55
+ check_path = path[0] == '/' ? path : File.join(@root_path, path)
56
+
57
+ unless File.directory?(check_path)
58
+ raise Error::Loader, "The \"#{path}\" directory does not exist (#{check_path})."
59
+ end
60
+
61
+ @paths[namespace] ||= []
62
+ @paths[namespace].unshift(path)
11
63
  end
12
64
 
13
65
  def get_source_context(name)
14
66
  if (file = find_template(name))
15
- return Source.new(File.read(file), name)
67
+ return Source.new(File.read(file), name, file)
16
68
  end
17
69
 
18
- raise "Unable to find '#{name}'"
70
+ raise Error::Loader, "Unable to find template \"#{name}\" (looked into: #{@paths.inspect})."
19
71
  end
20
72
 
21
73
  def get_cache_key(name)
@@ -34,11 +86,58 @@ module Twig
34
86
 
35
87
  private
36
88
 
37
- def find_template(name)
38
- @paths.each do |path|
39
- absolute = File.join(@root_path, path, name)
40
- return absolute if File.file?(absolute)
89
+ def find_template(name, throw: true)
90
+ return @cache[name] if @cache.key?(name)
91
+
92
+ begin
93
+ namespace, shortname = parse_name(name)
94
+ rescue Error::Loader => e
95
+ return nil unless throw
96
+ raise e if throw
97
+ end
98
+
99
+ unless @paths.key?(namespace)
100
+ @error_cache[name] = "There are no registered paths for namespace \"#{namespace}\"."
101
+
102
+ unless throw
103
+ return nil
104
+ end
105
+
106
+ raise Error::Loader, @error_cache[name]
107
+ end
108
+
109
+ paths(namespace).each do |path|
110
+ absolute = File.join(path, shortname)
111
+
112
+ unless path[0] == '/'
113
+ absolute = File.join(@root_path, absolute)
114
+ end
115
+
116
+ if File.file?(absolute)
117
+ return @cache[name] = absolute
118
+ end
119
+ end
120
+
121
+ @error_cache[name] = "Unable to find template \"#{name}\" (looked into: #{@paths.inspect})."
122
+
123
+ unless throw
124
+ return nil
125
+ end
126
+
127
+ raise Error::Loader, @error_cache[name]
128
+ end
129
+
130
+ def parse_name(name)
131
+ if name[0] == '@'
132
+ if (pos = name.index('/')).nil?
133
+ raise Error::Loader, "Malformed namespaced template name \"#{name}\" " \
134
+ '(expecting "@namespace/template_name").'
135
+ end
136
+
137
+ return [name[1...pos], name[(pos + 1)..]]
41
138
  end
139
+
140
+ [MAIN_NAMESPACE, name]
42
141
  end
43
142
  end
44
143
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class AutoEscape < Node::Base
6
+ # @param [String, FalseClass] value
7
+ # @param [Node::Base] body
8
+ # @param [Integer] lineno
9
+ def initialize(value, body, lineno)
10
+ super({ body: }, { value: }, lineno)
11
+ end
12
+
13
+ def compile(compiler)
14
+ compiler.subcompile(nodes[:body])
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,9 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'output'
4
+
3
5
  module Twig
4
6
  module Node
5
7
  class Base
6
- attr_reader :tag, :lineno
8
+ attr_reader :tag
9
+
10
+ # @return [Integer]
11
+ attr_reader :lineno
7
12
 
8
13
  # @return [Hash]
9
14
  attr_reader :attributes
@@ -11,7 +16,7 @@ module Twig
11
16
  # @return [Source]
12
17
  attr_reader :source_context
13
18
 
14
- # @return [AutoHash<Node::Base>]
19
+ # @return [AutoHash{[Symbol, Integer] => Node::Base}]
15
20
  attr_reader :nodes
16
21
 
17
22
  # @param [Hash<Node::Base>] nodes
@@ -56,6 +61,57 @@ module Twig
56
61
  def template_name
57
62
  source_context.name
58
63
  end
64
+
65
+ # @return [Integer]
66
+ def length
67
+ nodes.length
68
+ end
69
+
70
+ def empty?
71
+ nodes.empty?
72
+ end
73
+
74
+ def to_s
75
+ repr = +''
76
+ repr << self.class.name
77
+
78
+ if @tag
79
+ repr << "\n tag: #{@tag}"
80
+ end
81
+
82
+ attr = attributes.map do |name, value|
83
+ v = if value.is_a?(Proc) || value.is_a?(Method)
84
+ '\Closure'
85
+ elsif value.is_a?(String)
86
+ value
87
+ else
88
+ value.inspect
89
+ end
90
+
91
+ "#{name}: #{v}"
92
+ end
93
+
94
+ unless attr.empty?
95
+ repr << "\n attributes:\n #{attr.join("\n ")}"
96
+ end
97
+
98
+ unless empty?
99
+ repr << "\n nodes:"
100
+
101
+ nodes.each do |name, node|
102
+ len = name.to_s.length + 6
103
+ node_repr = []
104
+
105
+ node.to_s.each_line do |line|
106
+ node_repr << ((' ' * len) + line.rstrip)
107
+ end
108
+
109
+ repr << "\n #{name}: #{node_repr.join("\n").lstrip}"
110
+ end
111
+ end
112
+
113
+ repr
114
+ end
59
115
  end
60
116
  end
61
117
  end
@@ -9,8 +9,10 @@ module Twig
9
9
 
10
10
  def compile(compiler)
11
11
  compiler.
12
+ add_debug_info(self).
12
13
  write("def block_#{attributes[:name]}(context, blocks)\n").
13
14
  indent.
15
+ write("macros = @macros.dup\n").
14
16
  subcompile(nodes[:body]).
15
17
  outdent.
16
18
  write("end\n\n")
@@ -3,13 +3,17 @@
3
3
  module Twig
4
4
  module Node
5
5
  class BlockReference < Node::Base
6
+ include Output
7
+
6
8
  def initialize(name, lineno)
7
9
  super({}, { name: }, lineno)
8
10
  end
9
11
 
10
12
  def compile(compiler)
11
13
  compiler.
12
- write("yield_block(:#{attributes[:name]}, context, block_list.merge(blocks));").
14
+ add_debug_info(self).
15
+ write('context.output_buffer.safe_append = ').
16
+ raw("render_block(:#{attributes[:name]}, context, blocks)").
13
17
  raw("\n")
14
18
  end
15
19
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Body < Base; end
6
+ end
7
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Cache < Node::Base
6
+ def initialize(arguments, body, lineno)
7
+ super({ arguments:, body: }, {}, lineno)
8
+ end
9
+
10
+ def compile(compiler)
11
+ compiler.
12
+ add_debug_info(self).
13
+ # Cache method just writes strings onto the buffer, it doesn't return the fragment
14
+ # so we can capture any output to the main buffer and output that instead
15
+ write("context.output_buffer.safe_append = context.call_context.capture do\n").
16
+ indent.
17
+ write('context.call_context.cache(')
18
+
19
+ first = true
20
+ nodes[:arguments].nodes.each_value do |argument|
21
+ compiler.raw(', ') unless first
22
+ first = false
23
+
24
+ compiler.subcompile(argument)
25
+ end
26
+
27
+ compiler.
28
+ raw(") do\n").
29
+ indent.
30
+ # inside a capture block the original buffer is capturing so it's OK if we push strings there
31
+ write("context.original_buffer.safe_append = context.buffer_and_return do\n").
32
+ indent
33
+
34
+ compiler.
35
+ write("context.push_stack\n").
36
+ subcompile(nodes[:body]).
37
+ raw("\n").
38
+ write("context.pop_stack\n")
39
+
40
+ compiler.
41
+ write("end.to_s\n").
42
+ outdent.
43
+ write("end\n").
44
+ outdent.
45
+ write("end\n").
46
+ outdent
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Capture < Node::Base
6
+ # @param [Node::Base] body
7
+ # @param [Integer] lineno
8
+ def initialize(body, lineno)
9
+ super({ body: }, { raw: false }, lineno)
10
+ end
11
+
12
+ def compile(compiler)
13
+ compiler.
14
+ raw("context.buffer_and_return do\n").
15
+ indent.
16
+ subcompile(nodes[:body]).
17
+ outdent.
18
+ write("end.to_s.html_safe\n")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Deprecated < Base
6
+ def initialize(expr, lineno)
7
+ super({ expr: }, {}, lineno)
8
+ end
9
+
10
+ def compile(compiler)
11
+ compiler.add_debug_info(self)
12
+
13
+ expr = nodes[:expr]
14
+
15
+ unless expr.is_a?(Expression::Constant)
16
+ var_name = compiler.var_name
17
+ compiler.
18
+ write("#{var_name} = ").
19
+ subcompile(expr).
20
+ raw("\n")
21
+ end
22
+
23
+ compiler.write('::Twig::Extension::Core.deprecation_notice(')
24
+
25
+ if expr.is_a?(Expression::Constant)
26
+ compiler.subcompile(expr)
27
+ else
28
+ compiler.write(var_name)
29
+ end
30
+
31
+ compiler.
32
+ raw(', ').
33
+ repr(template_name).
34
+ raw(', ').
35
+ repr(lineno)
36
+
37
+ if (package = nodes.fetch(:package, nil))
38
+ compiler.
39
+ raw(', package: ').
40
+ subcompile(package)
41
+ end
42
+
43
+ if (version = nodes.fetch(:version, nil))
44
+ compiler.
45
+ raw(', version: ').
46
+ subcompile(version)
47
+ end
48
+
49
+ compiler.raw(")\n")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ class Do < Node::Base
6
+ # @param [Node::Expression::Base] expr
7
+ # @param [Integer] lineno
8
+ def initialize(expr, lineno)
9
+ super({ expr: }, {}, lineno)
10
+ end
11
+
12
+ def compile(compiler)
13
+ compiler.
14
+ subcompile(nodes[:expr], raw: false).
15
+ raw("\n")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'include'
4
+
5
+ module Twig
6
+ module Node
7
+ class Embed < Include
8
+ # we don't inject the module to avoid node visitors to traverse it twice (as it will
9
+ # be already visited in the main module)
10
+ def initialize(name, index, variables, only, ignore_missing, lineno)
11
+ super(
12
+ Expression::Constant.new('not_used', lineno),
13
+ variables,
14
+ only,
15
+ ignore_missing,
16
+ lineno
17
+ )
18
+
19
+ attributes[:name] = name
20
+ attributes[:index] = index
21
+ end
22
+
23
+ private
24
+
25
+ def add_get_template(compiler, template = '')
26
+ compiler.
27
+ raw('load(').
28
+ string(attributes[:name]).
29
+ raw(', ').
30
+ repr(lineno).
31
+ raw(', ').
32
+ string(attributes[:index]).
33
+ raw(')')
34
+
35
+ if attributes[:ignore_missing]
36
+ compiler.
37
+ raw("\n").
38
+ write("#{template}.get_parent(context)\n")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,48 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'support_defined_test'
4
+
3
5
  module Twig
4
6
  module Node
5
7
  module Expression
6
8
  class Array < Expression::Base
9
+ include Expression::SupportDefinedTest
10
+
11
+ # @param [AutoHash] elements
12
+ # @param [Integer] lineno
7
13
  def initialize(elements, lineno)
8
14
  super(elements, {}, lineno)
9
-
10
- @index = -1
11
15
  end
12
16
 
13
17
  # @param [Expression::Base] value
14
- # @param [Expression::Base|nil] key
15
- def add_element(value, key = nil)
16
- if key.nil?
17
- @index += 1
18
- key = Constant.new(@index, value.lineno)
19
- end
20
-
21
- nodes.add(key, value)
18
+ def add_element(value)
19
+ nodes.add(value)
22
20
  end
23
21
 
24
22
  def compile(compiler)
23
+ if define_test_enabled?
24
+ return compiler.repr(true)
25
+ end
26
+
25
27
  compiler.
26
- raw('{').
28
+ raw('[').
27
29
  indent
28
30
 
29
- key_value_pairs.each do |key, value|
31
+ first = true
32
+
33
+ values.each do |value|
34
+ unless first
35
+ compiler.raw(', ')
36
+ end
37
+
38
+ first = false
39
+
30
40
  compiler.
31
- subcompile(key).
32
- raw(' => ').
33
- subcompile(value).
34
- raw(', ')
41
+ subcompile(value)
35
42
  end
36
43
 
37
44
  compiler.
38
45
  outdent.
39
- raw('}')
46
+ raw(']')
40
47
  end
41
48
 
42
- private
49
+ def values
50
+ nodes.values
51
+ end
43
52
 
44
- def key_value_pairs
45
- nodes.each_value.each_slice(2)
53
+ def each_value(&)
54
+ values.each(&)
46
55
  end
47
56
  end
48
57
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ class ArrowFunction < Expression::Base
7
+ # @param [Expression::Base] expr
8
+ # @param [Node::Nodes] names
9
+ # @param [Integer] lineno
10
+ def initialize(expr, names, lineno)
11
+ if !names.is_a?(Expression::Array) && !names.is_a?(Expression::Variable::Context)
12
+ raise Error::Syntax.new(
13
+ 'The arrow function argument must be a list of variables or a single variable.',
14
+ names.lineno,
15
+ names.source_context
16
+ )
17
+ end
18
+
19
+ if names.is_a?(Expression::Variable::Context)
20
+ names = Expression::Array.new(AutoHash.new.add(
21
+ Expression::Variable::AssignContext.new(names.attributes[:name], names.lineno)
22
+ ), names.lineno)
23
+ end
24
+
25
+ super({ expr:, names: }, {}, lineno)
26
+ end
27
+
28
+ def compile(compiler)
29
+ compiler.
30
+ add_debug_info(self).
31
+ raw('-> (')
32
+
33
+ first = true
34
+ nodes[:names].each_value do |name|
35
+ compiler.raw(', ') unless first
36
+ compiler.raw("__#{name.attributes[:name]}__")
37
+ first = false
38
+ end
39
+
40
+ compiler.raw(') { ')
41
+
42
+ nodes[:names].nodes.each_value do |name|
43
+ compiler.
44
+ raw("context[:#{name.attributes[:name]}] = ").
45
+ raw("__#{name.attributes[:name]}__; ")
46
+ end
47
+
48
+ compiler.
49
+ subcompile(nodes[:expr]).
50
+ raw('}')
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -10,7 +10,7 @@ module Twig
10
10
  # @param [Integer] lineno
11
11
  def initialize(name, lineno)
12
12
  if %w[true false none null nil].include?(name.downcase)
13
- raise Error::Syntax.new("You cannot assign a value to #{name}", lineno)
13
+ raise Error::Syntax.new("You cannot assign a value to \"#{name}\".", lineno, source_context)
14
14
  end
15
15
 
16
16
  super
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'boolean'
4
+
5
+ module Twig
6
+ module Node
7
+ module Expression
8
+ module Binary
9
+ class And < Boolean
10
+ def operator(compiler)
11
+ compiler.raw('&&')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -31,6 +31,9 @@ module Twig
31
31
  end
32
32
 
33
33
  OPERATORS = {
34
+ BitwiseOr: '|',
35
+ BitwiseXor: '^',
36
+ BitwiseAnd: '&',
34
37
  Equal: '==',
35
38
  NotEqual: '!=',
36
39
  Spaceship: '<=>',
@@ -39,14 +42,13 @@ module Twig
39
42
  LessEqual: '<=',
40
43
  GreaterEqual: '>=',
41
44
 
42
- Or: '||',
43
- Xor: '^',
44
- And: '&&',
45
+ Range: '..',
45
46
  Add: '+',
46
47
  Sub: '-',
47
- Concat: '+',
48
48
  Mul: '*',
49
49
  Div: '/',
50
+ Mod: '%',
51
+ Power: '**',
50
52
  }.freeze
51
53
 
52
54
  # Lots of simple operator classes can just be generated dynamically
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twig
4
+ module Node
5
+ module Expression
6
+ module Binary
7
+ class Boolean < Binary::Base
8
+ def compile(compiler)
9
+ compiler.
10
+ raw('(::Twig::Extension::Core.bool(').
11
+ subcompile(nodes[:left]).
12
+ raw(') ')
13
+
14
+ operator(compiler)
15
+
16
+ compiler.raw(' ::Twig::Extension::Core.bool(').
17
+ subcompile(nodes[:right]).
18
+ raw('))')
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end