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.
- checksums.yaml +4 -4
- 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 +145 -6
- data/lib/twig/context.rb +0 -64
- data/lib/twig/expression_parser.rb +0 -517
- data/lib/twig/railtie.rb +0 -60
data/lib/twig/environment.rb
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
module Twig
|
|
4
4
|
class Environment
|
|
5
5
|
# @return [Cache::Base]
|
|
6
|
-
attr_reader :cache
|
|
6
|
+
attr_reader :cache, :charset
|
|
7
|
+
|
|
8
|
+
# @return [Twig::Loader::Base]
|
|
9
|
+
attr_reader :loader
|
|
7
10
|
|
|
8
11
|
# @param [::Twig::Loader::Base] loader
|
|
9
12
|
def initialize(loader, options = {})
|
|
@@ -12,87 +15,210 @@ module Twig
|
|
|
12
15
|
@options = {
|
|
13
16
|
cache: false,
|
|
14
17
|
debug: false,
|
|
18
|
+
charset: 'UTF-8',
|
|
19
|
+
strict_variables: false,
|
|
20
|
+
autoescape: :html,
|
|
15
21
|
auto_reload: nil,
|
|
22
|
+
allow_helper_methods: false,
|
|
16
23
|
}.merge(options)
|
|
17
24
|
|
|
18
|
-
@auto_reload = options[:auto_reload].nil? ? options[:debug] : options[:auto_reload]
|
|
25
|
+
@auto_reload = @options[:auto_reload].nil? ? @options[:debug] : @options[:auto_reload]
|
|
26
|
+
@charset = @options[:charset]
|
|
27
|
+
@strict_variables = @options[:strict_variables]
|
|
19
28
|
|
|
20
29
|
self.cache = @options[:cache]
|
|
21
30
|
|
|
31
|
+
@loaded_templates = {}
|
|
32
|
+
@globals = {}
|
|
33
|
+
@runtimes = {}
|
|
34
|
+
@runtime_loaders = []
|
|
35
|
+
@default_runtime_loader = RuntimeLoader::Factory.new({
|
|
36
|
+
Runtime::Escaper => -> { Runtime::Escaper.new(@charset) },
|
|
37
|
+
})
|
|
38
|
+
|
|
22
39
|
add_extension(Extension::Core.new)
|
|
40
|
+
add_extension(Extension::Escaper.new(@options[:autoescape]))
|
|
23
41
|
end
|
|
24
42
|
|
|
25
|
-
def template_class(name)
|
|
43
|
+
def template_class(name, index = nil)
|
|
26
44
|
key = loader.get_cache_key(name)
|
|
27
45
|
|
|
28
|
-
"Compiled::Template_#{::Digest::SHA256.hexdigest(key)}"
|
|
46
|
+
"Compiled::Template_#{::Digest::SHA256.hexdigest(key)}#{"___#{index}" if index}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param [String, Twig::TemplateWrapper] name
|
|
50
|
+
# @return [Twig::TemplateWrapper]
|
|
51
|
+
def load(name)
|
|
52
|
+
if name.is_a?(Twig::TemplateWrapper)
|
|
53
|
+
return name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
cls = template_class(name)
|
|
57
|
+
TemplateWrapper.new(
|
|
58
|
+
self,
|
|
59
|
+
load_template(cls, name)
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param [String, Twig::TemplateWrapper, Array<String>] name
|
|
64
|
+
def resolve_template(names)
|
|
65
|
+
unless names.is_a?(Array)
|
|
66
|
+
return load(names)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
count = names.length
|
|
70
|
+
|
|
71
|
+
names.each do |name|
|
|
72
|
+
if name.is_a?(Twig::TemplateWrapper)
|
|
73
|
+
return name
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
unless count == 1 || loader.exists?(name)
|
|
77
|
+
next
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return load(name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
raise Error::Loader, "Unable to find one of the following templates: \"#{names.join('", "')}\"."
|
|
29
84
|
end
|
|
30
85
|
|
|
86
|
+
# @param [String] cls The class name to load
|
|
87
|
+
# @param [String] name The template name
|
|
88
|
+
# @param [Integer, nil] index Index for embedded templates
|
|
31
89
|
# @return [Twig::Template]
|
|
32
|
-
def load_template(name,
|
|
33
|
-
|
|
34
|
-
cache_key = cache.generate_key(name, class_name)
|
|
90
|
+
def load_template(cls, name, index: nil)
|
|
91
|
+
main_cls = cls
|
|
35
92
|
|
|
36
|
-
|
|
93
|
+
if index
|
|
94
|
+
cls += "___#{index}"
|
|
95
|
+
end
|
|
37
96
|
|
|
38
|
-
if
|
|
39
|
-
@
|
|
97
|
+
if !index.nil? && Twig.const_defined?(cls)
|
|
98
|
+
return @loaded_templates[cls] = Twig.const_get(cls).new(self)
|
|
40
99
|
end
|
|
41
100
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
101
|
+
cache_key = cache.generate_key(name, main_cls)
|
|
102
|
+
|
|
103
|
+
# If not auto_reloading just use whatever is in the cache
|
|
104
|
+
if !@auto_reload || template_fresh?(name, cache.timestamp(cache_key))
|
|
105
|
+
if @loaded_templates.key?(cls)
|
|
106
|
+
return @loaded_templates[cls]
|
|
107
|
+
end
|
|
45
108
|
|
|
46
|
-
# File cache loader won't rely on eval
|
|
47
|
-
@cache.write(cache_key, code)
|
|
48
109
|
@cache.load(cache_key)
|
|
49
110
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
111
|
+
if Twig.const_defined?(cls)
|
|
112
|
+
return @loaded_templates[cls] = Twig.const_get(cls).new(self)
|
|
113
|
+
end
|
|
53
114
|
end
|
|
54
115
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
116
|
+
# No caches have fresh template at this point, so write it now
|
|
117
|
+
code = render_ruby(name)
|
|
58
118
|
|
|
59
|
-
|
|
60
|
-
|
|
119
|
+
# File cache loader won't rely on eval
|
|
120
|
+
@cache.write(cache_key, code)
|
|
121
|
+
@cache.load(cache_key)
|
|
122
|
+
|
|
123
|
+
# Finally just eval the generated code if cache doesn't
|
|
124
|
+
# create the class
|
|
125
|
+
Twig.module_eval(code) unless Twig.const_defined?(cls)
|
|
126
|
+
|
|
127
|
+
@loaded_templates[cls] = Twig.const_get(cls).new(self)
|
|
61
128
|
end
|
|
62
129
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
130
|
+
# @return [Twig::Template]
|
|
131
|
+
def create_template(template, name = nil)
|
|
132
|
+
hash = ::Digest::SHA256.hexdigest(template)
|
|
133
|
+
name = if name.nil?
|
|
134
|
+
"__string_template__#{hash}"
|
|
135
|
+
else
|
|
136
|
+
"#{name} (string template #{hash})"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
chain_loader = Loader::Chain.new([
|
|
140
|
+
Loader::Array.new({ name => template }),
|
|
141
|
+
current = loader,
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
@loader = chain_loader
|
|
145
|
+
|
|
146
|
+
cls = template_class(name)
|
|
147
|
+
TemplateWrapper.new(self, load_template(cls, name))
|
|
148
|
+
ensure
|
|
149
|
+
@loader = current
|
|
67
150
|
end
|
|
68
151
|
|
|
69
152
|
def extension(name)
|
|
70
|
-
|
|
153
|
+
extension_set.get(name)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def extension?(name)
|
|
157
|
+
extension_set.has?(name)
|
|
71
158
|
end
|
|
72
159
|
|
|
73
160
|
# @param [Extension::Base] extension
|
|
74
161
|
def add_extension(extension)
|
|
75
|
-
|
|
162
|
+
extension_set.add(extension)
|
|
76
163
|
end
|
|
77
164
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
165
|
+
def runtime(klass)
|
|
166
|
+
return runtimes[klass] if runtimes.key?(klass)
|
|
167
|
+
|
|
168
|
+
runtime_loaders.each do |loader|
|
|
169
|
+
if (runtime = loader.load(klass))
|
|
170
|
+
return runtimes[klass] = runtime
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
if (runtime = default_runtime_loader.load(klass))
|
|
175
|
+
return runtimes[klass] = runtime
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
raise Error::Runtime, "Unable to load the \"#{klass}\" runtime."
|
|
81
179
|
end
|
|
82
180
|
|
|
83
|
-
# @
|
|
181
|
+
# @param [RuntimeLoader::Base] loader
|
|
182
|
+
def add_runtime_loader(loader)
|
|
183
|
+
runtime_loaders << loader
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# @return [ExpressionParser::ExpressionParsers]
|
|
187
|
+
def expression_parsers
|
|
188
|
+
extension_set.expression_parsers
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @return [TwigFilter, nil]
|
|
84
192
|
def filter(name)
|
|
85
|
-
|
|
193
|
+
extension_set.filter(name)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# @return [TwigFunction, nil]
|
|
197
|
+
def function(name)
|
|
198
|
+
extension_set.function(name)
|
|
86
199
|
end
|
|
87
200
|
|
|
88
|
-
# @return [
|
|
201
|
+
# @return [TwigTest, nil]
|
|
202
|
+
def test(name)
|
|
203
|
+
extension_set.test(name)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @return [TokenParser::Base, nil]
|
|
89
207
|
def token_parser(name)
|
|
90
|
-
|
|
208
|
+
extension_set.token_parser(name)
|
|
91
209
|
end
|
|
92
210
|
|
|
93
|
-
# @return [
|
|
94
|
-
def
|
|
95
|
-
|
|
211
|
+
# @return [Array<NodeVisitor::Base>]
|
|
212
|
+
def node_visitors
|
|
213
|
+
extension_set.node_visitors
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def add_global(name, value)
|
|
217
|
+
@globals[name] = value
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def globals
|
|
221
|
+
@resolved_globals ||= extension_set.globals.merge(@globals)
|
|
96
222
|
end
|
|
97
223
|
|
|
98
224
|
# @param [Source] source
|
|
@@ -114,6 +240,9 @@ module Twig
|
|
|
114
240
|
# @param [Source] source
|
|
115
241
|
def compile_source(source)
|
|
116
242
|
compile(parse(tokenize(source)))
|
|
243
|
+
rescue Error::Base => e
|
|
244
|
+
e.source_context = source
|
|
245
|
+
raise e
|
|
117
246
|
end
|
|
118
247
|
|
|
119
248
|
# @return [String]
|
|
@@ -141,10 +270,38 @@ module Twig
|
|
|
141
270
|
end
|
|
142
271
|
end
|
|
143
272
|
|
|
273
|
+
# @return [Boolean]
|
|
274
|
+
def allow_helper_methods?
|
|
275
|
+
@options[:allow_helper_methods]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def debug?
|
|
279
|
+
@options[:debug]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def strict_variables?
|
|
283
|
+
@strict_variables
|
|
284
|
+
end
|
|
285
|
+
|
|
144
286
|
private
|
|
145
287
|
|
|
146
|
-
# @return [
|
|
147
|
-
attr_reader :
|
|
288
|
+
# @return [ExtensionSet]
|
|
289
|
+
attr_reader :extension_set
|
|
290
|
+
|
|
291
|
+
# @return [RuntimeLoader::Base]
|
|
292
|
+
attr_reader :default_runtime_loader
|
|
293
|
+
|
|
294
|
+
# @return [Array<RuntimeLoader::Base>]
|
|
295
|
+
attr_reader :runtime_loaders
|
|
296
|
+
|
|
297
|
+
# @return [Hash{String => Object}]
|
|
298
|
+
attr_reader :runtimes
|
|
299
|
+
|
|
300
|
+
def render_ruby(name)
|
|
301
|
+
compile_source(
|
|
302
|
+
loader.get_source_context(name)
|
|
303
|
+
)
|
|
304
|
+
end
|
|
148
305
|
|
|
149
306
|
def lexer
|
|
150
307
|
@lexer ||= Lexer.new(self)
|
data/lib/twig/error/base.rb
CHANGED
|
@@ -3,34 +3,99 @@
|
|
|
3
3
|
module Twig
|
|
4
4
|
module Error
|
|
5
5
|
class Base < StandardError
|
|
6
|
+
# @return [Integer]
|
|
6
7
|
attr_reader :lineno
|
|
7
8
|
|
|
8
9
|
# @param [String] message
|
|
9
10
|
# @param [Integer] lineno
|
|
10
11
|
# @param [Source] source
|
|
11
|
-
def initialize(message, lineno = -1, source = nil)
|
|
12
|
-
super(
|
|
13
|
-
|
|
14
|
-
if source
|
|
15
|
-
name = source.name
|
|
16
|
-
@source_code = source.code
|
|
17
|
-
@source_path = source.path
|
|
18
|
-
else
|
|
19
|
-
name = nil
|
|
20
|
-
end
|
|
12
|
+
def initialize(message, lineno = -1, source = nil, previous = nil)
|
|
13
|
+
super('')
|
|
21
14
|
|
|
22
15
|
@lineno = lineno
|
|
23
|
-
@
|
|
16
|
+
@source = source
|
|
24
17
|
@raw_message = message
|
|
18
|
+
@previous = previous
|
|
19
|
+
|
|
20
|
+
update_repr
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Source, nil]
|
|
24
|
+
def source_context
|
|
25
|
+
@source
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param [Source, nil] source
|
|
29
|
+
def source_context=(source)
|
|
30
|
+
@source = source
|
|
31
|
+
update_repr
|
|
25
32
|
end
|
|
26
33
|
|
|
27
34
|
def to_s
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
@message
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def lineno=(lineno)
|
|
39
|
+
@lineno = lineno
|
|
40
|
+
update_repr
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def append_message(raw_message)
|
|
44
|
+
@raw_message += raw_message
|
|
45
|
+
update_repr
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def guess
|
|
49
|
+
locations = [self, @previous].compact.map(&:backtrace_locations).flatten
|
|
50
|
+
locations.each do |location|
|
|
51
|
+
next if location.nil?
|
|
52
|
+
next unless location.label&.start_with?('Twig::Compiled::')
|
|
53
|
+
|
|
54
|
+
klass, _method = location.label.split('#')
|
|
55
|
+
klass = Twig::Compiled.const_get(klass)
|
|
56
|
+
|
|
57
|
+
next unless source.name == klass.source_context.name
|
|
58
|
+
|
|
59
|
+
klass.debug_info.each do |source_line, template_line|
|
|
60
|
+
next unless source_line <= location.lineno
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
self.lineno = template_line
|
|
63
|
+
update_repr
|
|
64
|
+
return # rubocop:disable Lint/NonLocalExitFromIterator
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# @return [Source, nil]
|
|
72
|
+
attr_reader :source
|
|
73
|
+
|
|
74
|
+
def update_repr
|
|
75
|
+
if !source.nil? && source.path
|
|
76
|
+
# only update file and line together
|
|
77
|
+
@file = source.path
|
|
78
|
+
@line = lineno.positive? ? lineno : -1
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@message = @raw_message.dup
|
|
82
|
+
last = @message[-1]
|
|
83
|
+
|
|
84
|
+
if (punctuation = %w[. ?].include?(last) ? last : '')
|
|
85
|
+
@message = @message[0...-1]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if !source.nil? && source.name
|
|
89
|
+
@message += " in \"#{source.name}\""
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if lineno.positive?
|
|
93
|
+
@message += " at line #{lineno}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unless punctuation.empty?
|
|
97
|
+
@message += punctuation
|
|
98
|
+
end
|
|
34
99
|
end
|
|
35
100
|
end
|
|
36
101
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'infix/parses_arguments'
|
|
4
|
+
|
|
5
|
+
module Twig
|
|
6
|
+
module ExpressionParser
|
|
7
|
+
class Base
|
|
8
|
+
def to_s
|
|
9
|
+
"Some kinda EP: #{name}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def precedence
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Symbol]
|
|
21
|
+
def type
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def aliases
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module ExpressionParser
|
|
5
|
+
class ExpressionParsers
|
|
6
|
+
# @param [Array<Twig::ExpressionParser::Base>] parsers
|
|
7
|
+
def initialize(parsers = [])
|
|
8
|
+
@parsers_by_name = {}
|
|
9
|
+
@parsers_by_class = {}
|
|
10
|
+
|
|
11
|
+
add(parsers)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param [Array<Twig::ExpressionParser::Base>] parsers
|
|
15
|
+
def add(parsers)
|
|
16
|
+
parsers.each do |parser|
|
|
17
|
+
if parser.precedence > 512 || parser.precedence.negative?
|
|
18
|
+
raise(
|
|
19
|
+
ArgumentError,
|
|
20
|
+
"Precedence for \"#{parser.name}\" must be between 0 and 512, got #{parser.precedence}."
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
parsers_by_name[parser.type] ||= {}
|
|
25
|
+
parsers_by_name[parser.type][parser.name] = parser
|
|
26
|
+
parsers_by_class[parser.class.name] = parser
|
|
27
|
+
|
|
28
|
+
parser.aliases.each do |alias_name|
|
|
29
|
+
parsers_by_name[parser.type][alias_name] = parser
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def each(&)
|
|
35
|
+
parsers_by_name.values.map(&:values).flatten.each(&)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [ExpressionParser::Base, nil]
|
|
39
|
+
def by_class(klass)
|
|
40
|
+
parsers_by_class[klass]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [ExpressionParser::Base, nil]
|
|
44
|
+
def by_name(type, name)
|
|
45
|
+
parsers_by_name[type][name]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# @return [Hash<Array<Twig::ExpressionParser::Base>>]]
|
|
51
|
+
attr_reader :parsers_by_name
|
|
52
|
+
|
|
53
|
+
# @return [Hash<Twig::ExpressionParser::Base>]
|
|
54
|
+
attr_reader :parsers_by_class
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module ExpressionParser
|
|
5
|
+
module Infix
|
|
6
|
+
class Arrow < InfixExpressionParser
|
|
7
|
+
def parse(parser, left, token)
|
|
8
|
+
# As the expression of the arrow function is independent from the current precedence,
|
|
9
|
+
# we want a precedence of 0
|
|
10
|
+
Node::Expression::ArrowFunction.new(parser.parse_expression, left, token.lineno)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def name
|
|
14
|
+
'=>'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def description
|
|
18
|
+
'Arrow function (x => expr)'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def precedence
|
|
22
|
+
250
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def associativity
|
|
26
|
+
LEFT
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module ExpressionParser
|
|
5
|
+
module Infix
|
|
6
|
+
class Binary < InfixExpressionParser
|
|
7
|
+
attr_reader :associativity, :precedence, :aliases, :name
|
|
8
|
+
|
|
9
|
+
def initialize(node_class, name, precedence, associativity = LEFT, description: nil, aliases: [])
|
|
10
|
+
super()
|
|
11
|
+
|
|
12
|
+
@node_class = node_class
|
|
13
|
+
@name = name
|
|
14
|
+
@precedence = precedence
|
|
15
|
+
@associativity = associativity
|
|
16
|
+
@description = description
|
|
17
|
+
@aliases = aliases
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse(parser, left, token)
|
|
21
|
+
right = parser.parse_expression(
|
|
22
|
+
left? ? precedence + 1 : precedence
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@node_class.new(left, right, token.lineno)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def description
|
|
29
|
+
@description || ''
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twig
|
|
4
|
+
module ExpressionParser
|
|
5
|
+
module Infix
|
|
6
|
+
class ConditionalTernary < InfixExpressionParser
|
|
7
|
+
def parse(parser, left, token)
|
|
8
|
+
then_expr = parser.parse_expression(precedence)
|
|
9
|
+
|
|
10
|
+
else_expr = if parser.stream.next_if(Token::PUNCTUATION_TYPE, ':')
|
|
11
|
+
# Ternary operator (expr ? expr2 : expr3)
|
|
12
|
+
parser.parse_expression(precedence)
|
|
13
|
+
else
|
|
14
|
+
# Ternary without else (expr ? expr2)
|
|
15
|
+
Node::Expression::Constant.new('', token.lineno)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Node::Expression::Ternary.new(left, then_expr, else_expr, token.lineno)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def name
|
|
22
|
+
'?'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def description
|
|
26
|
+
'Conditional operator (a ? b : c)'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def precedence
|
|
30
|
+
0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def associativity
|
|
34
|
+
LEFT
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|