python-liquid 2.3.6__py3-none-any.whl → 3.0.0__py3-none-any.whl
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.
- liquid/__about__.py +1 -0
- liquid/__init__.py +161 -187
- liquid/_base_tag.py +66 -0
- liquid/{utils/chain_map.py → _chain_map.py} +18 -19
- liquid/_context.py +465 -0
- liquid/_drop.py +108 -0
- liquid/_filter.py +174 -0
- liquid/_io.py +18 -0
- liquid/_lax_parser_mixin.py +100 -0
- liquid/_lexer.py +93 -0
- liquid/_lexer_extended.py +623 -0
- liquid/_lexer_legacy.py +536 -0
- liquid/_loader.py +84 -0
- liquid/{utils/lru_cache.py → _lru_cache.py} +28 -36
- liquid/_markup.py +253 -0
- liquid/_messages.py +292 -0
- liquid/_missing.py +3 -0
- liquid/_nothing.py +28 -0
- liquid/_parser.py +460 -0
- liquid/_parser_extended.py +293 -0
- liquid/_parser_legacy.py +819 -0
- liquid/_resource_limits.py +50 -0
- liquid/_slice.py +41 -0
- liquid/_static_analysis.py +503 -0
- liquid/_strftime.py +186 -0
- liquid/_template.py +536 -0
- liquid/drops/__init__.py +20 -0
- liquid/drops/forloop.py +78 -0
- liquid/drops/iterable.py +25 -0
- liquid/drops/range.py +76 -0
- liquid/drops/self_.py +45 -0
- liquid/drops/tablerowloop.py +142 -0
- liquid/drops/undefined.py +228 -0
- liquid/environment.py +717 -551
- liquid/exceptions.py +67 -214
- liquid/expressions.py +1184 -0
- liquid/filters/__init__.py +134 -0
- liquid/filters/abs.py +7 -0
- liquid/filters/append.py +5 -0
- liquid/filters/at_least.py +9 -0
- liquid/filters/at_most.py +9 -0
- liquid/filters/babel.py +560 -0
- liquid/filters/base64.py +26 -0
- liquid/filters/capitalize.py +5 -0
- liquid/filters/ceil.py +8 -0
- liquid/filters/compact.py +25 -0
- liquid/filters/concat.py +11 -0
- liquid/filters/date.py +27 -0
- liquid/filters/default.py +25 -0
- liquid/filters/divided_by.py +21 -0
- liquid/filters/downcase.py +5 -0
- liquid/filters/escape.py +56 -0
- liquid/filters/find.py +56 -0
- liquid/filters/first.py +19 -0
- liquid/filters/floor.py +8 -0
- liquid/filters/html.py +23 -0
- liquid/filters/index.py +12 -0
- liquid/filters/join.py +13 -0
- liquid/filters/json_.py +30 -0
- liquid/filters/last.py +17 -0
- liquid/filters/map.py +5 -0
- liquid/filters/minus.py +13 -0
- liquid/filters/modulo.py +18 -0
- liquid/filters/newline_to_br.py +16 -0
- liquid/filters/plus.py +13 -0
- liquid/filters/prepend.py +5 -0
- liquid/filters/reject.py +19 -0
- liquid/filters/remove.py +36 -0
- liquid/filters/replace.py +32 -0
- liquid/filters/reverse.py +5 -0
- liquid/filters/round.py +35 -0
- liquid/filters/size.py +25 -0
- liquid/filters/slice.py +39 -0
- liquid/filters/sort.py +148 -0
- liquid/filters/split.py +34 -0
- liquid/filters/squish.py +9 -0
- liquid/filters/strip.py +13 -0
- liquid/filters/strip_html.py +19 -0
- liquid/filters/strip_newlines.py +17 -0
- liquid/filters/sum.py +21 -0
- liquid/filters/times.py +13 -0
- liquid/filters/translate.py +380 -0
- liquid/filters/truncate.py +39 -0
- liquid/filters/uniq.py +11 -0
- liquid/filters/upcase.py +5 -0
- liquid/filters/url.py +18 -0
- liquid/filters/where.py +18 -0
- liquid/loaders/__init__.py +17 -0
- liquid/loaders/caching_file_system_loader.py +64 -0
- liquid/loaders/choice_loader.py +93 -0
- liquid/loaders/dict_loader.py +56 -0
- liquid/{builtin/loaders → loaders}/file_system_loader.py +51 -55
- liquid/{builtin/loaders → loaders}/mixins.py +39 -51
- liquid/loaders/package_loader.py +110 -0
- liquid/tags/__init__.py +61 -0
- liquid/tags/assign.py +68 -0
- liquid/tags/capture.py +83 -0
- liquid/tags/case.py +199 -0
- liquid/tags/comment.py +56 -0
- liquid/tags/cycle.py +131 -0
- liquid/tags/decrement.py +55 -0
- liquid/tags/doc.py +56 -0
- liquid/tags/echo.py +65 -0
- liquid/tags/else_.py +53 -0
- liquid/tags/error.py +51 -0
- liquid/tags/extends.py +448 -0
- liquid/tags/for_.py +421 -0
- liquid/tags/if_.py +156 -0
- liquid/tags/ifchanged.py +69 -0
- liquid/tags/include.py +223 -0
- liquid/tags/increment.py +55 -0
- liquid/tags/inline_comment.py +70 -0
- liquid/tags/liquid.py +56 -0
- liquid/tags/macro.py +281 -0
- liquid/tags/output.py +58 -0
- liquid/tags/raw.py +60 -0
- liquid/tags/render.py +245 -0
- liquid/tags/tablerow.py +254 -0
- liquid/tags/template_comment.py +56 -0
- liquid/tags/translate.py +316 -0
- liquid/tags/unless.py +121 -0
- liquid/tags/with_.py +83 -0
- liquid/tokens.py +370 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/METADATA +32 -39
- python_liquid-3.0.0.dist-info/RECORD +128 -0
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/WHEEL +1 -1
- {python_liquid-2.3.6.dist-info → python_liquid-3.0.0.dist-info}/licenses/LICENSE +1 -1
- liquid/analyze_tags.py +0 -214
- liquid/ast.py +0 -252
- liquid/builtin/__init__.py +0 -203
- liquid/builtin/content.py +0 -41
- liquid/builtin/drops/__init__.py +0 -3
- liquid/builtin/drops/drops.py +0 -13
- liquid/builtin/expressions/__init__.py +0 -47
- liquid/builtin/expressions/_tokenize.py +0 -186
- liquid/builtin/expressions/arguments.py +0 -207
- liquid/builtin/expressions/filtered.py +0 -387
- liquid/builtin/expressions/logical.py +0 -672
- liquid/builtin/expressions/loop.py +0 -275
- liquid/builtin/expressions/path.py +0 -163
- liquid/builtin/expressions/primitive.py +0 -417
- liquid/builtin/filters/__init__.py +0 -0
- liquid/builtin/filters/array.py +0 -329
- liquid/builtin/filters/extra.py +0 -83
- liquid/builtin/filters/math.py +0 -129
- liquid/builtin/filters/misc.py +0 -137
- liquid/builtin/filters/string.py +0 -416
- liquid/builtin/illegal.py +0 -28
- liquid/builtin/loaders/__init__.py +0 -0
- liquid/builtin/loaders/caching_file_system_loader.py +0 -61
- liquid/builtin/loaders/choice_loader.py +0 -94
- liquid/builtin/loaders/dict_loader.py +0 -64
- liquid/builtin/loaders/package_loader.py +0 -132
- liquid/builtin/output.py +0 -74
- liquid/builtin/tags/__init__.py +0 -0
- liquid/builtin/tags/assign_tag.py +0 -75
- liquid/builtin/tags/capture_tag.py +0 -98
- liquid/builtin/tags/case_tag.py +0 -287
- liquid/builtin/tags/comment_tag.py +0 -77
- liquid/builtin/tags/cycle_tag.py +0 -132
- liquid/builtin/tags/decrement_tag.py +0 -59
- liquid/builtin/tags/doc_tag.py +0 -68
- liquid/builtin/tags/echo_tag.py +0 -45
- liquid/builtin/tags/for_tag.py +0 -337
- liquid/builtin/tags/if_tag.py +0 -209
- liquid/builtin/tags/ifchanged_tag.py +0 -94
- liquid/builtin/tags/include_tag.py +0 -251
- liquid/builtin/tags/increment_tag.py +0 -60
- liquid/builtin/tags/inline_comment_tag.py +0 -47
- liquid/builtin/tags/liquid_tag.py +0 -183
- liquid/builtin/tags/render_tag.py +0 -371
- liquid/builtin/tags/tablerow_tag.py +0 -320
- liquid/builtin/tags/unless_tag.py +0 -218
- liquid/context.py +0 -615
- liquid/expression.py +0 -41
- liquid/extra/__init__.py +0 -98
- liquid/extra/filters/__init__.py +0 -33
- liquid/extra/filters/_json.py +0 -33
- liquid/extra/filters/array.py +0 -60
- liquid/extra/filters/babel.py +0 -552
- liquid/extra/filters/html.py +0 -32
- liquid/extra/filters/translate.py +0 -448
- liquid/extra/tags/__init__.py +0 -17
- liquid/extra/tags/_with.py +0 -80
- liquid/extra/tags/extends_tag.py +0 -576
- liquid/extra/tags/macro_tag.py +0 -270
- liquid/extra/tags/snippet_tag.py +0 -107
- liquid/extra/tags/translate_tag.py +0 -388
- liquid/filter.py +0 -230
- liquid/future/__init__.py +0 -3
- liquid/future/environment.py +0 -30
- liquid/future/filters/__init__.py +0 -1
- liquid/future/tags/__init__.py +0 -1
- liquid/lex.py +0 -260
- liquid/limits.py +0 -66
- liquid/loader.py +0 -141
- liquid/messages.py +0 -357
- liquid/mode.py +0 -12
- liquid/output.py +0 -35
- liquid/parser.py +0 -119
- liquid/span.py +0 -35
- liquid/static_analysis.py +0 -443
- liquid/stream.py +0 -148
- liquid/stringify.py +0 -31
- liquid/tag.py +0 -47
- liquid/template.py +0 -658
- liquid/token.py +0 -145
- liquid/undefined.py +0 -218
- liquid/utils/__init__.py +0 -15
- liquid/utils/html.py +0 -57
- liquid/utils/text.py +0 -28
- python_liquid-2.3.6.dist-info/RECORD +0 -96
liquid/expressions.py
ADDED
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import InitVar, dataclass, field
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import markupsafe
|
|
8
|
+
|
|
9
|
+
from ._filter import FilterContext
|
|
10
|
+
from ._nothing import NOTHING
|
|
11
|
+
from .drops import BLANK, EMPTY, Range
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
LiquidFilterInterrupt,
|
|
14
|
+
LiquidNameError,
|
|
15
|
+
LiquidSyntaxError,
|
|
16
|
+
LiquidTypeError,
|
|
17
|
+
LiquidZeroDivisionError,
|
|
18
|
+
)
|
|
19
|
+
from .tokens import span
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from ._context import RenderContext
|
|
23
|
+
from ._markup import Expression
|
|
24
|
+
from .tokens import Token
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(slots=True)
|
|
28
|
+
class Filtered:
|
|
29
|
+
token: Token
|
|
30
|
+
left: Expression
|
|
31
|
+
name: Name
|
|
32
|
+
args: list[Expression | KeywordArgument]
|
|
33
|
+
span: Token = field(init=False)
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
self.span = (
|
|
37
|
+
span(self.left.token, self.name.token)
|
|
38
|
+
if not self.args
|
|
39
|
+
else span(self.left.token, self.args[-1].span)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
43
|
+
func = context.env.filters.get(self.name.value)
|
|
44
|
+
|
|
45
|
+
# Like Shopify/liquid, filter strictness is applied at render time,
|
|
46
|
+
# and only on rendered markup. Unknown filters in unreached markup will
|
|
47
|
+
# not throw an error.
|
|
48
|
+
if func is None:
|
|
49
|
+
if context.env.strict_filters:
|
|
50
|
+
raise LiquidNameError(
|
|
51
|
+
f"unknown filter {self.name.value!r}",
|
|
52
|
+
token=self.name.token,
|
|
53
|
+
source=context.template.source,
|
|
54
|
+
name=context.template.name,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Pass the input value through.
|
|
58
|
+
return self.left.evaluate(context)
|
|
59
|
+
|
|
60
|
+
left = self.left.evaluate(context)
|
|
61
|
+
|
|
62
|
+
if not self.args:
|
|
63
|
+
try:
|
|
64
|
+
return func(FilterContext(context, self.span), left)
|
|
65
|
+
except LiquidFilterInterrupt:
|
|
66
|
+
return None
|
|
67
|
+
except (TypeError, ValueError) as err:
|
|
68
|
+
raise LiquidTypeError(
|
|
69
|
+
str(err),
|
|
70
|
+
token=self.span,
|
|
71
|
+
source=context.template.source,
|
|
72
|
+
name=context.template.name,
|
|
73
|
+
) from err
|
|
74
|
+
|
|
75
|
+
args: list[object] = []
|
|
76
|
+
kwargs: dict[str, object] = {}
|
|
77
|
+
|
|
78
|
+
for arg in self.args:
|
|
79
|
+
if isinstance(arg, KeywordArgument):
|
|
80
|
+
kwargs[arg.name.value] = arg.expr.evaluate(context)
|
|
81
|
+
else:
|
|
82
|
+
args.append(arg.evaluate(context))
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
return func(FilterContext(context, self.span), left, *args, **kwargs)
|
|
86
|
+
except LiquidFilterInterrupt:
|
|
87
|
+
return None
|
|
88
|
+
except (TypeError, ValueError) as err:
|
|
89
|
+
raise LiquidTypeError(
|
|
90
|
+
str(err),
|
|
91
|
+
token=self.span,
|
|
92
|
+
source=context.template.source,
|
|
93
|
+
name=context.template.name,
|
|
94
|
+
) from err
|
|
95
|
+
|
|
96
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
97
|
+
func = context.env.filters.get(self.name.value)
|
|
98
|
+
|
|
99
|
+
# Like Shopify/liquid, filter strictness is applied at render time,
|
|
100
|
+
# and only on rendered markup. Unknown filters in unreached markup will
|
|
101
|
+
# not throw an error.
|
|
102
|
+
if func is None:
|
|
103
|
+
if context.env.strict_filters:
|
|
104
|
+
raise LiquidNameError(
|
|
105
|
+
f"unknown filter {self.name.value!r}",
|
|
106
|
+
token=self.name.token,
|
|
107
|
+
source=context.template.source,
|
|
108
|
+
name=context.template.name,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Pass the input value through.
|
|
112
|
+
return await self.left.evaluate_async(context)
|
|
113
|
+
|
|
114
|
+
left = await self.left.evaluate_async(context)
|
|
115
|
+
|
|
116
|
+
if not self.args:
|
|
117
|
+
try:
|
|
118
|
+
return func(FilterContext(context, self.span), left)
|
|
119
|
+
except LiquidFilterInterrupt:
|
|
120
|
+
return None
|
|
121
|
+
except (TypeError, ValueError) as err:
|
|
122
|
+
raise LiquidTypeError(
|
|
123
|
+
str(err),
|
|
124
|
+
token=self.span,
|
|
125
|
+
source=context.template.source,
|
|
126
|
+
name=context.template.name,
|
|
127
|
+
) from err
|
|
128
|
+
|
|
129
|
+
args: list[object] = []
|
|
130
|
+
kwargs: dict[str, object] = {}
|
|
131
|
+
|
|
132
|
+
for arg in self.args:
|
|
133
|
+
if isinstance(arg, KeywordArgument):
|
|
134
|
+
kwargs[arg.name.value] = await arg.expr.evaluate_async(context)
|
|
135
|
+
else:
|
|
136
|
+
args.append(await arg.evaluate_async(context))
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
return func(FilterContext(context, self.span), left, *args, **kwargs)
|
|
140
|
+
except LiquidFilterInterrupt:
|
|
141
|
+
return None
|
|
142
|
+
except (TypeError, ValueError) as err:
|
|
143
|
+
raise LiquidTypeError(
|
|
144
|
+
str(err),
|
|
145
|
+
token=self.span,
|
|
146
|
+
source=context.template.source,
|
|
147
|
+
name=context.template.name,
|
|
148
|
+
) from err
|
|
149
|
+
|
|
150
|
+
def children(self) -> list[Expression]:
|
|
151
|
+
return [
|
|
152
|
+
self.left,
|
|
153
|
+
*(a.expr if isinstance(a, KeywordArgument) else a for a in self.args),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
def __str__(self) -> str:
|
|
157
|
+
if self.args:
|
|
158
|
+
args = ": " + ", ".join(
|
|
159
|
+
f"{a.name.value}: {a.expr}"
|
|
160
|
+
if isinstance(a, KeywordArgument)
|
|
161
|
+
else str(a)
|
|
162
|
+
for a in self.args
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
args = ""
|
|
166
|
+
|
|
167
|
+
return f"{self.left} | {self.name.value}{args}"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass(slots=True)
|
|
171
|
+
class Group:
|
|
172
|
+
token: Token
|
|
173
|
+
expr: Expression
|
|
174
|
+
segments: list[PathSegment] | None
|
|
175
|
+
end_token: InitVar[Token]
|
|
176
|
+
span: Token = field(init=False)
|
|
177
|
+
|
|
178
|
+
def __post_init__(self, end_token: Token) -> None:
|
|
179
|
+
if self.segments:
|
|
180
|
+
self.span = span(self.token, self.segments[-1].span)
|
|
181
|
+
else:
|
|
182
|
+
self.span = span(self.token, end_token)
|
|
183
|
+
|
|
184
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
185
|
+
obj = self.expr.evaluate(context)
|
|
186
|
+
|
|
187
|
+
if not self.segments:
|
|
188
|
+
return obj
|
|
189
|
+
|
|
190
|
+
obj, index = context.resolve_path(
|
|
191
|
+
obj,
|
|
192
|
+
[s if isinstance(s, Name) else s.evaluate(context) for s in self.segments],
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if obj is NOTHING:
|
|
196
|
+
return context.env.undefined(
|
|
197
|
+
self._path(self.segments[: index + 1]),
|
|
198
|
+
self.span,
|
|
199
|
+
context.template.source,
|
|
200
|
+
context.template.name,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return obj
|
|
204
|
+
|
|
205
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
206
|
+
obj = await self.expr.evaluate_async(context)
|
|
207
|
+
|
|
208
|
+
if not self.segments:
|
|
209
|
+
return obj
|
|
210
|
+
|
|
211
|
+
obj, index = context.resolve_path(
|
|
212
|
+
obj,
|
|
213
|
+
[
|
|
214
|
+
s if isinstance(s, Name) else await s.evaluate_async(context)
|
|
215
|
+
for s in self.segments
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if obj is NOTHING:
|
|
220
|
+
return context.env.undefined(
|
|
221
|
+
self._path(self.segments[: index + 1]),
|
|
222
|
+
self.span,
|
|
223
|
+
context.template.source,
|
|
224
|
+
context.template.name,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return obj
|
|
228
|
+
|
|
229
|
+
def __str__(self) -> str:
|
|
230
|
+
return f"({self.expr}){self._path(self.segments)}"
|
|
231
|
+
|
|
232
|
+
def children(self) -> list[Expression]:
|
|
233
|
+
if not self.segments:
|
|
234
|
+
return [self.expr]
|
|
235
|
+
return [
|
|
236
|
+
self.expr,
|
|
237
|
+
*(s.var for s in self.segments if isinstance(s, VariableSegment)),
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
def _path(self, segments: list[PathSegment] | None) -> str:
|
|
241
|
+
if not segments:
|
|
242
|
+
return ""
|
|
243
|
+
|
|
244
|
+
return "".join(f".{s}" if isinstance(s, Name) else str(s) for s in segments)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass(slots=True)
|
|
248
|
+
class Ternary:
|
|
249
|
+
token: Token
|
|
250
|
+
consequence: Expression
|
|
251
|
+
condition: Expression
|
|
252
|
+
alternative: Expression | None
|
|
253
|
+
span: Token = field(init=False)
|
|
254
|
+
|
|
255
|
+
def __post_init__(self) -> None:
|
|
256
|
+
self.span = span(self.token, (self.alternative or self.condition).span)
|
|
257
|
+
|
|
258
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
259
|
+
if context.env.truthy(self.condition.evaluate(context), context):
|
|
260
|
+
return self.consequence.evaluate(context)
|
|
261
|
+
|
|
262
|
+
if self.alternative:
|
|
263
|
+
return self.alternative.evaluate(context)
|
|
264
|
+
|
|
265
|
+
return NOTHING
|
|
266
|
+
|
|
267
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
268
|
+
if context.env.truthy(await self.condition.evaluate_async(context), context):
|
|
269
|
+
return await self.consequence.evaluate_async(context)
|
|
270
|
+
|
|
271
|
+
if self.alternative:
|
|
272
|
+
return await self.alternative.evaluate_async(context)
|
|
273
|
+
|
|
274
|
+
return NOTHING
|
|
275
|
+
|
|
276
|
+
def __str__(self) -> str:
|
|
277
|
+
if self.alternative:
|
|
278
|
+
return f"{self.consequence} if {self.condition} else {self.alternative}"
|
|
279
|
+
return f"{self.consequence} if {self.condition}"
|
|
280
|
+
|
|
281
|
+
def children(self) -> list[Expression]:
|
|
282
|
+
result: list[Expression] = [self.consequence, self.condition]
|
|
283
|
+
if self.alternative:
|
|
284
|
+
result.append(self.alternative)
|
|
285
|
+
return result
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass(slots=True)
|
|
289
|
+
class PrefixExpression:
|
|
290
|
+
token: Token
|
|
291
|
+
right: Expression
|
|
292
|
+
span: Token = field(init=False)
|
|
293
|
+
|
|
294
|
+
def __post_init__(self) -> None:
|
|
295
|
+
self.span = span(self.token, self.right.span)
|
|
296
|
+
|
|
297
|
+
def children(self) -> list[Expression]:
|
|
298
|
+
return [self.right]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass(slots=True)
|
|
302
|
+
class LogicalNot(PrefixExpression):
|
|
303
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
304
|
+
return not context.env.truthy(self.right.evaluate(context), context)
|
|
305
|
+
|
|
306
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
307
|
+
return not context.env.truthy(await self.right.evaluate_async(context), context)
|
|
308
|
+
|
|
309
|
+
def __str__(self) -> str:
|
|
310
|
+
return f"not {self.right}"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@dataclass(slots=True)
|
|
314
|
+
class Pos(PrefixExpression):
|
|
315
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
316
|
+
return context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
317
|
+
|
|
318
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
319
|
+
return context.env.to_numeric(
|
|
320
|
+
await self.right.evaluate_async(context), context, 0
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def __str__(self) -> str:
|
|
324
|
+
return f"+{self.right}"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass(slots=True)
|
|
328
|
+
class Neg(PrefixExpression):
|
|
329
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
330
|
+
return -context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
331
|
+
|
|
332
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
333
|
+
return -context.env.to_numeric(
|
|
334
|
+
await self.right.evaluate_async(context), context, 0
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def __str__(self) -> str:
|
|
338
|
+
return f"-{self.right}"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@dataclass(slots=True)
|
|
342
|
+
class InfixExpression:
|
|
343
|
+
token: Token
|
|
344
|
+
left: Expression
|
|
345
|
+
right: Expression
|
|
346
|
+
span: Token = field(init=False)
|
|
347
|
+
|
|
348
|
+
def __post_init__(self) -> None:
|
|
349
|
+
self.span = span(self.left.span, self.right.span)
|
|
350
|
+
|
|
351
|
+
def children(self) -> list[Expression]:
|
|
352
|
+
return [self.left, self.right]
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@dataclass(slots=True)
|
|
356
|
+
class LogicalOr(InfixExpression):
|
|
357
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
358
|
+
left = self.left.evaluate(context)
|
|
359
|
+
return (
|
|
360
|
+
left if context.env.truthy(left, context) else self.right.evaluate(context)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
364
|
+
left = await self.left.evaluate_async(context)
|
|
365
|
+
return (
|
|
366
|
+
left
|
|
367
|
+
if context.env.truthy(left, context)
|
|
368
|
+
else await self.right.evaluate_async(context)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def __str__(self) -> str:
|
|
372
|
+
return f"{self.left} or {self.right}"
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@dataclass(slots=True)
|
|
376
|
+
class LogicalAnd(InfixExpression):
|
|
377
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
378
|
+
left = self.left.evaluate(context)
|
|
379
|
+
return (
|
|
380
|
+
self.right.evaluate(context) if context.env.truthy(left, context) else left
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
384
|
+
left = await self.left.evaluate_async(context)
|
|
385
|
+
return (
|
|
386
|
+
await self.right.evaluate_async(context)
|
|
387
|
+
if context.env.truthy(left, context)
|
|
388
|
+
else left
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
def __str__(self) -> str:
|
|
392
|
+
return f"{self.left} and {self.right}"
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
@dataclass(slots=True)
|
|
396
|
+
class Eq(InfixExpression):
|
|
397
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
398
|
+
return context.env.eq(
|
|
399
|
+
self.left.evaluate(context),
|
|
400
|
+
self.right.evaluate(context),
|
|
401
|
+
context,
|
|
402
|
+
self.span,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
406
|
+
return context.env.eq(
|
|
407
|
+
await self.left.evaluate_async(context),
|
|
408
|
+
await self.right.evaluate_async(context),
|
|
409
|
+
context,
|
|
410
|
+
self.span,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
def __str__(self) -> str:
|
|
414
|
+
return f"{self.left} == {self.right}"
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@dataclass(slots=True)
|
|
418
|
+
class Ne(InfixExpression):
|
|
419
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
420
|
+
return not context.env.eq(
|
|
421
|
+
self.left.evaluate(context),
|
|
422
|
+
self.right.evaluate(context),
|
|
423
|
+
context,
|
|
424
|
+
self.span,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
428
|
+
return not context.env.eq(
|
|
429
|
+
await self.left.evaluate_async(context),
|
|
430
|
+
await self.right.evaluate_async(context),
|
|
431
|
+
context,
|
|
432
|
+
self.span,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def __str__(self) -> str:
|
|
436
|
+
return f"{self.left} != {self.right}"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@dataclass(slots=True)
|
|
440
|
+
class Lt(InfixExpression):
|
|
441
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
442
|
+
return context.env.lt(
|
|
443
|
+
self.left.evaluate(context),
|
|
444
|
+
self.right.evaluate(context),
|
|
445
|
+
context,
|
|
446
|
+
self.span,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
450
|
+
return context.env.lt(
|
|
451
|
+
await self.left.evaluate_async(context),
|
|
452
|
+
await self.right.evaluate_async(context),
|
|
453
|
+
context,
|
|
454
|
+
self.span,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
def __str__(self) -> str:
|
|
458
|
+
return f"{self.left} < {self.right}"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@dataclass(slots=True)
|
|
462
|
+
class Le(InfixExpression):
|
|
463
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
464
|
+
return context.env.le(
|
|
465
|
+
self.left.evaluate(context),
|
|
466
|
+
self.right.evaluate(context),
|
|
467
|
+
context,
|
|
468
|
+
self.span,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
472
|
+
return context.env.le(
|
|
473
|
+
await self.left.evaluate_async(context),
|
|
474
|
+
await self.right.evaluate_async(context),
|
|
475
|
+
context,
|
|
476
|
+
self.span,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
def __str__(self) -> str:
|
|
480
|
+
return f"{self.left} <= {self.right}"
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@dataclass(slots=True)
|
|
484
|
+
class Gt(InfixExpression):
|
|
485
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
486
|
+
return context.env.lt(
|
|
487
|
+
self.right.evaluate(context),
|
|
488
|
+
self.left.evaluate(context),
|
|
489
|
+
context,
|
|
490
|
+
self.span,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
494
|
+
return context.env.lt(
|
|
495
|
+
await self.right.evaluate_async(context),
|
|
496
|
+
await self.left.evaluate_async(context),
|
|
497
|
+
context,
|
|
498
|
+
self.span,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
def __str__(self) -> str:
|
|
502
|
+
return f"{self.left} > {self.right}"
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@dataclass(slots=True)
|
|
506
|
+
class Ge(InfixExpression):
|
|
507
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
508
|
+
return context.env.le(
|
|
509
|
+
self.right.evaluate(context),
|
|
510
|
+
self.left.evaluate(context),
|
|
511
|
+
context,
|
|
512
|
+
self.span,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
516
|
+
return context.env.le(
|
|
517
|
+
await self.right.evaluate_async(context),
|
|
518
|
+
await self.left.evaluate_async(context),
|
|
519
|
+
context,
|
|
520
|
+
self.span,
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
def __str__(self) -> str:
|
|
524
|
+
return f"{self.left} >= {self.right}"
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@dataclass(slots=True)
|
|
528
|
+
class Contains(InfixExpression):
|
|
529
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
530
|
+
return context.env.contains(
|
|
531
|
+
self.left.evaluate(context),
|
|
532
|
+
self.right.evaluate(context),
|
|
533
|
+
context,
|
|
534
|
+
self.span,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
538
|
+
return context.env.contains(
|
|
539
|
+
await self.left.evaluate_async(context),
|
|
540
|
+
await self.right.evaluate_async(context),
|
|
541
|
+
context,
|
|
542
|
+
self.span,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def __str__(self) -> str:
|
|
546
|
+
return f"{self.left} contains {self.right}"
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
@dataclass(slots=True)
|
|
550
|
+
class Add(InfixExpression):
|
|
551
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
552
|
+
left = context.env.to_numeric(self.left.evaluate(context), context, 0)
|
|
553
|
+
right = context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
554
|
+
|
|
555
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
556
|
+
return left + right
|
|
557
|
+
|
|
558
|
+
return float(Decimal(str(left)) + Decimal(str(right)))
|
|
559
|
+
|
|
560
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
561
|
+
left = context.env.to_numeric(
|
|
562
|
+
await self.left.evaluate_async(context), context, 0
|
|
563
|
+
)
|
|
564
|
+
right = context.env.to_numeric(
|
|
565
|
+
await self.right.evaluate_async(context), context, 0
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
569
|
+
return left + right
|
|
570
|
+
|
|
571
|
+
return float(Decimal(str(left)) + Decimal(str(right)))
|
|
572
|
+
|
|
573
|
+
def __str__(self) -> str:
|
|
574
|
+
return f"{self.left} + {self.right}"
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@dataclass(slots=True)
|
|
578
|
+
class Sub(InfixExpression):
|
|
579
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
580
|
+
left = context.env.to_numeric(self.left.evaluate(context), context, 0)
|
|
581
|
+
right = context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
582
|
+
|
|
583
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
584
|
+
return left - right
|
|
585
|
+
|
|
586
|
+
return float(Decimal(str(left)) - Decimal(str(right)))
|
|
587
|
+
|
|
588
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
589
|
+
left = context.env.to_numeric(
|
|
590
|
+
await self.left.evaluate_async(context), context, 0
|
|
591
|
+
)
|
|
592
|
+
right = context.env.to_numeric(
|
|
593
|
+
await self.right.evaluate_async(context), context, 0
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
597
|
+
return left - right
|
|
598
|
+
|
|
599
|
+
return float(Decimal(str(left)) - Decimal(str(right)))
|
|
600
|
+
|
|
601
|
+
def __str__(self) -> str:
|
|
602
|
+
return f"{self.left} - {self.right}"
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@dataclass(slots=True)
|
|
606
|
+
class Mul(InfixExpression):
|
|
607
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
608
|
+
left = context.env.to_numeric(self.left.evaluate(context), context, 0)
|
|
609
|
+
right = context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
610
|
+
|
|
611
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
612
|
+
return left * right
|
|
613
|
+
|
|
614
|
+
return float(Decimal(str(left)) * Decimal(str(right)))
|
|
615
|
+
|
|
616
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
617
|
+
left = context.env.to_numeric(
|
|
618
|
+
await self.left.evaluate_async(context), context, 0
|
|
619
|
+
)
|
|
620
|
+
right = context.env.to_numeric(
|
|
621
|
+
await self.right.evaluate_async(context), context, 0
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
625
|
+
return left * right
|
|
626
|
+
|
|
627
|
+
return float(Decimal(str(left)) * Decimal(str(right)))
|
|
628
|
+
|
|
629
|
+
def __str__(self) -> str:
|
|
630
|
+
return f"{self.left} * {self.right}"
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
@dataclass(slots=True)
|
|
634
|
+
class Div(InfixExpression):
|
|
635
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
636
|
+
left = context.env.to_numeric(self.left.evaluate(context), context, 0)
|
|
637
|
+
right = context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
638
|
+
|
|
639
|
+
if right == 0:
|
|
640
|
+
raise LiquidZeroDivisionError(
|
|
641
|
+
"divide by zero",
|
|
642
|
+
token=self.right.span,
|
|
643
|
+
source=context.template.source,
|
|
644
|
+
name=context.template.name,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
648
|
+
return left // right
|
|
649
|
+
|
|
650
|
+
if isinstance(left, Decimal) or isinstance(right, Decimal):
|
|
651
|
+
return float(Decimal(str(left)) / Decimal(str(right)))
|
|
652
|
+
|
|
653
|
+
return left / right
|
|
654
|
+
|
|
655
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
656
|
+
left = context.env.to_numeric(
|
|
657
|
+
await self.left.evaluate_async(context), context, 0
|
|
658
|
+
)
|
|
659
|
+
right = context.env.to_numeric(
|
|
660
|
+
await self.right.evaluate_async(context), context, 0
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
if right == 0:
|
|
664
|
+
raise LiquidZeroDivisionError(
|
|
665
|
+
"divide by zero",
|
|
666
|
+
token=self.right.span,
|
|
667
|
+
source=context.template.source,
|
|
668
|
+
name=context.template.name,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
672
|
+
return left // right
|
|
673
|
+
|
|
674
|
+
if isinstance(left, Decimal) or isinstance(right, Decimal):
|
|
675
|
+
return float(Decimal(str(left)) / Decimal(str(right)))
|
|
676
|
+
|
|
677
|
+
return left / right
|
|
678
|
+
|
|
679
|
+
def __str__(self) -> str:
|
|
680
|
+
return f"{self.left} / {self.right}"
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
@dataclass(slots=True)
|
|
684
|
+
class Mod(InfixExpression):
|
|
685
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
686
|
+
left = context.env.to_numeric(self.left.evaluate(context), context, 0)
|
|
687
|
+
right = context.env.to_numeric(self.right.evaluate(context), context, 0)
|
|
688
|
+
|
|
689
|
+
if right == 0:
|
|
690
|
+
raise LiquidZeroDivisionError(
|
|
691
|
+
"divide by zero",
|
|
692
|
+
token=self.right.span,
|
|
693
|
+
source=context.template.source,
|
|
694
|
+
name=context.template.name,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
698
|
+
return left % right
|
|
699
|
+
|
|
700
|
+
return float(Decimal(str(left)) % Decimal(str(right)))
|
|
701
|
+
|
|
702
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
703
|
+
left = context.env.to_numeric(
|
|
704
|
+
await self.left.evaluate_async(context), context, 0
|
|
705
|
+
)
|
|
706
|
+
right = context.env.to_numeric(
|
|
707
|
+
await self.right.evaluate_async(context), context, 0
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
if right == 0:
|
|
711
|
+
raise LiquidZeroDivisionError(
|
|
712
|
+
"divide by zero",
|
|
713
|
+
token=self.right.span,
|
|
714
|
+
source=context.template.source,
|
|
715
|
+
name=context.template.name,
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if isinstance(left, int) and isinstance(right, int):
|
|
719
|
+
return left % right
|
|
720
|
+
|
|
721
|
+
return float(Decimal(str(left)) % Decimal(str(right)))
|
|
722
|
+
|
|
723
|
+
def __str__(self) -> str:
|
|
724
|
+
return f"{self.left} / {self.right}"
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
@dataclass(slots=True)
|
|
728
|
+
class Variable:
|
|
729
|
+
token: Token
|
|
730
|
+
root: Name | NameSegment | VariableSegment
|
|
731
|
+
selectors: list[PathSegment] | None
|
|
732
|
+
span: Token = field(init=False)
|
|
733
|
+
|
|
734
|
+
def __post_init__(self) -> None:
|
|
735
|
+
self.span = (
|
|
736
|
+
self.root.span
|
|
737
|
+
if not self.selectors
|
|
738
|
+
else span(self.root.span, self.selectors[-1].span)
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
742
|
+
selector = (
|
|
743
|
+
self.root.evaluate(context)
|
|
744
|
+
if isinstance(self.root, VariableSegment)
|
|
745
|
+
else self.root.value
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
index = 0
|
|
749
|
+
obj = context.resolve(selector)
|
|
750
|
+
|
|
751
|
+
if self.selectors:
|
|
752
|
+
obj, index = context.resolve_path(
|
|
753
|
+
obj,
|
|
754
|
+
[
|
|
755
|
+
s if isinstance(s, (Name, IntegerLiteral)) else s.evaluate(context)
|
|
756
|
+
for s in self.selectors
|
|
757
|
+
],
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
if obj is NOTHING:
|
|
761
|
+
return context.env.undefined(
|
|
762
|
+
self.path(self.selectors[: index + 1] if self.selectors else None),
|
|
763
|
+
self.span,
|
|
764
|
+
context.template.source,
|
|
765
|
+
context.template.name,
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
return obj
|
|
769
|
+
|
|
770
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
771
|
+
selector = (
|
|
772
|
+
await self.root.evaluate_async(context)
|
|
773
|
+
if isinstance(self.root, VariableSegment)
|
|
774
|
+
else self.root.value
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
index = 0
|
|
778
|
+
obj = context.resolve(selector)
|
|
779
|
+
|
|
780
|
+
if self.selectors:
|
|
781
|
+
obj, index = await context.resolve_path_async(
|
|
782
|
+
obj,
|
|
783
|
+
[
|
|
784
|
+
s
|
|
785
|
+
if isinstance(s, (Name, IntegerLiteral))
|
|
786
|
+
else await s.evaluate_async(context)
|
|
787
|
+
for s in self.selectors
|
|
788
|
+
],
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
if obj is NOTHING:
|
|
792
|
+
return context.env.undefined(
|
|
793
|
+
self.path(self.selectors[: index + 1] if self.selectors else None),
|
|
794
|
+
self.span,
|
|
795
|
+
context.template.source,
|
|
796
|
+
context.template.name,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
return obj
|
|
800
|
+
|
|
801
|
+
def children(self) -> list[Expression]:
|
|
802
|
+
exprs: list[Expression] = []
|
|
803
|
+
|
|
804
|
+
if isinstance(self.root, VariableSegment):
|
|
805
|
+
exprs.append(self.root.var)
|
|
806
|
+
|
|
807
|
+
if self.selectors:
|
|
808
|
+
exprs.extend(
|
|
809
|
+
s.var for s in self.selectors if isinstance(s, VariableSegment)
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
return exprs
|
|
813
|
+
|
|
814
|
+
def path(self, selectors: list[PathSegment] | None) -> str:
|
|
815
|
+
root = str(self.root)
|
|
816
|
+
|
|
817
|
+
if selectors:
|
|
818
|
+
_selectors = "".join(
|
|
819
|
+
f".{s}" if isinstance(s, (Name, IntegerLiteral)) else str(s)
|
|
820
|
+
for s in selectors
|
|
821
|
+
)
|
|
822
|
+
return f"{root}{_selectors}"
|
|
823
|
+
|
|
824
|
+
return root
|
|
825
|
+
|
|
826
|
+
def location(self) -> Location:
|
|
827
|
+
def serialize(s: PathSegment) -> int | str | Location:
|
|
828
|
+
if isinstance(s, (Name, NameSegment, IndexSegment, IntegerLiteral)):
|
|
829
|
+
return s.value
|
|
830
|
+
if isinstance(s, VariableSegment):
|
|
831
|
+
return s.var.location()
|
|
832
|
+
return str(s)
|
|
833
|
+
|
|
834
|
+
segments: Location = [serialize(self.root)]
|
|
835
|
+
if self.selectors:
|
|
836
|
+
segments.extend(serialize(s) for s in self.selectors)
|
|
837
|
+
|
|
838
|
+
return segments
|
|
839
|
+
|
|
840
|
+
def __str__(self) -> str:
|
|
841
|
+
return self.path(self.selectors)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
@dataclass(slots=True)
|
|
845
|
+
class IndexSegment:
|
|
846
|
+
token: Token
|
|
847
|
+
value: int
|
|
848
|
+
span: Token
|
|
849
|
+
|
|
850
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
851
|
+
return self.value
|
|
852
|
+
|
|
853
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
854
|
+
return self.value
|
|
855
|
+
|
|
856
|
+
def children(self) -> list[Expression]:
|
|
857
|
+
return []
|
|
858
|
+
|
|
859
|
+
def __str__(self) -> str:
|
|
860
|
+
return f"[{self.value}]"
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
@dataclass(slots=True)
|
|
864
|
+
class NameSegment:
|
|
865
|
+
token: Token
|
|
866
|
+
value: str
|
|
867
|
+
span: Token
|
|
868
|
+
|
|
869
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
870
|
+
return self.value
|
|
871
|
+
|
|
872
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
873
|
+
return self.value
|
|
874
|
+
|
|
875
|
+
def children(self) -> list[Expression]:
|
|
876
|
+
return []
|
|
877
|
+
|
|
878
|
+
def __str__(self) -> str:
|
|
879
|
+
return f"[{self.value!r}]"
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
@dataclass(slots=True)
|
|
883
|
+
class VariableSegment:
|
|
884
|
+
token: Token
|
|
885
|
+
var: Variable
|
|
886
|
+
span: Token
|
|
887
|
+
|
|
888
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
889
|
+
return self.var.evaluate(context)
|
|
890
|
+
|
|
891
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
892
|
+
return await self.var.evaluate_async(context)
|
|
893
|
+
|
|
894
|
+
def children(self) -> list[Expression]:
|
|
895
|
+
return [self.var]
|
|
896
|
+
|
|
897
|
+
def __str__(self) -> str:
|
|
898
|
+
return f"[{self.var}]"
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@dataclass(slots=True)
|
|
902
|
+
class StringLiteral:
|
|
903
|
+
token: Token
|
|
904
|
+
value: str
|
|
905
|
+
span: Token
|
|
906
|
+
|
|
907
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
908
|
+
return markupsafe.Markup(self.value) if context.env.autoescape else self.value
|
|
909
|
+
|
|
910
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
911
|
+
return markupsafe.Markup(self.value) if context.env.autoescape else self.value
|
|
912
|
+
|
|
913
|
+
def children(self) -> list[Expression]:
|
|
914
|
+
return []
|
|
915
|
+
|
|
916
|
+
def __str__(self) -> str:
|
|
917
|
+
return repr(self.value)
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
@dataclass(slots=True)
|
|
921
|
+
class IntegerLiteral:
|
|
922
|
+
token: Token
|
|
923
|
+
value: int
|
|
924
|
+
span: Token = field(init=False)
|
|
925
|
+
|
|
926
|
+
def __post_init__(self) -> None:
|
|
927
|
+
self.span = self.token
|
|
928
|
+
|
|
929
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
930
|
+
return self.value
|
|
931
|
+
|
|
932
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
933
|
+
return self.value
|
|
934
|
+
|
|
935
|
+
def children(self) -> list[Expression]:
|
|
936
|
+
return []
|
|
937
|
+
|
|
938
|
+
def __str__(self) -> str:
|
|
939
|
+
return str(self.value)
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
@dataclass(slots=True)
|
|
943
|
+
class FloatLiteral:
|
|
944
|
+
token: Token
|
|
945
|
+
value: float
|
|
946
|
+
span: Token = field(init=False)
|
|
947
|
+
|
|
948
|
+
def __post_init__(self) -> None:
|
|
949
|
+
self.span = self.token
|
|
950
|
+
|
|
951
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
952
|
+
return self.value
|
|
953
|
+
|
|
954
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
955
|
+
return self.value
|
|
956
|
+
|
|
957
|
+
def children(self) -> list[Expression]:
|
|
958
|
+
return []
|
|
959
|
+
|
|
960
|
+
def __str__(self) -> str:
|
|
961
|
+
return str(self.value)
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
@dataclass(slots=True)
|
|
965
|
+
class BooleanLiteral:
|
|
966
|
+
token: Token
|
|
967
|
+
value: bool
|
|
968
|
+
span: Token = field(init=False)
|
|
969
|
+
|
|
970
|
+
def __post_init__(self) -> None:
|
|
971
|
+
self.span = self.token
|
|
972
|
+
|
|
973
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
974
|
+
return self.value
|
|
975
|
+
|
|
976
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
977
|
+
return self.value
|
|
978
|
+
|
|
979
|
+
def children(self) -> list[Expression]:
|
|
980
|
+
return []
|
|
981
|
+
|
|
982
|
+
def __str__(self) -> str:
|
|
983
|
+
return str(self.value).lower()
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
@dataclass(slots=True)
|
|
987
|
+
class NullLiteral:
|
|
988
|
+
token: Token
|
|
989
|
+
span: Token = field(init=False)
|
|
990
|
+
|
|
991
|
+
def __post_init__(self) -> None:
|
|
992
|
+
self.span = self.token
|
|
993
|
+
|
|
994
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
995
|
+
return None
|
|
996
|
+
|
|
997
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
998
|
+
return None
|
|
999
|
+
|
|
1000
|
+
def children(self) -> list[Expression]:
|
|
1001
|
+
return []
|
|
1002
|
+
|
|
1003
|
+
def __str__(self) -> str:
|
|
1004
|
+
return "null"
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
@dataclass(slots=True)
|
|
1008
|
+
class Blank:
|
|
1009
|
+
token: Token
|
|
1010
|
+
span: Token = field(init=False)
|
|
1011
|
+
|
|
1012
|
+
def __post_init__(self) -> None:
|
|
1013
|
+
self.span = self.token
|
|
1014
|
+
|
|
1015
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
1016
|
+
return BLANK
|
|
1017
|
+
|
|
1018
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
1019
|
+
return BLANK
|
|
1020
|
+
|
|
1021
|
+
def children(self) -> list[Expression]:
|
|
1022
|
+
return []
|
|
1023
|
+
|
|
1024
|
+
def __str__(self) -> str:
|
|
1025
|
+
return ""
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@dataclass(slots=True)
|
|
1029
|
+
class Empty:
|
|
1030
|
+
token: Token
|
|
1031
|
+
span: Token = field(init=False)
|
|
1032
|
+
|
|
1033
|
+
def __post_init__(self) -> None:
|
|
1034
|
+
self.span = self.token
|
|
1035
|
+
|
|
1036
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
1037
|
+
return EMPTY
|
|
1038
|
+
|
|
1039
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
1040
|
+
return EMPTY
|
|
1041
|
+
|
|
1042
|
+
def children(self) -> list[Expression]:
|
|
1043
|
+
return []
|
|
1044
|
+
|
|
1045
|
+
def __str__(self) -> str:
|
|
1046
|
+
return ""
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@dataclass(slots=True)
|
|
1050
|
+
class RangeLiteral:
|
|
1051
|
+
token: Token
|
|
1052
|
+
start: Expression
|
|
1053
|
+
stop: Expression
|
|
1054
|
+
span: Token = field(init=False)
|
|
1055
|
+
value: str = field(init=False)
|
|
1056
|
+
|
|
1057
|
+
def __post_init__(self) -> None:
|
|
1058
|
+
self.span = span(self.start.span, self.stop.span)
|
|
1059
|
+
self.value = ""
|
|
1060
|
+
|
|
1061
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
1062
|
+
start = context.env.to_numeric(self.start.evaluate(context), context)
|
|
1063
|
+
stop = context.env.to_numeric(self.stop.evaluate(context), context)
|
|
1064
|
+
return Range(int(start), int(stop))
|
|
1065
|
+
|
|
1066
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
1067
|
+
start = context.env.to_numeric(
|
|
1068
|
+
await self.start.evaluate_async(context), context
|
|
1069
|
+
)
|
|
1070
|
+
stop = context.env.to_numeric(await self.stop.evaluate_async(context), context)
|
|
1071
|
+
return Range(int(start), int(stop))
|
|
1072
|
+
|
|
1073
|
+
def children(self) -> list[Expression]:
|
|
1074
|
+
return [self.start, self.stop]
|
|
1075
|
+
|
|
1076
|
+
def __str__(self) -> str:
|
|
1077
|
+
return f"({self.start}..{self.stop})"
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
@dataclass(slots=True)
|
|
1081
|
+
class Name:
|
|
1082
|
+
token: Token
|
|
1083
|
+
value: str
|
|
1084
|
+
span: Token = field(init=False)
|
|
1085
|
+
|
|
1086
|
+
def __post_init__(self) -> None:
|
|
1087
|
+
self.span = self.token
|
|
1088
|
+
|
|
1089
|
+
def __str__(self) -> str:
|
|
1090
|
+
return self.value
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
@dataclass(slots=True)
|
|
1094
|
+
class KeywordArgument:
|
|
1095
|
+
token: Token
|
|
1096
|
+
name: Name
|
|
1097
|
+
expr: Expression
|
|
1098
|
+
span: Token = field(init=False)
|
|
1099
|
+
|
|
1100
|
+
def __post_init__(self) -> None:
|
|
1101
|
+
self.span = span(self.token, self.expr.token)
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
@dataclass(slots=True)
|
|
1105
|
+
class Parameter:
|
|
1106
|
+
token: Token
|
|
1107
|
+
name: Name
|
|
1108
|
+
expr: Expression | None
|
|
1109
|
+
span: Token = field(init=False)
|
|
1110
|
+
|
|
1111
|
+
def __post_init__(self) -> None:
|
|
1112
|
+
if self.expr:
|
|
1113
|
+
self.span = span(self.token, self.expr.token)
|
|
1114
|
+
else:
|
|
1115
|
+
self.span = self.token
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
type PathSegment = (
|
|
1119
|
+
Name | NameSegment | IndexSegment | IntegerLiteral | VariableSegment | RangeLiteral
|
|
1120
|
+
)
|
|
1121
|
+
|
|
1122
|
+
type Location = list[int | str | Location]
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
@dataclass(slots=True)
|
|
1126
|
+
class ErrorExpr:
|
|
1127
|
+
token: Token
|
|
1128
|
+
error: LiquidSyntaxError
|
|
1129
|
+
span: Token = field(init=False)
|
|
1130
|
+
|
|
1131
|
+
def __post_init__(self) -> None:
|
|
1132
|
+
self.span = self.token
|
|
1133
|
+
|
|
1134
|
+
def evaluate(self, context: RenderContext) -> object:
|
|
1135
|
+
if context.capture_errors:
|
|
1136
|
+
raise self.error
|
|
1137
|
+
return None
|
|
1138
|
+
|
|
1139
|
+
async def evaluate_async(self, context: RenderContext) -> object:
|
|
1140
|
+
if context.capture_errors:
|
|
1141
|
+
raise self.error
|
|
1142
|
+
return None
|
|
1143
|
+
|
|
1144
|
+
def children(self) -> list[Expression]:
|
|
1145
|
+
return []
|
|
1146
|
+
|
|
1147
|
+
def __str__(self) -> str:
|
|
1148
|
+
return self.error.detailed_message()
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def tree_view(expr: Expression) -> str:
|
|
1152
|
+
"""Return an ASCII tree representation of `expr`."""
|
|
1153
|
+
# (prefix, connector, class_name, inspect_value)
|
|
1154
|
+
nodes: list[tuple[str, str, str, str]] = []
|
|
1155
|
+
|
|
1156
|
+
def visit(node: Expression, prefix: str, *, is_last: bool) -> None:
|
|
1157
|
+
if not prefix:
|
|
1158
|
+
connector = ""
|
|
1159
|
+
elif is_last:
|
|
1160
|
+
connector = "└── "
|
|
1161
|
+
else:
|
|
1162
|
+
connector = "├── "
|
|
1163
|
+
|
|
1164
|
+
nodes.append((prefix, connector, node.__class__.__name__, str(node)))
|
|
1165
|
+
|
|
1166
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
1167
|
+
for i, child in enumerate(node.children()):
|
|
1168
|
+
last = i == len(node.children()) - 1
|
|
1169
|
+
visit(child, child_prefix, is_last=last)
|
|
1170
|
+
|
|
1171
|
+
visit(expr, "", is_last=True)
|
|
1172
|
+
|
|
1173
|
+
widths = [len(prefix + connector + name) for prefix, connector, name, _ in nodes]
|
|
1174
|
+
max_width = max(widths) if widths else 0
|
|
1175
|
+
|
|
1176
|
+
lines: list[str] = []
|
|
1177
|
+
|
|
1178
|
+
for node, width in zip(nodes, widths, strict=True):
|
|
1179
|
+
prefix, connector, name, value = node
|
|
1180
|
+
left = prefix + connector + name
|
|
1181
|
+
padding = " " * (max_width - width + 4)
|
|
1182
|
+
lines.append(left + padding + value)
|
|
1183
|
+
|
|
1184
|
+
return "\n".join(lines)
|