luoma 0.1.0
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 +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.rdoc_options +16 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +69 -0
- data/Steepfile +41 -0
- data/certs/jgrp.pem +27 -0
- data/docs/configuration.md +180 -0
- data/docs/custom_filters.md +3 -0
- data/docs/custom_tags.md +3 -0
- data/docs/expressions.md +3 -0
- data/docs/extension_types.md +41 -0
- data/docs/filter_reference.md +1702 -0
- data/docs/index.md +73 -0
- data/docs/luoma_for_template_authors.md +3 -0
- data/docs/markdown.md +111 -0
- data/docs/predicate_reference.md +3 -0
- data/docs/static_analysis.md +3 -0
- data/docs/tag_reference.md +352 -0
- data/docs/template_loaders.md +177 -0
- data/docs/undefined_variables.md +3 -0
- data/docs-requirements.txt +11 -0
- data/docs_/cycle.md +17 -0
- data/docs_/font_rendering_example.md +157 -0
- data/docs_/header_block_example.md +51 -0
- data/docs_/increment_and_decrement.md +49 -0
- data/docs_/logo_style_example.md +40 -0
- data/docs_/migration.md +11 -0
- data/docs_/notes.md +19 -0
- data/lib/luoma/cache.rb +80 -0
- data/lib/luoma/chain_hash.rb +54 -0
- data/lib/luoma/context.rb +227 -0
- data/lib/luoma/drop.rb +84 -0
- data/lib/luoma/drops/block.rb +33 -0
- data/lib/luoma/drops/expression.rb +29 -0
- data/lib/luoma/drops/html_safe.rb +36 -0
- data/lib/luoma/drops/range.rb +69 -0
- data/lib/luoma/drops/undefined.rb +119 -0
- data/lib/luoma/environment.rb +479 -0
- data/lib/luoma/errors.rb +79 -0
- data/lib/luoma/escape.rb +35 -0
- data/lib/luoma/expression.rb +1271 -0
- data/lib/luoma/filter.rb +97 -0
- data/lib/luoma/filters/array.rb +372 -0
- data/lib/luoma/filters/date.rb +19 -0
- data/lib/luoma/filters/default.rb +16 -0
- data/lib/luoma/filters/json.rb +14 -0
- data/lib/luoma/filters/math.rb +84 -0
- data/lib/luoma/filters/size.rb +13 -0
- data/lib/luoma/filters/slice.rb +52 -0
- data/lib/luoma/filters/sort.rb +113 -0
- data/lib/luoma/filters/string.rb +186 -0
- data/lib/luoma/fnv.rb +15 -0
- data/lib/luoma/lexer.rb +106 -0
- data/lib/luoma/lexer_legacy.rb +396 -0
- data/lib/luoma/lexer_unified.rb +279 -0
- data/lib/luoma/loader.rb +50 -0
- data/lib/luoma/loaders/choice_loader.rb +20 -0
- data/lib/luoma/loaders/file_system_loader.rb +75 -0
- data/lib/luoma/loaders/mixins.rb +52 -0
- data/lib/luoma/markup.rb +109 -0
- data/lib/luoma/parser.rb +252 -0
- data/lib/luoma/parser_unified.rb +979 -0
- data/lib/luoma/predicates/blank.rb +19 -0
- data/lib/luoma/predicates/defined.rb +10 -0
- data/lib/luoma/predicates/empty.rb +15 -0
- data/lib/luoma/predicates/type.rb +35 -0
- data/lib/luoma/static_analysis.rb +427 -0
- data/lib/luoma/tags/assign.rb +63 -0
- data/lib/luoma/tags/break.rb +23 -0
- data/lib/luoma/tags/capture.rb +43 -0
- data/lib/luoma/tags/case.rb +137 -0
- data/lib/luoma/tags/comment.rb +17 -0
- data/lib/luoma/tags/continue.rb +23 -0
- data/lib/luoma/tags/define.rb +42 -0
- data/lib/luoma/tags/else.rb +30 -0
- data/lib/luoma/tags/for.rb +108 -0
- data/lib/luoma/tags/if.rb +109 -0
- data/lib/luoma/tags/import.rb +91 -0
- data/lib/luoma/tags/include.rb +77 -0
- data/lib/luoma/tags/output.rb +20 -0
- data/lib/luoma/tags/raw.rb +26 -0
- data/lib/luoma/tags/render.rb +92 -0
- data/lib/luoma/tags/with.rb +55 -0
- data/lib/luoma/template.rb +178 -0
- data/lib/luoma/token.rb +84 -0
- data/lib/luoma/version.rb +5 -0
- data/lib/luoma.rb +75 -0
- data/sig/luoma/cache.rbs +44 -0
- data/sig/luoma/chain_hash.rbs +26 -0
- data/sig/luoma/context.rbs +115 -0
- data/sig/luoma/drop.rbs +44 -0
- data/sig/luoma/drops/block.rbs +10 -0
- data/sig/luoma/drops/expression.rbs +10 -0
- data/sig/luoma/drops/html_safe.rbs +14 -0
- data/sig/luoma/drops/range.rbs +15 -0
- data/sig/luoma/drops/undefined.rbs +27 -0
- data/sig/luoma/environment.rbs +212 -0
- data/sig/luoma/errors.rbs +67 -0
- data/sig/luoma/escape.rbs +15 -0
- data/sig/luoma/expression.rbs +514 -0
- data/sig/luoma/filter.rbs +66 -0
- data/sig/luoma/filters/array.rbs +74 -0
- data/sig/luoma/filters/date.rbs +9 -0
- data/sig/luoma/filters/default.rbs +8 -0
- data/sig/luoma/filters/json.rbs +7 -0
- data/sig/luoma/filters/math.rbs +37 -0
- data/sig/luoma/filters/size.rbs +6 -0
- data/sig/luoma/filters/slice.rbs +10 -0
- data/sig/luoma/filters/sort.rbs +17 -0
- data/sig/luoma/filters/string.rbs +103 -0
- data/sig/luoma/fnv.rbs +3 -0
- data/sig/luoma/lexer.rbs +42 -0
- data/sig/luoma/lexer_legacy.rbs +81 -0
- data/sig/luoma/lexer_unified.rbs +49 -0
- data/sig/luoma/loader.rbs +40 -0
- data/sig/luoma/loaders/choice_loader.rbs +9 -0
- data/sig/luoma/loaders/file_system_loader.rbs +19 -0
- data/sig/luoma/loaders/mixins.rbs +16 -0
- data/sig/luoma/markup.rbs +68 -0
- data/sig/luoma/parser.rbs +105 -0
- data/sig/luoma/parser_unified.rbs +150 -0
- data/sig/luoma/predicates/blank.rbs +6 -0
- data/sig/luoma/predicates/defined.rbs +6 -0
- data/sig/luoma/predicates/empty.rbs +6 -0
- data/sig/luoma/predicates/type.rbs +21 -0
- data/sig/luoma/static_analysis.rbs +141 -0
- data/sig/luoma/tags/assign.rbs +12 -0
- data/sig/luoma/tags/break.rbs +11 -0
- data/sig/luoma/tags/capture.rbs +14 -0
- data/sig/luoma/tags/case.rbs +36 -0
- data/sig/luoma/tags/comment.rbs +11 -0
- data/sig/luoma/tags/continue.rbs +11 -0
- data/sig/luoma/tags/define.rbs +16 -0
- data/sig/luoma/tags/else.rbs +17 -0
- data/sig/luoma/tags/for.rbs +26 -0
- data/sig/luoma/tags/if.rbs +45 -0
- data/sig/luoma/tags/import.rbs +13 -0
- data/sig/luoma/tags/include.rbs +13 -0
- data/sig/luoma/tags/output.rbs +13 -0
- data/sig/luoma/tags/raw.rbs +11 -0
- data/sig/luoma/tags/render.rbs +13 -0
- data/sig/luoma/tags/with.rbs +15 -0
- data/sig/luoma/template.rbs +117 -0
- data/sig/luoma/token.rbs +81 -0
- data/sig/luoma.rbs +11 -0
- data/zensical.toml +363 -0
- data.tar.gz.sig +0 -0
- metadata +233 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,1271 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rubocop:disable Naming/PredicateMethod
|
|
4
|
+
|
|
5
|
+
module Luoma
|
|
6
|
+
# The base class for all expressions.
|
|
7
|
+
class Expression
|
|
8
|
+
attr_reader :token, :span
|
|
9
|
+
|
|
10
|
+
#: (t_token) -> void
|
|
11
|
+
def initialize(token)
|
|
12
|
+
@token = token
|
|
13
|
+
@span = token
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#: (RenderContext) -> untyped
|
|
17
|
+
def evaluate(context)
|
|
18
|
+
raise "not implemented"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def children
|
|
22
|
+
raise "not implemented"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: () -> Array[Name]
|
|
26
|
+
def scope
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: () -> String
|
|
31
|
+
def to_s
|
|
32
|
+
raise "not implemented"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class GroupExpression < Expression
|
|
37
|
+
#: (t_token, Expression, Array[t_path_segment]) -> void
|
|
38
|
+
def initialize(token, expr, segments)
|
|
39
|
+
super(token)
|
|
40
|
+
@expr = expr
|
|
41
|
+
@segments = segments
|
|
42
|
+
|
|
43
|
+
@span = if segments.empty?
|
|
44
|
+
Luoma.span(token, expr.span)
|
|
45
|
+
else
|
|
46
|
+
Luoma.span(token, segments.last.token)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: (RenderContext) -> untyped
|
|
51
|
+
def evaluate(context)
|
|
52
|
+
obj = @expr.evaluate(context)
|
|
53
|
+
|
|
54
|
+
return obj if @segments.empty?
|
|
55
|
+
|
|
56
|
+
obj, index = context.resolve_path(obj, @segments.map { |s| s.evaluate(context) })
|
|
57
|
+
|
|
58
|
+
if obj == :nothing
|
|
59
|
+
context.env.undefined.new(
|
|
60
|
+
path(@segments[0..index] || raise),
|
|
61
|
+
@span,
|
|
62
|
+
context.template.source,
|
|
63
|
+
context.template.name
|
|
64
|
+
)
|
|
65
|
+
else
|
|
66
|
+
obj
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def children
|
|
71
|
+
[@expr, *@segments.grep(Variable)] #: Array[_Traversable]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def with(token)
|
|
75
|
+
@span = Luoma.span(@token, token)
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#: () -> String
|
|
80
|
+
def to_s
|
|
81
|
+
"(#{@expr})#{path(@segments)}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
#: (Array[t_path_segment]) -> String
|
|
87
|
+
def path(segments)
|
|
88
|
+
if segments.empty?
|
|
89
|
+
""
|
|
90
|
+
else
|
|
91
|
+
segments.map { |s| s.is_a?(Name) ? ".#{s}" : "[#{s}]" }.join
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class TernaryExpression < Expression
|
|
97
|
+
#: (t_token, Expression, Expression, Expression?) -> void
|
|
98
|
+
def initialize(token, consequence, condition, alternative)
|
|
99
|
+
super(token)
|
|
100
|
+
@consequence = consequence
|
|
101
|
+
@condition = condition
|
|
102
|
+
@alternative = alternative
|
|
103
|
+
@span = Luoma.span(@consequence.token, (@alternative || @condition).span)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#: (RenderContext) -> untyped
|
|
107
|
+
def evaluate(context)
|
|
108
|
+
if context.env.truthy?(@condition.evaluate(context), context)
|
|
109
|
+
@consequence.evaluate(context)
|
|
110
|
+
else
|
|
111
|
+
@alternative&.evaluate(context) || :nothing
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def children
|
|
116
|
+
[@consequence, @condition, @alternative].compact
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#: () -> String
|
|
120
|
+
def to_s
|
|
121
|
+
if @alternative
|
|
122
|
+
"#{@consequence} if #{@condition} else #{@alternative}"
|
|
123
|
+
else
|
|
124
|
+
"#{@consequence} if #{@condition}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class FilteredExpression < Expression
|
|
130
|
+
#: (t_token, Expression, Filter) -> void
|
|
131
|
+
def initialize(token, left, filter)
|
|
132
|
+
super(token)
|
|
133
|
+
@left = left
|
|
134
|
+
@filter = filter
|
|
135
|
+
@span = filter.span
|
|
136
|
+
|
|
137
|
+
# Filter names are stored as instances of `Variable` so we can "call"
|
|
138
|
+
# namespaced lambda expressions from the `{% import %}` tag.
|
|
139
|
+
#
|
|
140
|
+
# Here we extract an identifier from that variable for use with
|
|
141
|
+
# environment-defined filters that are never namespaced.
|
|
142
|
+
@name = filter.name.root.value if filter.name.segments.empty? && filter.name.root.is_a?(Name)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
#: (RenderContext) -> untyped
|
|
146
|
+
def evaluate(context)
|
|
147
|
+
obj = @filter.name.evaluate(context)
|
|
148
|
+
|
|
149
|
+
if obj.is_a?(ExpressionDrop)
|
|
150
|
+
# User-defined filter.
|
|
151
|
+
evaluate_lambda(obj.expr, context)
|
|
152
|
+
elsif @name
|
|
153
|
+
evaluate_filter(@name, context) # steep:ignore
|
|
154
|
+
elsif context.env.strict
|
|
155
|
+
raise FilterArgumentError.new(
|
|
156
|
+
"unknown filter",
|
|
157
|
+
@token,
|
|
158
|
+
context.template.source,
|
|
159
|
+
context.template.name
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def children
|
|
165
|
+
[@left, @filter]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#: () -> String
|
|
169
|
+
def to_s
|
|
170
|
+
"#{@left} | #{@filter}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
#: (LambdaExpr, RenderContext) -> untyped
|
|
176
|
+
def evaluate_lambda(expr, context)
|
|
177
|
+
if context.env.strict && !@filter.kwargs.empty?
|
|
178
|
+
raise FilterArgumentError.new(
|
|
179
|
+
"unexpected keyword arguments",
|
|
180
|
+
@span,
|
|
181
|
+
context.template.source,
|
|
182
|
+
context.template.name
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
expr.call([@left.evaluate(context), *@filter.args.map { |arg| arg.evaluate(context) }])
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
#: (RenderContext) -> untyped
|
|
190
|
+
def evaluate_filter(name, context)
|
|
191
|
+
func = context.env.filters[name]
|
|
192
|
+
|
|
193
|
+
if func.nil?
|
|
194
|
+
if context.env.strict
|
|
195
|
+
raise FilterNotFoundError.new(
|
|
196
|
+
"unknown filter #{name.inspect}",
|
|
197
|
+
@filter.token,
|
|
198
|
+
context.template.source,
|
|
199
|
+
context.template.name
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
return :nothing
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
filter_context = FilterContext.new(@span, context)
|
|
207
|
+
left = @left.evaluate(context)
|
|
208
|
+
|
|
209
|
+
# This will throw a FilterArgumentError if needed when `strict` is `true`.
|
|
210
|
+
args, kwargs = normalize_arguments(
|
|
211
|
+
context,
|
|
212
|
+
func,
|
|
213
|
+
@filter.args.map { |arg| arg.evaluate(context) },
|
|
214
|
+
@filter.kwargs.to_h { |arg| [arg.name.value.to_sym, arg.expression.evaluate(context)] }
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if args.empty? && kwargs.empty?
|
|
218
|
+
func.call(filter_context, left)
|
|
219
|
+
elsif kwargs.empty?
|
|
220
|
+
func.call(filter_context, left, *args)
|
|
221
|
+
else
|
|
222
|
+
func.call(
|
|
223
|
+
filter_context,
|
|
224
|
+
left,
|
|
225
|
+
*args,
|
|
226
|
+
**kwargs
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def normalize_arguments(context, method, args, kwargs)
|
|
232
|
+
params = method.parameters
|
|
233
|
+
|
|
234
|
+
# The first two required arguments are always `context` and `left`, neither
|
|
235
|
+
# of which are included in `args`.
|
|
236
|
+
required_positional = params.count { |type, _name| type == :req } - 2
|
|
237
|
+
optional_positional = params.count { |type, _name| type == :opt }
|
|
238
|
+
has_rest = params.any? { |type, _name| type == :rest }
|
|
239
|
+
|
|
240
|
+
required_keys = params.select { |type, _name| type == :keyreq }.map(&:last) # rubocop:disable Style/HashSlice
|
|
241
|
+
optional_keys = params.select { |type, _name| type == :key }.map(&:last) # rubocop:disable Style/HashSlice
|
|
242
|
+
has_keyrest = params.any? { |type, _name| type == :keyrest }
|
|
243
|
+
|
|
244
|
+
if context.env.strict
|
|
245
|
+
validate_arguments(
|
|
246
|
+
context,
|
|
247
|
+
method,
|
|
248
|
+
args,
|
|
249
|
+
kwargs,
|
|
250
|
+
required_positional: required_positional,
|
|
251
|
+
optional_positional: optional_positional,
|
|
252
|
+
has_rest: has_rest,
|
|
253
|
+
required_keys: required_keys,
|
|
254
|
+
optional_keys: optional_keys,
|
|
255
|
+
has_keyrest: has_keyrest
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
unless has_rest
|
|
260
|
+
max = required_positional + optional_positional
|
|
261
|
+
args = args.take(max)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
args.fill(:nothing, args.length...required_positional)
|
|
265
|
+
|
|
266
|
+
unless has_keyrest
|
|
267
|
+
allowed_keys = required_keys + optional_keys
|
|
268
|
+
kwargs.select! { |key, _value| allowed_keys.include?(key) }
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
required_keys.each do |key|
|
|
272
|
+
kwargs[key] = :nothing unless kwargs.key?(key)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
[args, kwargs]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def validate_arguments(
|
|
279
|
+
context, method, args, kwargs,
|
|
280
|
+
required_positional:,
|
|
281
|
+
optional_positional:,
|
|
282
|
+
has_rest:,
|
|
283
|
+
required_keys:,
|
|
284
|
+
optional_keys:,
|
|
285
|
+
has_keyrest:
|
|
286
|
+
)
|
|
287
|
+
if args.length < required_positional
|
|
288
|
+
message = [
|
|
289
|
+
"wrong number of arguments (given #{args.length}, ",
|
|
290
|
+
"expected #{required_positional}) for #{@name.inspect}"
|
|
291
|
+
]
|
|
292
|
+
raise FilterArgumentError.new(
|
|
293
|
+
message.join,
|
|
294
|
+
@span,
|
|
295
|
+
context.template.source,
|
|
296
|
+
context.template.name
|
|
297
|
+
)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
unless has_rest
|
|
301
|
+
max_positional = required_positional + optional_positional
|
|
302
|
+
if args.length > max_positional
|
|
303
|
+
message = [
|
|
304
|
+
"wrong number of arguments (given #{args.length}, ",
|
|
305
|
+
"expected #{required_positional}..#{max_positional}) for #{@name.inspect}"
|
|
306
|
+
]
|
|
307
|
+
raise FilterArgumentError.new(
|
|
308
|
+
message.join,
|
|
309
|
+
@span,
|
|
310
|
+
context.template.source,
|
|
311
|
+
context.template.name
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
unless has_keyrest
|
|
317
|
+
allowed_keys = required_keys + optional_keys
|
|
318
|
+
unknown_keys = kwargs.keys - allowed_keys
|
|
319
|
+
unless unknown_keys.empty?
|
|
320
|
+
message = [
|
|
321
|
+
"unknown keyword#{"s" if unknown_keys.length > 1}: ",
|
|
322
|
+
"#{unknown_keys.map(&:inspect).join(", ")} for #{@name.inspect}"
|
|
323
|
+
]
|
|
324
|
+
raise FilterArgumentError.new(
|
|
325
|
+
message.join,
|
|
326
|
+
@span,
|
|
327
|
+
context.template.source,
|
|
328
|
+
context.template.name
|
|
329
|
+
)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
missing_keys = required_keys.reject { |key| kwargs.key?(key) }
|
|
334
|
+
unless missing_keys.empty?
|
|
335
|
+
message = [
|
|
336
|
+
"missing keyword#{"s" if missing_keys.length > 1}: ",
|
|
337
|
+
"#{missing_keys.map(&:inspect).join(", ")} for #{@name.inspect}"
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
raise FilterArgumentError.new(
|
|
341
|
+
message.join,
|
|
342
|
+
@span,
|
|
343
|
+
context.template.source,
|
|
344
|
+
context.template.name
|
|
345
|
+
)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
class PrefixExpression < Expression
|
|
351
|
+
#: (t_token, Expression) -> void
|
|
352
|
+
def initialize(token, right)
|
|
353
|
+
super(token)
|
|
354
|
+
@right = right
|
|
355
|
+
@span = Luoma.span(token, right.span)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def children
|
|
359
|
+
[@right]
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
class NotExpression < PrefixExpression
|
|
364
|
+
def evaluate(context)
|
|
365
|
+
!context.env.truthy?(@right.evaluate(context), context)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
#: () -> String
|
|
369
|
+
def to_s
|
|
370
|
+
"not #{@right}"
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
class PosExpression < PrefixExpression
|
|
375
|
+
def evaluate(context)
|
|
376
|
+
context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
#: () -> String
|
|
380
|
+
def to_s
|
|
381
|
+
"+#{@right}"
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
class NegExpression < PrefixExpression
|
|
386
|
+
def evaluate(context)
|
|
387
|
+
right = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
388
|
+
context.env.nothing?(right) ? :nothing : -right # steep:ignore
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
#: () -> String
|
|
392
|
+
def to_s
|
|
393
|
+
"-#{@right}"
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
class InfixExpression < Expression
|
|
398
|
+
#: (t_token, Expression, Expression) -> void
|
|
399
|
+
def initialize(token, left, right)
|
|
400
|
+
super(token)
|
|
401
|
+
@left = left
|
|
402
|
+
@right = right
|
|
403
|
+
@span = Luoma.span(left.span, right.span)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def children
|
|
407
|
+
[@left, @right]
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
class CoalesceExpression < InfixExpression
|
|
412
|
+
#: (RenderContext) -> untyped
|
|
413
|
+
def evaluate(context)
|
|
414
|
+
left = @left.evaluate(context)
|
|
415
|
+
context.env.nothing?(left) ? @right.evaluate(context) : left
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
#: () -> String
|
|
419
|
+
def to_s
|
|
420
|
+
"#{@left} orElse #{@right}"
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
class OrExpression < InfixExpression
|
|
425
|
+
#: (RenderContext) -> untyped
|
|
426
|
+
def evaluate(context)
|
|
427
|
+
left = @left.evaluate(context)
|
|
428
|
+
context.env.truthy?(left, context) ? left : @right.evaluate(context)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
#: () -> String
|
|
432
|
+
def to_s
|
|
433
|
+
"#{@left} or #{@right}"
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
class AndExpression < InfixExpression
|
|
438
|
+
#: (RenderContext) -> untyped
|
|
439
|
+
def evaluate(context)
|
|
440
|
+
left = @left.evaluate(context)
|
|
441
|
+
context.env.truthy?(left, context) ? @right.evaluate(context) : left
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
#: () -> String
|
|
445
|
+
def to_s
|
|
446
|
+
"#{@left} and #{@right}"
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
class EqExpression < InfixExpression
|
|
451
|
+
#: (RenderContext) -> untyped
|
|
452
|
+
def evaluate(context)
|
|
453
|
+
context.env.eq?(@left.evaluate(context), @right.evaluate(context), context, @span)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
#: () -> String
|
|
457
|
+
def to_s
|
|
458
|
+
"#{@left} == #{@right}"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
class NeExpression < InfixExpression
|
|
463
|
+
#: (RenderContext) -> untyped
|
|
464
|
+
def evaluate(context)
|
|
465
|
+
!context.env.eq?(@left.evaluate(context), @right.evaluate(context), context, @span)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
#: () -> String
|
|
469
|
+
def to_s
|
|
470
|
+
"#{@left} != #{@right}"
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
class LtExpression < InfixExpression
|
|
475
|
+
#: (RenderContext) -> untyped
|
|
476
|
+
def evaluate(context)
|
|
477
|
+
context.env.lt?(@left.evaluate(context), @right.evaluate(context), context, @span)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
#: () -> String
|
|
481
|
+
def to_s
|
|
482
|
+
"#{@left} < #{@right}"
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
class LeExpression < InfixExpression
|
|
487
|
+
#: (RenderContext) -> untyped
|
|
488
|
+
def evaluate(context)
|
|
489
|
+
left = @left.evaluate(context)
|
|
490
|
+
right = @right.evaluate(context)
|
|
491
|
+
context.env.lt?(left, right, context, @span) || context.env.eq?(left, right, context, @span)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
#: () -> String
|
|
495
|
+
def to_s
|
|
496
|
+
"#{@left} <= #{@right}"
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
class GtExpression < InfixExpression
|
|
501
|
+
#: (RenderContext) -> untyped
|
|
502
|
+
def evaluate(context)
|
|
503
|
+
context.env.lt?(@right.evaluate(context), @left.evaluate(context), context, @span)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
#: () -> String
|
|
507
|
+
def to_s
|
|
508
|
+
"#{@left} > #{@right}"
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
class GeExpression < InfixExpression
|
|
513
|
+
#: (RenderContext) -> untyped
|
|
514
|
+
def evaluate(context)
|
|
515
|
+
left = @left.evaluate(context)
|
|
516
|
+
right = @right.evaluate(context)
|
|
517
|
+
context.env.lt?(right, left, context, @span) || context.env.eq?(left, right, context, @span)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
#: () -> String
|
|
521
|
+
def to_s
|
|
522
|
+
"#{@left} >= #{@right}"
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
class ContainsExpression < InfixExpression
|
|
527
|
+
#: (RenderContext) -> untyped
|
|
528
|
+
def evaluate(context)
|
|
529
|
+
context.env.contains?(@left.evaluate(context), @right.evaluate(context), context, @span)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
#: () -> String
|
|
533
|
+
def to_s
|
|
534
|
+
"#{@left} contains #{@right}"
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
class InExpression < InfixExpression
|
|
539
|
+
#: (RenderContext) -> untyped
|
|
540
|
+
def evaluate(context)
|
|
541
|
+
context.env.contains?(@right.evaluate(context), @left.evaluate(context), context, @span)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
#: () -> String
|
|
545
|
+
def to_s
|
|
546
|
+
"#{@left} in #{@right}"
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
class AddExpression < InfixExpression
|
|
551
|
+
#: (RenderContext) -> untyped
|
|
552
|
+
def evaluate(context)
|
|
553
|
+
left = context.env.to_numeric(@left.evaluate(context), context, default: :nothing)
|
|
554
|
+
right = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
555
|
+
left == :nothing || right == :nothing ? :nothing : left + right # steep:ignore
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
#: () -> String
|
|
559
|
+
def to_s
|
|
560
|
+
"#{@left} + #{@right}"
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
class SubExpression < InfixExpression
|
|
565
|
+
#: (RenderContext) -> untyped
|
|
566
|
+
def evaluate(context)
|
|
567
|
+
left = context.env.to_numeric(@left.evaluate(context), context, default: :nothing)
|
|
568
|
+
right = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
569
|
+
left == :nothing || right == :nothing ? :nothing : left - right # steep:ignore
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
#: () -> String
|
|
573
|
+
def to_s
|
|
574
|
+
"#{@left} - #{@right}"
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
class MulExpression < InfixExpression
|
|
579
|
+
#: (RenderContext) -> untyped
|
|
580
|
+
def evaluate(context)
|
|
581
|
+
left = context.env.to_numeric(@left.evaluate(context), context, default: :nothing)
|
|
582
|
+
right = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
583
|
+
left == :nothing || right == :nothing ? :nothing : left * right # steep:ignore
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
#: () -> String
|
|
587
|
+
def to_s
|
|
588
|
+
"#{@left} * #{@right}"
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
class DivExpression < InfixExpression
|
|
593
|
+
#: (RenderContext) -> untyped
|
|
594
|
+
def evaluate(context)
|
|
595
|
+
lhs = context.env.to_numeric(@left.evaluate(context), context, default: :nothing)
|
|
596
|
+
rhs = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
597
|
+
return :nothing if lhs == :nothing || rhs == :nothing || rhs.zero? # steep:ignore
|
|
598
|
+
|
|
599
|
+
result = lhs.to_d / rhs # steep:ignore
|
|
600
|
+
result.frac.zero? && lhs.is_a?(::Integer) && rhs.is_a?(::Integer) ? result.to_i : result
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
#: () -> String
|
|
604
|
+
def to_s
|
|
605
|
+
"#{@left} / #{@right}"
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
class ModExpression < InfixExpression
|
|
610
|
+
#: (RenderContext) -> untyped
|
|
611
|
+
def evaluate(context)
|
|
612
|
+
lhs = context.env.to_numeric(@left.evaluate(context), context, default: :nothing)
|
|
613
|
+
rhs = context.env.to_numeric(@right.evaluate(context), context, default: :nothing)
|
|
614
|
+
return :nothing if lhs == :nothing || rhs == :nothing || rhs.zero? # steep:ignore
|
|
615
|
+
|
|
616
|
+
result = lhs.to_d % rhs # steep:ignore
|
|
617
|
+
result.frac.zero? && lhs.is_a?(::Integer) && rhs.is_a?(::Integer) ? result.to_i : result
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
#: () -> String
|
|
621
|
+
def to_s
|
|
622
|
+
"#{@left} % #{@right}"
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
class Variable < Expression
|
|
627
|
+
attr_reader :root, :segments
|
|
628
|
+
|
|
629
|
+
#: (t_token, Name | StringLiteral | Variable, Array[t_path_segment]) -> void
|
|
630
|
+
def initialize(token, root, segments)
|
|
631
|
+
super(token)
|
|
632
|
+
@root = root
|
|
633
|
+
@segments = segments
|
|
634
|
+
@span = segments.empty? ? root.span : Luoma.span(root.span, segments.last.span)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
#: (RenderContext) -> untyped
|
|
638
|
+
def evaluate(context)
|
|
639
|
+
root_segment = @root.is_a?(Variable) ? @root.evaluate(context) : @root.value # steep:ignore
|
|
640
|
+
root = root_segment.is_a?(String) ? context.scopes.fetch(root_segment, :nothing) : :nothing
|
|
641
|
+
|
|
642
|
+
obj, index = if @segments.empty?
|
|
643
|
+
[root, 0]
|
|
644
|
+
else
|
|
645
|
+
context.resolve_path(root, @segments.map { |s| s.evaluate(context) })
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
if obj == :nothing
|
|
649
|
+
context.env.undefined.new(
|
|
650
|
+
path(@segments[0..index] || raise),
|
|
651
|
+
@span,
|
|
652
|
+
context.template.source,
|
|
653
|
+
context.template.name
|
|
654
|
+
)
|
|
655
|
+
else
|
|
656
|
+
obj
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def children
|
|
661
|
+
if @root.is_a?(Variable)
|
|
662
|
+
[@root, *@segments.grep(Variable)] #: Array[_Traversable]
|
|
663
|
+
else
|
|
664
|
+
@segments.filter.grep(Variable) #: Array[_Traversable]
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
#: () -> String
|
|
669
|
+
def to_s
|
|
670
|
+
path(@segments)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# Return true if this variable has a root segment matching `name` and
|
|
674
|
+
# no other segments.
|
|
675
|
+
#: (String) -> bool
|
|
676
|
+
def ident?(name)
|
|
677
|
+
@segments.empty? && @root.is_a?(Name) && @root.value == name # steep:ignore
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
private
|
|
681
|
+
|
|
682
|
+
#: (Array[t_path_segment]) -> String
|
|
683
|
+
def path(segments)
|
|
684
|
+
root = @root.is_a?(Name) ? @root.to_s : "[#{@root}]"
|
|
685
|
+
return root if segments.empty?
|
|
686
|
+
|
|
687
|
+
segments_ = segments.map { |s| s.is_a?(Name) || s.is_a?(Predicate) ? ".#{s}" : "[#{s}]" }.join
|
|
688
|
+
"#{root}#{segments_}"
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
class IndexSelector < Expression
|
|
693
|
+
attr_reader :value
|
|
694
|
+
|
|
695
|
+
#: (t_token, Integer)
|
|
696
|
+
def initialize(token, value)
|
|
697
|
+
super(token)
|
|
698
|
+
@value = value
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
#: (RenderContext) -> untyped
|
|
702
|
+
def evaluate(context)
|
|
703
|
+
@value
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def children
|
|
707
|
+
[]
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
#: () -> String
|
|
711
|
+
def to_s
|
|
712
|
+
@value.to_s
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
def with(token)
|
|
716
|
+
@span = Luoma.span(@token, token)
|
|
717
|
+
self
|
|
718
|
+
end
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
class Lambda < Expression
|
|
722
|
+
#: (t_token, Array[Name], Expression) -> void
|
|
723
|
+
def initialize(token, params, expr)
|
|
724
|
+
super(token)
|
|
725
|
+
@params = params
|
|
726
|
+
@expr = expr
|
|
727
|
+
@span = Luoma.span(token, expr.span)
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
#: (RenderContext) -> untyped
|
|
731
|
+
def evaluate(context)
|
|
732
|
+
ExpressionDrop.new(LambdaExpr.new(@params.map(&:value), @expr, context))
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def children
|
|
736
|
+
[@expr]
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def scope
|
|
740
|
+
@params
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
#: () -> String
|
|
744
|
+
def to_s
|
|
745
|
+
"(#{@params.join(", ")}) => #{@expr}"
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
class StringLiteral < Expression
|
|
750
|
+
attr_reader :segments
|
|
751
|
+
|
|
752
|
+
#: (t_token, Array[String|Expression], t_token)
|
|
753
|
+
def initialize(token, segments, end_token)
|
|
754
|
+
super(token)
|
|
755
|
+
@segments = segments
|
|
756
|
+
@span = Luoma.span(token, end_token)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
#: (RenderContext) -> untyped
|
|
760
|
+
def evaluate(context)
|
|
761
|
+
@segments.map do |s|
|
|
762
|
+
s.is_a?(String) ? s : context.env.to_string(s.evaluate(context), context)
|
|
763
|
+
end.join
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def children
|
|
767
|
+
@segments.grep_v(String) #: Array[Expression]
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
#: () -> String
|
|
771
|
+
def to_s
|
|
772
|
+
@segments.map { |s| s.is_a?(String) ? s : "${#{s}}" }.join.inspect
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
def with(token)
|
|
776
|
+
@span = Luoma.span(@token, token)
|
|
777
|
+
self
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
# Return the string, or nil if the string literal contains interpolated expressions.
|
|
781
|
+
#: () -> String?
|
|
782
|
+
def value
|
|
783
|
+
@segments.first if @segments.length == 1 && @segments.first.is_a?(String) #: String?
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
class IntegerLiteral < Expression
|
|
788
|
+
attr_reader :value
|
|
789
|
+
|
|
790
|
+
#: (t_token, Integer)
|
|
791
|
+
def initialize(token, value)
|
|
792
|
+
super(token)
|
|
793
|
+
@value = value
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
#: (RenderContext) -> untyped
|
|
797
|
+
def evaluate(context)
|
|
798
|
+
@value
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def children
|
|
802
|
+
[]
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
#: () -> String
|
|
806
|
+
def to_s
|
|
807
|
+
@value.to_s
|
|
808
|
+
end
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
class FloatLiteral < Expression
|
|
812
|
+
attr_reader :value
|
|
813
|
+
|
|
814
|
+
#: (t_token, Float)
|
|
815
|
+
def initialize(token, value)
|
|
816
|
+
super(token)
|
|
817
|
+
@value = value
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
#: (RenderContext) -> untyped
|
|
821
|
+
def evaluate(context)
|
|
822
|
+
@value
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
def children
|
|
826
|
+
[]
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
#: () -> String
|
|
830
|
+
def to_s
|
|
831
|
+
@value.to_s
|
|
832
|
+
end
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
class BooleanLiteral < Expression
|
|
836
|
+
attr_reader :value
|
|
837
|
+
|
|
838
|
+
#: (t_token, bool)
|
|
839
|
+
def initialize(token, value)
|
|
840
|
+
super(token)
|
|
841
|
+
@value = value
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
#: (RenderContext) -> untyped
|
|
845
|
+
def evaluate(context)
|
|
846
|
+
@value
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
def children
|
|
850
|
+
[]
|
|
851
|
+
end
|
|
852
|
+
|
|
853
|
+
#: () -> String
|
|
854
|
+
def to_s
|
|
855
|
+
@value.to_s
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
class NullLiteral < Expression
|
|
860
|
+
#: (RenderContext) -> untyped
|
|
861
|
+
def evaluate(context)
|
|
862
|
+
nil
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
def children
|
|
866
|
+
[]
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
#: () -> String
|
|
870
|
+
def to_s
|
|
871
|
+
""
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
class RangeLiteral < Expression
|
|
876
|
+
#: (t_token, Expression, Expression) -> void
|
|
877
|
+
def initialize(token, start, stop)
|
|
878
|
+
super(token)
|
|
879
|
+
@start = start
|
|
880
|
+
@stop = stop
|
|
881
|
+
@span = Luoma.span(start.token, stop.token)
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
#: (RenderContext) -> untyped
|
|
885
|
+
def evaluate(context)
|
|
886
|
+
start = context.env.to_i(@start.evaluate(context), context, default: 0)
|
|
887
|
+
stop = context.env.to_i(@stop.evaluate(context), context, default: 0)
|
|
888
|
+
RangeDrop.new(start, stop)
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def children
|
|
892
|
+
[@start, @stop]
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
#: () -> String
|
|
896
|
+
def to_s
|
|
897
|
+
"(#{@start}..#{@stop})"
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
class ArrayLiteral < Expression
|
|
902
|
+
#: (t_token token, Array[Expression|Spread] items) -> void
|
|
903
|
+
def initialize(token, items)
|
|
904
|
+
super(token)
|
|
905
|
+
@items = items
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
#: (RenderContext) -> untyped
|
|
909
|
+
def evaluate(context)
|
|
910
|
+
result = [] #: Array[untyped]
|
|
911
|
+
|
|
912
|
+
@items.each do |item|
|
|
913
|
+
if item.is_a?(Spread)
|
|
914
|
+
result.concat(context.env.to_a(item.expr.evaluate(context), context))
|
|
915
|
+
else
|
|
916
|
+
result << item.evaluate(context)
|
|
917
|
+
end
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
result
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
def children
|
|
924
|
+
@items
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
#: () -> String
|
|
928
|
+
def to_s
|
|
929
|
+
"[#{@items.join(", ")}]"
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def with(token)
|
|
933
|
+
@span = Luoma.span(@token, token)
|
|
934
|
+
self
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
class ObjectLiteral < Expression
|
|
939
|
+
#: (t_token token, Array[Item|Spread] items) -> void
|
|
940
|
+
def initialize(token, items)
|
|
941
|
+
super(token)
|
|
942
|
+
@items = items
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
#: (RenderContext) -> untyped
|
|
946
|
+
def evaluate(context)
|
|
947
|
+
result = {} #: Hash[String, untyped]
|
|
948
|
+
|
|
949
|
+
@items.each do |item|
|
|
950
|
+
if item.is_a?(Spread)
|
|
951
|
+
result.update(context.env.to_h(item.expr.evaluate(context), context))
|
|
952
|
+
else
|
|
953
|
+
result[item.key.value] = item.expr.evaluate(context)
|
|
954
|
+
end
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
result
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
def children
|
|
961
|
+
@items
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
#: () -> String
|
|
965
|
+
def to_s
|
|
966
|
+
"{#{@items.join(", ")}}"
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def with(token)
|
|
970
|
+
@span = Luoma.span(@token, token)
|
|
971
|
+
self
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
class Predicate < Expression
|
|
976
|
+
attr_reader :value
|
|
977
|
+
|
|
978
|
+
#: (t_token, String) -> void
|
|
979
|
+
def initialize(token, value)
|
|
980
|
+
super(token)
|
|
981
|
+
@value = value
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
#: (RenderContext) -> untyped
|
|
985
|
+
def evaluate(context)
|
|
986
|
+
func = context.env.predicates[@value]
|
|
987
|
+
|
|
988
|
+
if func.nil?
|
|
989
|
+
if context.env.strict
|
|
990
|
+
raise PredicateNotFoundError.new(
|
|
991
|
+
"unknown predicate #{@value.inspect}",
|
|
992
|
+
@token,
|
|
993
|
+
context.template.source,
|
|
994
|
+
context.template.name
|
|
995
|
+
)
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
:nothing
|
|
999
|
+
else
|
|
1000
|
+
PredicateFunction.new(@value, func)
|
|
1001
|
+
end
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
def children
|
|
1005
|
+
[]
|
|
1006
|
+
end
|
|
1007
|
+
|
|
1008
|
+
#: () -> String
|
|
1009
|
+
def to_s
|
|
1010
|
+
"#{@value}?"
|
|
1011
|
+
end
|
|
1012
|
+
end
|
|
1013
|
+
|
|
1014
|
+
class Spread
|
|
1015
|
+
attr_reader :token, :expr
|
|
1016
|
+
|
|
1017
|
+
#: (t_token, Expression) -> void
|
|
1018
|
+
def initialize(token, expr)
|
|
1019
|
+
@token = token
|
|
1020
|
+
@expr = expr
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def children
|
|
1024
|
+
[@expr]
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
#: () -> String
|
|
1028
|
+
def to_s
|
|
1029
|
+
"...#{@expr}"
|
|
1030
|
+
end
|
|
1031
|
+
end
|
|
1032
|
+
|
|
1033
|
+
class Item
|
|
1034
|
+
attr_reader :token, :key, :expr
|
|
1035
|
+
|
|
1036
|
+
#: (t_token, Name, Expression) -> void
|
|
1037
|
+
def initialize(token, key, expr)
|
|
1038
|
+
@token = token
|
|
1039
|
+
@key = key
|
|
1040
|
+
@expr = expr
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
def children
|
|
1044
|
+
[@expr]
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
#: () -> String
|
|
1048
|
+
def to_s
|
|
1049
|
+
"#{@key}: #{@expr}"
|
|
1050
|
+
end
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
class Name
|
|
1054
|
+
attr_reader :token, :span, :value
|
|
1055
|
+
|
|
1056
|
+
#: (t_token, String) -> void
|
|
1057
|
+
def initialize(token, value)
|
|
1058
|
+
@token = token
|
|
1059
|
+
@value = value
|
|
1060
|
+
@span = token
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
#: (RenderContext) -> untyped
|
|
1064
|
+
def evaluate(context)
|
|
1065
|
+
@value
|
|
1066
|
+
end
|
|
1067
|
+
|
|
1068
|
+
def children
|
|
1069
|
+
[]
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
#: () -> String
|
|
1073
|
+
def to_s
|
|
1074
|
+
@value
|
|
1075
|
+
end
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
class Filter
|
|
1079
|
+
attr_reader :token, :span, :name, :args, :kwargs
|
|
1080
|
+
|
|
1081
|
+
#: (t_token, Variable, Array[Expression], Array[KeywordArgument])
|
|
1082
|
+
def initialize(token, name, args, kwargs)
|
|
1083
|
+
@token = token
|
|
1084
|
+
@name = name
|
|
1085
|
+
@args = args
|
|
1086
|
+
@kwargs = kwargs
|
|
1087
|
+
|
|
1088
|
+
@span = if args.empty? && kwargs.empty?
|
|
1089
|
+
token
|
|
1090
|
+
elsif kwargs.empty?
|
|
1091
|
+
Luoma.span(token, args.last.span)
|
|
1092
|
+
elsif args.empty?
|
|
1093
|
+
Luoma.span(token, kwargs.last.span)
|
|
1094
|
+
else
|
|
1095
|
+
Luoma.span(
|
|
1096
|
+
token,
|
|
1097
|
+
kwargs.last.token[1] > args.last.token[1] ? kwargs.last.span : args.last.span
|
|
1098
|
+
)
|
|
1099
|
+
end
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
def children
|
|
1103
|
+
@kwargs.empty? ? @args : [*@args, *@kwargs.map(&:expression)]
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
def to_s
|
|
1107
|
+
return @name.to_s if @args.empty? && @kwargs.empty?
|
|
1108
|
+
|
|
1109
|
+
args = @args.join(", ")
|
|
1110
|
+
args << ", " << @kwargs.join(",") unless @kwargs.empty?
|
|
1111
|
+
"#{@name}: #{args}"
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
class KeywordArgument
|
|
1116
|
+
attr_reader :token, :span, :name, :expression
|
|
1117
|
+
|
|
1118
|
+
#: (t_token, Name, Expression) -> void
|
|
1119
|
+
def initialize(token, name, expression)
|
|
1120
|
+
@token = token
|
|
1121
|
+
@name = name
|
|
1122
|
+
@expression = expression
|
|
1123
|
+
@span = Luoma.span(token, expression.span)
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def children
|
|
1127
|
+
[@expression]
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
#: () -> String
|
|
1131
|
+
def to_s
|
|
1132
|
+
"#{@name}: #{@expression}"
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
def deconstruct
|
|
1136
|
+
[@name.value, @expression]
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
def deconstruct_keys(keys)
|
|
1140
|
+
{ name: @name.value, expression: @expression }
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
class PredicateFunction
|
|
1145
|
+
attr_reader :name, :func
|
|
1146
|
+
|
|
1147
|
+
#: (String, ^(untyped) -> bool) -> void
|
|
1148
|
+
def initialize(name, func)
|
|
1149
|
+
@name = name
|
|
1150
|
+
@func = func
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
#: (untyped) -> bool
|
|
1154
|
+
def call(context, obj)
|
|
1155
|
+
@func.call(context, obj)
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
class LambdaExpr
|
|
1160
|
+
attr_reader :params
|
|
1161
|
+
|
|
1162
|
+
#: (Array[String], Expression, RenderContext) -> void
|
|
1163
|
+
def initialize(params, expr, context)
|
|
1164
|
+
@params = params
|
|
1165
|
+
@expr = expr
|
|
1166
|
+
@context = context
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
#: (Array[untyped]) -> untyped
|
|
1170
|
+
def call(args)
|
|
1171
|
+
# zip pads with `nil` and ignores excess args.
|
|
1172
|
+
@context.extends(@params.zip(args).to_h) do
|
|
1173
|
+
@expr.evaluate(@context)
|
|
1174
|
+
end
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
# Call this lambda expression for each item in `enum`, passing item and
|
|
1178
|
+
# index as arguments if this expression accepts two arguments, otherwise
|
|
1179
|
+
# just item. Excess parameters are ignored.
|
|
1180
|
+
#
|
|
1181
|
+
#: (Enumerable[untyped]) -> Array[untyped]
|
|
1182
|
+
def broadcast_with_index(enum)
|
|
1183
|
+
scope = {} #: Hash[String, untyped]
|
|
1184
|
+
result = [] #: Array[untyped]
|
|
1185
|
+
|
|
1186
|
+
if @params.length == 1
|
|
1187
|
+
param = @params[0]
|
|
1188
|
+
|
|
1189
|
+
@context.extends(scope) do
|
|
1190
|
+
enum.each do |item|
|
|
1191
|
+
scope[param] = item
|
|
1192
|
+
result << @expr.evaluate(@context)
|
|
1193
|
+
end
|
|
1194
|
+
end
|
|
1195
|
+
else
|
|
1196
|
+
name_param = @params[0]
|
|
1197
|
+
index_param = @params[1]
|
|
1198
|
+
|
|
1199
|
+
@context.extends(scope) do
|
|
1200
|
+
enum.each_with_index do |item, i|
|
|
1201
|
+
scope[index_param] = i
|
|
1202
|
+
scope[name_param] = item
|
|
1203
|
+
result << @expr.evaluate(@context)
|
|
1204
|
+
end
|
|
1205
|
+
end
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
result
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
# Call this lambda expression, passing `value` and `index` as arguments
|
|
1212
|
+
# if this expression accepts two arguments, otherwise just `value`. Excess
|
|
1213
|
+
# parameters are ignored.
|
|
1214
|
+
#
|
|
1215
|
+
#: (untyped, Integer) -> untyped
|
|
1216
|
+
def call_with_index(value, index)
|
|
1217
|
+
scope = { @params[0] => value }
|
|
1218
|
+
scope[@params[1]] = index if @params.length > 1
|
|
1219
|
+
|
|
1220
|
+
@context.extends(scope) do
|
|
1221
|
+
@expr.evaluate(@context)
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
#: () -> String
|
|
1226
|
+
def to_s
|
|
1227
|
+
"(#{@params.join(", ")}) => #{@expr}"
|
|
1228
|
+
end
|
|
1229
|
+
end
|
|
1230
|
+
|
|
1231
|
+
#: (Expression) -> String
|
|
1232
|
+
def self.tree_view(e)
|
|
1233
|
+
# (prefix, connector, class_name, inspect_value)
|
|
1234
|
+
nodes = [] # : Array[[String, String, String, String]]
|
|
1235
|
+
|
|
1236
|
+
# @type var visit: ^(_Traversable, String, bool) -> void
|
|
1237
|
+
visit = lambda do |node, prefix, is_last|
|
|
1238
|
+
connector = if prefix.empty?
|
|
1239
|
+
""
|
|
1240
|
+
elsif is_last
|
|
1241
|
+
"└── "
|
|
1242
|
+
else
|
|
1243
|
+
"├── "
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
nodes << [prefix, connector, node.class.to_s, node.to_s]
|
|
1247
|
+
child_prefix = prefix + (is_last ? " " : "│ ")
|
|
1248
|
+
node.children.each_with_index do |child, i|
|
|
1249
|
+
last = i == node.children.length - 1
|
|
1250
|
+
visit.call(child, child_prefix, last)
|
|
1251
|
+
end
|
|
1252
|
+
end
|
|
1253
|
+
|
|
1254
|
+
visit.call(e, "", true)
|
|
1255
|
+
|
|
1256
|
+
widths = nodes.map { |prefix, connector, cls| (prefix + connector + cls).length }
|
|
1257
|
+
max_width = widths.max || 0
|
|
1258
|
+
|
|
1259
|
+
lines = [] # : Array[String]
|
|
1260
|
+
nodes.zip(widths).each do |node, width|
|
|
1261
|
+
prefix, connector, cls, val = node
|
|
1262
|
+
left = prefix + connector + cls
|
|
1263
|
+
padding = " " * (max_width - (width || raise) + 4)
|
|
1264
|
+
lines << (left + padding + val)
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
lines.join("\n")
|
|
1268
|
+
end
|
|
1269
|
+
end
|
|
1270
|
+
|
|
1271
|
+
# rubocop:enable Naming/PredicateMethod
|