lorebase-core 0.2.3__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.
- lorebase_core/__init__.py +1 -0
- lorebase_core/canonical/__init__.py +35 -0
- lorebase_core/canonical/hashing.py +128 -0
- lorebase_core/canonical/jcs.py +162 -0
- lorebase_core/expr/__init__.py +118 -0
- lorebase_core/expr/ast.py +443 -0
- lorebase_core/expr/checker.py +428 -0
- lorebase_core/expr/compiler.py +87 -0
- lorebase_core/expr/context.py +82 -0
- lorebase_core/expr/derivations.py +306 -0
- lorebase_core/expr/environment.py +176 -0
- lorebase_core/expr/errors.py +98 -0
- lorebase_core/expr/evaluator.py +414 -0
- lorebase_core/expr/functions.py +898 -0
- lorebase_core/expr/limits.py +52 -0
- lorebase_core/expr/operations.py +479 -0
- lorebase_core/expr/ops.py +243 -0
- lorebase_core/expr/resolver.py +96 -0
- lorebase_core/expr/rows.py +148 -0
- lorebase_core/expr/sensitivity.py +337 -0
- lorebase_core/expr/types.py +227 -0
- lorebase_core/expr/validation.py +51 -0
- lorebase_core/expr/values.py +169 -0
- lorebase_core/expr/visibility.py +363 -0
- lorebase_core/infer/__init__.py +36 -0
- lorebase_core/infer/columns.py +621 -0
- lorebase_core/infer/engine.py +546 -0
- lorebase_core/infer/keys.py +164 -0
- lorebase_core/infer/names.py +145 -0
- lorebase_core/infer/options.py +290 -0
- lorebase_core/infer/profile.py +54 -0
- lorebase_core/infer/proposal.py +122 -0
- lorebase_core/infer/references.py +314 -0
- lorebase_core/infer/suggestions.py +731 -0
- lorebase_core/infer/tables.py +81 -0
- lorebase_core/merge/__init__.py +89 -0
- lorebase_core/merge/conflicts.py +262 -0
- lorebase_core/merge/consistency.py +440 -0
- lorebase_core/merge/data_merge.py +522 -0
- lorebase_core/merge/engine.py +476 -0
- lorebase_core/merge/index.py +100 -0
- lorebase_core/merge/objects.py +204 -0
- lorebase_core/merge/policies.py +322 -0
- lorebase_core/merge/resolve.py +321 -0
- lorebase_core/merge/schema_merge.py +429 -0
- lorebase_core/merge/state.py +443 -0
- lorebase_core/model/__init__.py +245 -0
- lorebase_core/model/attribute.py +72 -0
- lorebase_core/model/base.py +61 -0
- lorebase_core/model/derivation.py +95 -0
- lorebase_core/model/domain.py +71 -0
- lorebase_core/model/embedding.py +193 -0
- lorebase_core/model/ids.py +78 -0
- lorebase_core/model/kind.py +85 -0
- lorebase_core/model/lineage.py +114 -0
- lorebase_core/model/operation.py +161 -0
- lorebase_core/model/paths.py +141 -0
- lorebase_core/model/record.py +127 -0
- lorebase_core/model/relation.py +83 -0
- lorebase_core/model/schema.py +219 -0
- lorebase_core/model/source.py +204 -0
- lorebase_core/model/trait.py +37 -0
- lorebase_core/model/vault.py +43 -0
- lorebase_core/model/yaml_schema.py +1058 -0
- lorebase_core/query/__init__.py +97 -0
- lorebase_core/query/dsl.py +227 -0
- lorebase_core/query/resolve.py +794 -0
- lorebase_core/revision/__init__.py +59 -0
- lorebase_core/revision/ancestry.py +272 -0
- lorebase_core/revision/changeset.py +62 -0
- lorebase_core/revision/revision.py +121 -0
- lorebase_core/tree/__init__.py +76 -0
- lorebase_core/tree/diff.py +119 -0
- lorebase_core/tree/hamt.py +333 -0
- lorebase_core/tree/node.py +280 -0
- lorebase_core/tree/params.py +33 -0
- lorebase_core/tree/root.py +70 -0
- lorebase_core/tree/store.py +98 -0
- lorebase_core/types/__init__.py +71 -0
- lorebase_core/types/casts.py +271 -0
- lorebase_core/types/constraints.py +299 -0
- lorebase_core/types/encoding.py +538 -0
- lorebase_core/types/errors.py +41 -0
- lorebase_core/types/matching.py +40 -0
- lorebase_core/types/types.py +187 -0
- lorebase_core/validate/__init__.py +49 -0
- lorebase_core/validate/changes.py +70 -0
- lorebase_core/validate/changeset.py +551 -0
- lorebase_core/validate/data_rules.py +291 -0
- lorebase_core/validate/issues.py +123 -0
- lorebase_core/validate/schema_rules.py +1304 -0
- lorebase_core-0.2.3.dist-info/METADATA +9 -0
- lorebase_core-0.2.3.dist-info/RECORD +94 -0
- lorebase_core-0.2.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Expression syntax: parse CEL source with cel-python and build a small typed-independent AST.
|
|
2
|
+
|
|
3
|
+
cel-python provides the grammar (the CEL specification's, as a Lark LALR parser) with
|
|
4
|
+
positions; the engine converts its parse tree into the nodes below so that checking and
|
|
5
|
+
evaluation do not depend on the parser's internals.
|
|
6
|
+
|
|
7
|
+
Supported syntax: literals (int, double, string, bytes, bool, null), identifiers, field
|
|
8
|
+
selection, indexing, global and method calls, list and map literals, unary ``-`` and ``!``,
|
|
9
|
+
arithmetic, comparisons, ``in``, ``&&``, ``||``, ``?:`` and the macros ``has``, ``all``,
|
|
10
|
+
``exists``, ``exists_one``, ``map`` (two arguments), ``filter`` and ``count``. Unsigned
|
|
11
|
+
integers and message construction (``Type{field: value}``) are not supported.
|
|
12
|
+
|
|
13
|
+
``$step[n]`` in Operation steps refers to the Record of an earlier step. ``$`` is not CEL
|
|
14
|
+
syntax, so it is rewritten to the reserved identifier ``_step`` before parsing (same length,
|
|
15
|
+
so positions are unchanged); text inside string literals is left alone.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
from celpy.celparser import CELParser
|
|
23
|
+
from celpy.evaluation import celbytes, celstr
|
|
24
|
+
from lark import Lark, Token, Tree
|
|
25
|
+
from lark.exceptions import (
|
|
26
|
+
LarkError,
|
|
27
|
+
UnexpectedCharacters,
|
|
28
|
+
UnexpectedEOF,
|
|
29
|
+
UnexpectedInput,
|
|
30
|
+
UnexpectedToken,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from lorebase_core.expr.errors import ExprError, Position, limit_error, syntax_error
|
|
34
|
+
from lorebase_core.expr.limits import ExprLimits
|
|
35
|
+
from lorebase_core.expr.types import (
|
|
36
|
+
BOOL,
|
|
37
|
+
BYTES,
|
|
38
|
+
DOUBLE,
|
|
39
|
+
INT,
|
|
40
|
+
NULL,
|
|
41
|
+
ExprType,
|
|
42
|
+
string_values,
|
|
43
|
+
)
|
|
44
|
+
from lorebase_core.types import INT64_MAX, INT64_MIN
|
|
45
|
+
|
|
46
|
+
#: Source form and identifier of step references (see the module docstring).
|
|
47
|
+
STEP_SYNTAX = "$step"
|
|
48
|
+
STEP_VARIABLE = "_step"
|
|
49
|
+
|
|
50
|
+
#: Comprehension macros: ``target.macro(variable, body)``.
|
|
51
|
+
MACROS = frozenset({"all", "exists", "exists_one", "map", "filter", "count"})
|
|
52
|
+
|
|
53
|
+
_PASS_THROUGH = frozenset(
|
|
54
|
+
{
|
|
55
|
+
"expr",
|
|
56
|
+
"conditionalor",
|
|
57
|
+
"conditionaland",
|
|
58
|
+
"relation",
|
|
59
|
+
"addition",
|
|
60
|
+
"multiplication",
|
|
61
|
+
"unary",
|
|
62
|
+
"member",
|
|
63
|
+
"primary",
|
|
64
|
+
"paren_expr",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
_RELATIONS = {
|
|
68
|
+
"relation_lt": "<",
|
|
69
|
+
"relation_le": "<=",
|
|
70
|
+
"relation_gt": ">",
|
|
71
|
+
"relation_ge": ">=",
|
|
72
|
+
"relation_eq": "==",
|
|
73
|
+
"relation_ne": "!=",
|
|
74
|
+
"relation_in": "in",
|
|
75
|
+
}
|
|
76
|
+
_ARITHMETIC = {
|
|
77
|
+
"addition_add": "+",
|
|
78
|
+
"addition_sub": "-",
|
|
79
|
+
"multiplication_mul": "*",
|
|
80
|
+
"multiplication_div": "/",
|
|
81
|
+
"multiplication_mod": "%",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
86
|
+
class Node:
|
|
87
|
+
pos: Position
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
91
|
+
class Literal(Node):
|
|
92
|
+
value: object
|
|
93
|
+
type: ExprType
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
97
|
+
class Ident(Node):
|
|
98
|
+
name: str
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
102
|
+
class Select(Node):
|
|
103
|
+
"""``operand.field``; with ``test`` it is ``has(operand.field)``."""
|
|
104
|
+
|
|
105
|
+
operand: Node
|
|
106
|
+
field: str
|
|
107
|
+
test: bool = False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
111
|
+
class Index(Node):
|
|
112
|
+
operand: Node
|
|
113
|
+
index: Node
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
117
|
+
class Call(Node):
|
|
118
|
+
"""``function(args)``, or ``target.function(args)`` when ``target`` is set."""
|
|
119
|
+
|
|
120
|
+
function: str
|
|
121
|
+
target: Node | None
|
|
122
|
+
args: tuple[Node, ...]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
126
|
+
class Comprehension(Node):
|
|
127
|
+
"""``target.macro(variable, body)``."""
|
|
128
|
+
|
|
129
|
+
macro: str
|
|
130
|
+
target: Node
|
|
131
|
+
variable: str
|
|
132
|
+
body: Node
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
136
|
+
class ListLit(Node):
|
|
137
|
+
items: tuple[Node, ...]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
141
|
+
class MapLit(Node):
|
|
142
|
+
entries: tuple[tuple[Node, Node], ...]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
146
|
+
class Unary(Node):
|
|
147
|
+
op: str
|
|
148
|
+
operand: Node
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
152
|
+
class Binary(Node):
|
|
153
|
+
op: str
|
|
154
|
+
left: Node
|
|
155
|
+
right: Node
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass(frozen=True, eq=False, slots=True)
|
|
159
|
+
class Conditional(Node):
|
|
160
|
+
condition: Node
|
|
161
|
+
then: Node
|
|
162
|
+
otherwise: Node
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def rewrite_step_references(source: str) -> str:
|
|
166
|
+
"""Replace ``$step`` outside string literals by ``_step`` (see the module docstring)."""
|
|
167
|
+
out: list[str] = []
|
|
168
|
+
index = 0
|
|
169
|
+
quote: str | None = None
|
|
170
|
+
length = len(source)
|
|
171
|
+
while index < length:
|
|
172
|
+
char = source[index]
|
|
173
|
+
if quote is not None:
|
|
174
|
+
if char == "\\":
|
|
175
|
+
out.append(source[index : index + 2])
|
|
176
|
+
index += 2
|
|
177
|
+
continue
|
|
178
|
+
if source.startswith(quote, index):
|
|
179
|
+
out.append(quote)
|
|
180
|
+
index += len(quote)
|
|
181
|
+
quote = None
|
|
182
|
+
continue
|
|
183
|
+
out.append(char)
|
|
184
|
+
index += 1
|
|
185
|
+
continue
|
|
186
|
+
if char in "\"'":
|
|
187
|
+
quote = char * 3 if source.startswith(char * 3, index) else char
|
|
188
|
+
out.append(quote)
|
|
189
|
+
index += len(quote)
|
|
190
|
+
continue
|
|
191
|
+
if source.startswith(STEP_SYNTAX, index):
|
|
192
|
+
out.append(STEP_VARIABLE)
|
|
193
|
+
index += len(STEP_SYNTAX)
|
|
194
|
+
continue
|
|
195
|
+
out.append(char)
|
|
196
|
+
index += 1
|
|
197
|
+
return "".join(out)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def parse(source: str, limits: ExprLimits) -> Node:
|
|
201
|
+
"""Parse ``source`` into an AST. Raises ``ExprError`` (``syntax`` or ``limit_exceeded``)."""
|
|
202
|
+
if len(source) > limits.max_source_length:
|
|
203
|
+
raise limit_error(
|
|
204
|
+
f"expression is longer than {limits.max_source_length} characters"
|
|
205
|
+
).located(source=source)
|
|
206
|
+
text = rewrite_step_references(source)
|
|
207
|
+
if not text.strip():
|
|
208
|
+
raise syntax_error("expression is empty").located(source=source)
|
|
209
|
+
try:
|
|
210
|
+
tree = _lark().parse(text)
|
|
211
|
+
except UnexpectedInput as error:
|
|
212
|
+
message, position = _syntax_problem(text, error)
|
|
213
|
+
raise syntax_error(message, position).located(source=source) from None
|
|
214
|
+
except LarkError as error:
|
|
215
|
+
raise syntax_error(f"syntax error: {error}").located(source=source) from None
|
|
216
|
+
except RecursionError:
|
|
217
|
+
raise limit_error(f"expression nests deeper than {limits.max_depth} levels").located(
|
|
218
|
+
source=source
|
|
219
|
+
) from None
|
|
220
|
+
try:
|
|
221
|
+
return _Builder(limits.max_depth).build(tree, 0, Position(1, 1))
|
|
222
|
+
except ExprError as error:
|
|
223
|
+
raise error.located(source=source) from None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _lark() -> Lark:
|
|
227
|
+
"""cel-python's CEL grammar (loaded once by its ``CELParser``); used directly so that
|
|
228
|
+
parse errors keep the offending token and parsing holds no per-call state."""
|
|
229
|
+
CELParser()
|
|
230
|
+
parser = CELParser.CEL_PARSER
|
|
231
|
+
assert parser is not None
|
|
232
|
+
return parser
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _end(text: str) -> Position:
|
|
236
|
+
lines = text.splitlines() or [""]
|
|
237
|
+
return Position(len(lines), len(lines[-1]) + 1)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _syntax_problem(text: str, error: UnexpectedInput) -> tuple[str, Position]:
|
|
241
|
+
if isinstance(error, UnexpectedEOF) or (
|
|
242
|
+
isinstance(error, UnexpectedToken) and error.token.type == "$END"
|
|
243
|
+
):
|
|
244
|
+
return "syntax error: unexpected end of expression", _end(text)
|
|
245
|
+
position = Position(error.line, error.column)
|
|
246
|
+
if isinstance(error, UnexpectedCharacters):
|
|
247
|
+
found = error.char
|
|
248
|
+
elif isinstance(error, UnexpectedToken):
|
|
249
|
+
found = str(error.token.value)
|
|
250
|
+
else:
|
|
251
|
+
found = text.splitlines()[error.line - 1][error.column - 1 :][:1]
|
|
252
|
+
if found == "$":
|
|
253
|
+
return f"syntax error: unexpected '$' ({STEP_SYNTAX}[n] is the only $ form)", position
|
|
254
|
+
return f"syntax error: unexpected {found!r}", position
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class _Builder:
|
|
258
|
+
"""Converts the cel-python parse tree into AST nodes, bounding the nesting depth."""
|
|
259
|
+
|
|
260
|
+
def __init__(self, max_depth: int) -> None:
|
|
261
|
+
self.max_depth = max_depth
|
|
262
|
+
|
|
263
|
+
def build(self, item: Tree[Token] | Token, depth: int, parent: Position) -> Node:
|
|
264
|
+
if isinstance(item, Token):
|
|
265
|
+
raise syntax_error(f"unexpected token {item.value!r}", _token_position(item, parent))
|
|
266
|
+
tree = item
|
|
267
|
+
while (
|
|
268
|
+
tree.data in _PASS_THROUGH
|
|
269
|
+
and len(tree.children) == 1
|
|
270
|
+
and isinstance(tree.children[0], Tree)
|
|
271
|
+
):
|
|
272
|
+
tree = tree.children[0]
|
|
273
|
+
position = _tree_position(tree, parent)
|
|
274
|
+
if depth >= self.max_depth:
|
|
275
|
+
raise limit_error(f"expression nests deeper than {self.max_depth} levels", position)
|
|
276
|
+
handler = getattr(self, f"_{tree.data}", None)
|
|
277
|
+
if handler is None:
|
|
278
|
+
raise syntax_error(f"unsupported syntax ({tree.data})", position)
|
|
279
|
+
node: Node = handler(tree, depth + 1, position)
|
|
280
|
+
return node
|
|
281
|
+
|
|
282
|
+
def _expr(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
283
|
+
condition, then, otherwise = tree.children
|
|
284
|
+
return Conditional(
|
|
285
|
+
pos,
|
|
286
|
+
self.build(condition, depth, pos),
|
|
287
|
+
self.build(then, depth, pos),
|
|
288
|
+
self.build(otherwise, depth, pos),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def _conditionalor(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
292
|
+
left, right = tree.children
|
|
293
|
+
return Binary(pos, "||", self.build(left, depth, pos), self.build(right, depth, pos))
|
|
294
|
+
|
|
295
|
+
def _conditionaland(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
296
|
+
left, right = tree.children
|
|
297
|
+
return Binary(pos, "&&", self.build(left, depth, pos), self.build(right, depth, pos))
|
|
298
|
+
|
|
299
|
+
def _operator(
|
|
300
|
+
self, tree: Tree[Token], depth: int, pos: Position, table: dict[str, str]
|
|
301
|
+
) -> Node:
|
|
302
|
+
operator, right = tree.children
|
|
303
|
+
assert isinstance(operator, Tree)
|
|
304
|
+
(left,) = operator.children
|
|
305
|
+
op = table[operator.data]
|
|
306
|
+
right_node = self.build(right, depth, pos)
|
|
307
|
+
return Binary(_tree_position(operator, pos), op, self.build(left, depth, pos), right_node)
|
|
308
|
+
|
|
309
|
+
def _relation(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
310
|
+
return self._operator(tree, depth, pos, _RELATIONS)
|
|
311
|
+
|
|
312
|
+
def _addition(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
313
|
+
return self._operator(tree, depth, pos, _ARITHMETIC)
|
|
314
|
+
|
|
315
|
+
def _multiplication(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
316
|
+
return self._operator(tree, depth, pos, _ARITHMETIC)
|
|
317
|
+
|
|
318
|
+
def _unary(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
319
|
+
operator, operand = tree.children
|
|
320
|
+
assert isinstance(operator, Tree)
|
|
321
|
+
op = "!" if operator.data == "unary_not" else "-"
|
|
322
|
+
return Unary(pos, op, self.build(operand, depth, pos))
|
|
323
|
+
|
|
324
|
+
def _member_dot(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
325
|
+
operand, name = tree.children
|
|
326
|
+
assert isinstance(name, Token)
|
|
327
|
+
return Select(_token_position(name, pos), self.build(operand, depth, pos), str(name.value))
|
|
328
|
+
|
|
329
|
+
def _member_dot_arg(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
330
|
+
operand, name, *rest = tree.children
|
|
331
|
+
assert isinstance(name, Token)
|
|
332
|
+
function = str(name.value)
|
|
333
|
+
name_pos = _token_position(name, pos)
|
|
334
|
+
target = self.build(operand, depth, pos)
|
|
335
|
+
args = self._arguments(rest, depth, pos)
|
|
336
|
+
if function in MACROS:
|
|
337
|
+
if len(args) != 2 or not isinstance(args[0], Ident):
|
|
338
|
+
raise syntax_error(
|
|
339
|
+
f"{function}() takes a variable name and an expression, "
|
|
340
|
+
f"for example list.{function}(x, x > 0)",
|
|
341
|
+
name_pos,
|
|
342
|
+
)
|
|
343
|
+
return Comprehension(name_pos, function, target, args[0].name, args[1])
|
|
344
|
+
return Call(name_pos, function, target, args)
|
|
345
|
+
|
|
346
|
+
def _member_index(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
347
|
+
operand, index = tree.children
|
|
348
|
+
return Index(pos, self.build(operand, depth, pos), self.build(index, depth, pos))
|
|
349
|
+
|
|
350
|
+
def _member_object(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
351
|
+
raise syntax_error("message construction (Type{field: value}) is not supported", pos)
|
|
352
|
+
|
|
353
|
+
def _dot_ident(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
354
|
+
return self._ident(tree, depth, pos)
|
|
355
|
+
|
|
356
|
+
def _dot_ident_arg(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
357
|
+
return self._ident_arg(tree, depth, pos)
|
|
358
|
+
|
|
359
|
+
def _ident(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
360
|
+
(name,) = tree.children
|
|
361
|
+
assert isinstance(name, Token)
|
|
362
|
+
return Ident(_token_position(name, pos), str(name.value))
|
|
363
|
+
|
|
364
|
+
def _ident_arg(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
365
|
+
name, *rest = tree.children
|
|
366
|
+
assert isinstance(name, Token)
|
|
367
|
+
function = str(name.value)
|
|
368
|
+
args = self._arguments(rest, depth, pos)
|
|
369
|
+
if function == "has":
|
|
370
|
+
if len(args) != 1 or not isinstance(args[0], Select) or args[0].test:
|
|
371
|
+
raise syntax_error("has() takes one field selection, for example has(x.f)", pos)
|
|
372
|
+
selection = args[0]
|
|
373
|
+
return Select(selection.pos, selection.operand, selection.field, test=True)
|
|
374
|
+
return Call(pos, function, None, args)
|
|
375
|
+
|
|
376
|
+
def _arguments(
|
|
377
|
+
self, rest: list[Tree[Token] | Token], depth: int, pos: Position
|
|
378
|
+
) -> tuple[Node, ...]:
|
|
379
|
+
if not rest:
|
|
380
|
+
return ()
|
|
381
|
+
(exprlist,) = rest
|
|
382
|
+
assert isinstance(exprlist, Tree)
|
|
383
|
+
return tuple(self.build(child, depth, pos) for child in exprlist.children)
|
|
384
|
+
|
|
385
|
+
def _list_lit(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
386
|
+
return ListLit(pos, self._arguments(list(tree.children), depth, pos))
|
|
387
|
+
|
|
388
|
+
def _map_lit(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
389
|
+
if not tree.children:
|
|
390
|
+
return MapLit(pos, ())
|
|
391
|
+
(inits,) = tree.children
|
|
392
|
+
assert isinstance(inits, Tree)
|
|
393
|
+
items = [self.build(child, depth, pos) for child in inits.children]
|
|
394
|
+
return MapLit(pos, tuple(zip(items[0::2], items[1::2], strict=True)))
|
|
395
|
+
|
|
396
|
+
def _literal(self, tree: Tree[Token], depth: int, pos: Position) -> Node:
|
|
397
|
+
(token,) = tree.children
|
|
398
|
+
assert isinstance(token, Token)
|
|
399
|
+
kind = token.type
|
|
400
|
+
text = str(token.value)
|
|
401
|
+
if kind == "INT_LIT":
|
|
402
|
+
negative = text.startswith("-")
|
|
403
|
+
digits = text.lstrip("-")
|
|
404
|
+
value = int(digits, 16) if digits[:2].lower() == "0x" else int(digits, 10)
|
|
405
|
+
value = -value if negative else value
|
|
406
|
+
if not INT64_MIN <= value <= INT64_MAX:
|
|
407
|
+
raise syntax_error(f"integer literal {text} is outside the int64 range", pos)
|
|
408
|
+
return Literal(pos, value, INT)
|
|
409
|
+
if kind == "UINT_LIT":
|
|
410
|
+
raise syntax_error("unsigned integers are not supported; use an int literal", pos)
|
|
411
|
+
if kind == "FLOAT_LIT":
|
|
412
|
+
return Literal(pos, float(text), DOUBLE)
|
|
413
|
+
if kind in ("STRING_LIT", "MLSTRING_LIT"):
|
|
414
|
+
try:
|
|
415
|
+
string = str(celstr(token))
|
|
416
|
+
except ValueError as error:
|
|
417
|
+
raise syntax_error(f"invalid string literal: {error}", pos) from None
|
|
418
|
+
return Literal(pos, string, string_values(frozenset({string}), enum=False))
|
|
419
|
+
if kind == "BYTES_LIT":
|
|
420
|
+
try:
|
|
421
|
+
data = bytes(celbytes(token))
|
|
422
|
+
except ValueError as error:
|
|
423
|
+
raise syntax_error(f"invalid bytes literal: {error}", pos) from None
|
|
424
|
+
return Literal(pos, data, BYTES)
|
|
425
|
+
if kind == "BOOL_LIT":
|
|
426
|
+
return Literal(pos, text == "true", BOOL)
|
|
427
|
+
if kind == "NULL_LIT":
|
|
428
|
+
return Literal(pos, None, NULL)
|
|
429
|
+
raise syntax_error(f"unsupported literal {text!r}", pos)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _tree_position(tree: Tree[Token], fallback: Position) -> Position:
|
|
433
|
+
line = getattr(tree.meta, "line", None)
|
|
434
|
+
column = getattr(tree.meta, "column", None)
|
|
435
|
+
if line is None or column is None:
|
|
436
|
+
return fallback
|
|
437
|
+
return Position(line, column)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _token_position(token: Token, fallback: Position) -> Position:
|
|
441
|
+
if token.line is None or token.column is None:
|
|
442
|
+
return fallback
|
|
443
|
+
return Position(token.line, token.column)
|