nadata 0.1.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.
- nadata/__init__.py +15 -0
- nadata/api.py +894 -0
- nadata/cli.py +445 -0
- nadata/console.py +486 -0
- nadata/core/__init__.py +0 -0
- nadata/core/database/__init__.py +38 -0
- nadata/core/database/builtins.py +87 -0
- nadata/core/database/database.py +958 -0
- nadata/core/database/dumper.py +285 -0
- nadata/core/database/entity_id.py +142 -0
- nadata/core/database/instance.py +706 -0
- nadata/core/database/network_index.py +1142 -0
- nadata/core/database/page_manager.py +175 -0
- nadata/core/database/persistence/__init__.py +28 -0
- nadata/core/database/persistence/binary_cells.py +1141 -0
- nadata/core/database/persistence/compaction.py +97 -0
- nadata/core/database/persistence/dumper.py +798 -0
- nadata/core/database/persistence/lazy_instances.py +1537 -0
- nadata/core/database/persistence/loader.py +424 -0
- nadata/core/database/persistence/record_store.py +228 -0
- nadata/core/database/persistence/serialization.py +605 -0
- nadata/core/database/query_engine.py +222 -0
- nadata/core/database/queryset.py +508 -0
- nadata/core/database/reconciliation.py +287 -0
- nadata/core/database/slotted_page.py +190 -0
- nadata/core/database/spatial_index.py +1080 -0
- nadata/core/expressions/__init__.py +44 -0
- nadata/core/expressions/autograd.py +2909 -0
- nadata/core/expressions/builtins.py +4673 -0
- nadata/core/expressions/expression.py +2658 -0
- nadata/core/expressions/io_builtins.py +972 -0
- nadata/core/expressions/scope.py +336 -0
- nadata/core/expressions/statement.py +953 -0
- nadata/core/struct/__init__.py +14 -0
- nadata/core/struct/struct.py +218 -0
- nadata/core/struct/struct_attribute.py +100 -0
- nadata/core/struct/struct_method.py +324 -0
- nadata/core/types/__init__.py +33 -0
- nadata/core/types/bits/__init__.py +0 -0
- nadata/core/types/bits/bits.py +686 -0
- nadata/core/types/home_types/__init__.py +19 -0
- nadata/core/types/home_types/color.py +102 -0
- nadata/core/types/home_types/coordinate.py +1008 -0
- nadata/core/types/home_types/datetime.py +362 -0
- nadata/core/types/home_types/geogrid.py +92 -0
- nadata/core/types/home_types/geometry.py +1223 -0
- nadata/core/types/home_types/graph.py +0 -0
- nadata/core/types/home_types/planet.py +359 -0
- nadata/core/types/home_types/sea_level.py +98 -0
- nadata/core/types/home_types/vec.py +0 -0
- nadata/core/types/types.py +1345 -0
- nadata/core/values/__init__.py +14 -0
- nadata/core/values/values.py +1568 -0
- nadata/parser/__init__.py +0 -0
- nadata/parser/expressions/__init__.py +6 -0
- nadata/parser/expressions/dependencies.py +318 -0
- nadata/parser/expressions/expression_parser.py +993 -0
- nadata/parser/expressions/splitter.py +98 -0
- nadata/parser/expressions/statement_parser.py +1425 -0
- nadata/parser/expressions/tokenizer.py +402 -0
- nadata/parser/expressions/type_parser.py +187 -0
- nadata/parser/nadata_loader/__init__.py +8 -0
- nadata/parser/nadata_loader/loader.py +951 -0
- nadata/parser/nastruct_parser/__init__.py +0 -0
- nadata/parser/nastruct_parser/declaration.py +105 -0
- nadata/parser/nastruct_parser/imports.py +164 -0
- nadata/parser/nastruct_parser/nastruct_parser.py +209 -0
- nadata/parser/nastruct_parser/parse_attributes.py +149 -0
- nadata/parser/nastruct_parser/parse_callables.py +436 -0
- nadata/parser/nastruct_parser/parse_expression.py +59 -0
- nadata/parser/nastruct_parser/parse_structs.py +206 -0
- nadata/parser/nastruct_parser/parser_state.py +84 -0
- nadata/parser/nastruct_parser/resolver.py +1412 -0
- nadata/parser/nastruct_parser/struct_body_state.py +50 -0
- nadata/parser/nastruct_parser/tmp_structs.py +156 -0
- nadata/parser/utils/__init__.py +20 -0
- nadata/parser/utils/brackets.py +252 -0
- nadata/parser/utils/comments_remove.py +149 -0
- nadata/parser/utils/identifiers.py +121 -0
- nadata/session.py +868 -0
- nadata-0.1.0.dist-info/METADATA +191 -0
- nadata-0.1.0.dist-info/RECORD +86 -0
- nadata-0.1.0.dist-info/WHEEL +5 -0
- nadata-0.1.0.dist-info/entry_points.txt +2 -0
- nadata-0.1.0.dist-info/licenses/LICENSE +21 -0
- nadata-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resolution phase for .nastruct parsing.
|
|
3
|
+
|
|
4
|
+
Converts the intermediate representation (TmpStruct / TmpFunction) into core
|
|
5
|
+
Struct, StructAttribute, and StructMethod objects registered in the database:
|
|
6
|
+
TmpType trees are resolved into Type instances, default expressions are
|
|
7
|
+
evaluated into typed values, and bodies are attached as statement blocks.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Import Modules
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
import re
|
|
13
|
+
from typing import Any, Callable, Optional, cast
|
|
14
|
+
|
|
15
|
+
from nadata.core.database.database import NaDataDatabase
|
|
16
|
+
from nadata.core.expressions.builtins import (
|
|
17
|
+
TENSOR_INIT_NAMES,
|
|
18
|
+
TensorInit,
|
|
19
|
+
builtin_type,
|
|
20
|
+
build_type_globals,
|
|
21
|
+
)
|
|
22
|
+
from nadata.core.expressions.expression import (
|
|
23
|
+
ArrayLiteralExpr,
|
|
24
|
+
BinaryOpExpr,
|
|
25
|
+
CallExpr,
|
|
26
|
+
DictLiteralExpr,
|
|
27
|
+
Expression,
|
|
28
|
+
IdentifierExpr,
|
|
29
|
+
IndexExpr,
|
|
30
|
+
LiteralExpr,
|
|
31
|
+
MemberAccessExpr,
|
|
32
|
+
SetLiteralExpr,
|
|
33
|
+
UnaryOpExpr,
|
|
34
|
+
)
|
|
35
|
+
from nadata.core.expressions.scope import ExecutionScope
|
|
36
|
+
from nadata.core.expressions.statement import (
|
|
37
|
+
BlockStmt,
|
|
38
|
+
BreakStmt,
|
|
39
|
+
ContinueStmt,
|
|
40
|
+
ForInStmt,
|
|
41
|
+
ForStmt,
|
|
42
|
+
IfStmt,
|
|
43
|
+
Statement,
|
|
44
|
+
VarDeclStmt,
|
|
45
|
+
WhileStmt,
|
|
46
|
+
)
|
|
47
|
+
from nadata.core.struct.struct import Struct
|
|
48
|
+
from nadata.core.struct.struct_attribute import StructAttribute
|
|
49
|
+
from nadata.core.struct.struct_method import StructMethod
|
|
50
|
+
from nadata.core.types.home_types.color import Color
|
|
51
|
+
from nadata.core.types.home_types.coordinate import Coordinate
|
|
52
|
+
from nadata.core.types.home_types.datetime import DateTime
|
|
53
|
+
from nadata.core.types.home_types.geometry import (
|
|
54
|
+
Polygon,
|
|
55
|
+
Polyline,
|
|
56
|
+
area,
|
|
57
|
+
circle,
|
|
58
|
+
contains,
|
|
59
|
+
ellipse,
|
|
60
|
+
perimeter,
|
|
61
|
+
regular_polygon,
|
|
62
|
+
)
|
|
63
|
+
from nadata.core.types.home_types.planet import Planet
|
|
64
|
+
from nadata.core.types.types import (
|
|
65
|
+
Type,
|
|
66
|
+
TypeInt,
|
|
67
|
+
TypeStr,
|
|
68
|
+
TypeBool,
|
|
69
|
+
TypeDateTime,
|
|
70
|
+
TypeColor,
|
|
71
|
+
TypeCoordinate,
|
|
72
|
+
TypePolyline,
|
|
73
|
+
TypePolygon,
|
|
74
|
+
TypePlanet,
|
|
75
|
+
TypeArray,
|
|
76
|
+
TypeDict,
|
|
77
|
+
TypeSet,
|
|
78
|
+
TypeQueue,
|
|
79
|
+
TypePriorityQueue,
|
|
80
|
+
SPATIAL_GRID_NAMES,
|
|
81
|
+
TypeSpatialGrid,
|
|
82
|
+
TypeGeoGrid,
|
|
83
|
+
TypeTensor,
|
|
84
|
+
flatten_nested_lists,
|
|
85
|
+
Int2,
|
|
86
|
+
Int4,
|
|
87
|
+
Int8,
|
|
88
|
+
Int16,
|
|
89
|
+
Int32,
|
|
90
|
+
Int64,
|
|
91
|
+
UInt2,
|
|
92
|
+
UInt4,
|
|
93
|
+
UInt8,
|
|
94
|
+
UInt16,
|
|
95
|
+
UInt32,
|
|
96
|
+
UInt64,
|
|
97
|
+
Float8_E4M3,
|
|
98
|
+
Float8_E5M2,
|
|
99
|
+
Float16,
|
|
100
|
+
BFloat16,
|
|
101
|
+
Float32,
|
|
102
|
+
Float64,
|
|
103
|
+
UFloat8_E4M3,
|
|
104
|
+
UFloat8_E5M2,
|
|
105
|
+
UFloat16,
|
|
106
|
+
UBFloat16,
|
|
107
|
+
UFloat32,
|
|
108
|
+
UFloat64,
|
|
109
|
+
)
|
|
110
|
+
from nadata.parser.expressions.type_parser import TmpType, TypeParser
|
|
111
|
+
from nadata.parser.nastruct_parser.parser_state import NaStructParserState
|
|
112
|
+
from nadata.core.values.values import ValueTensor
|
|
113
|
+
from nadata.parser.nastruct_parser.tmp_structs import TmpAttribute, TmpFunction, TmpStruct
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def build_builtin_types() -> dict[str, Type]:
|
|
117
|
+
"""
|
|
118
|
+
Builds the mapping of builtin type names to their Type instances.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
dict[str, Type]: Builtin type catalog keyed by source name.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"Str": TypeStr(),
|
|
126
|
+
"Bool": TypeBool(),
|
|
127
|
+
"DateTime": TypeDateTime(),
|
|
128
|
+
"Color": TypeColor(),
|
|
129
|
+
"Coordinate": TypeCoordinate(),
|
|
130
|
+
"Polyline": TypePolyline(),
|
|
131
|
+
"Polygon": TypePolygon(),
|
|
132
|
+
"Planet": TypePlanet(),
|
|
133
|
+
"Int2": Int2,
|
|
134
|
+
"Int4": Int4,
|
|
135
|
+
"Int8": Int8,
|
|
136
|
+
"Int16": Int16,
|
|
137
|
+
"Int32": Int32,
|
|
138
|
+
"Int64": Int64,
|
|
139
|
+
"UInt2": UInt2,
|
|
140
|
+
"UInt4": UInt4,
|
|
141
|
+
"UInt8": UInt8,
|
|
142
|
+
"UInt16": UInt16,
|
|
143
|
+
"UInt32": UInt32,
|
|
144
|
+
"UInt64": UInt64,
|
|
145
|
+
"Float8_E4M3": Float8_E4M3,
|
|
146
|
+
"Float8_E5M2": Float8_E5M2,
|
|
147
|
+
"Float16": Float16,
|
|
148
|
+
"BFloat16": BFloat16,
|
|
149
|
+
"Float32": Float32,
|
|
150
|
+
"Float64": Float64,
|
|
151
|
+
"UFloat8_E4M3": UFloat8_E4M3,
|
|
152
|
+
"UFloat8_E5M2": UFloat8_E5M2,
|
|
153
|
+
"UFloat16": UFloat16,
|
|
154
|
+
"UBFloat16": UBFloat16,
|
|
155
|
+
"UFloat32": UFloat32,
|
|
156
|
+
"UFloat64": UFloat64,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _self_member_names(expression: Expression) -> list[str]:
|
|
161
|
+
"""
|
|
162
|
+
Collects the member names accessed through `self` in an expression.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
expression (Expression): Parsed expression AST to analyze.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
list[str]: Member names in encounter order (duplicates kept).
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
names: list[str] = []
|
|
172
|
+
|
|
173
|
+
_walk_self_members(node=expression, names=names)
|
|
174
|
+
|
|
175
|
+
return names
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _walk_self_members(node: Expression, names: list[str]) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Recursively walks an AST collecting `self.<member>` references.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
node (Expression): Current AST node.
|
|
184
|
+
names (list[str]): Accumulator updated in place.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
# self.member (possibly chained: only the first hop counts)
|
|
188
|
+
if isinstance(node, MemberAccessExpr):
|
|
189
|
+
|
|
190
|
+
if isinstance(node.target, IdentifierExpr) and node.target.name == "self":
|
|
191
|
+
names.append(node.member)
|
|
192
|
+
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
_walk_self_members(node=node.target, names=names)
|
|
196
|
+
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
if isinstance(node, (LiteralExpr, IdentifierExpr)):
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
if isinstance(node, BinaryOpExpr):
|
|
203
|
+
_walk_self_members(node=node.left, names=names)
|
|
204
|
+
_walk_self_members(node=node.right, names=names)
|
|
205
|
+
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
if isinstance(node, UnaryOpExpr):
|
|
209
|
+
_walk_self_members(node=node.operand, names=names)
|
|
210
|
+
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
if isinstance(node, IndexExpr):
|
|
214
|
+
_walk_self_members(node=node.target, names=names)
|
|
215
|
+
|
|
216
|
+
for index_expr in node.indices:
|
|
217
|
+
_walk_self_members(node=index_expr, names=names)
|
|
218
|
+
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
if isinstance(node, CallExpr):
|
|
222
|
+
_walk_self_members(node=node.callee, names=names)
|
|
223
|
+
|
|
224
|
+
for argument in node.args:
|
|
225
|
+
_walk_self_members(node=argument, names=names)
|
|
226
|
+
|
|
227
|
+
for argument in node.kwargs.values():
|
|
228
|
+
_walk_self_members(node=argument, names=names)
|
|
229
|
+
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
if isinstance(node, (ArrayLiteralExpr, SetLiteralExpr)):
|
|
233
|
+
for element in node.elements:
|
|
234
|
+
_walk_self_members(node=element, names=names)
|
|
235
|
+
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
if isinstance(node, DictLiteralExpr):
|
|
239
|
+
for entry_key, entry_value in node.entries:
|
|
240
|
+
_walk_self_members(node=entry_value, names=names)
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class TypeResolver:
|
|
248
|
+
"""
|
|
249
|
+
Resolves TmpType trees into Type instances.
|
|
250
|
+
|
|
251
|
+
Handles builtin types, home types, generics (Array, Dict), and struct
|
|
252
|
+
types. Struct types may be resolved on demand through a provider
|
|
253
|
+
callback, allowing forward references regardless of declaration order.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def __init__(self) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Initializes the TypeResolver instance.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
self.builtin_types: dict[str, Type] = build_builtin_types()
|
|
262
|
+
self.struct_types: dict[str, Struct] = {}
|
|
263
|
+
|
|
264
|
+
# Optional callback resolving a struct name into its Struct object
|
|
265
|
+
# (set by the SchemaResolver to support forward references)
|
|
266
|
+
self.struct_provider: Optional[Callable[[str], Struct]] = None
|
|
267
|
+
|
|
268
|
+
def register_struct_type(self, struct: Struct) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Makes a resolved struct usable as an attribute / parameter type.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
struct (Struct): Resolved struct to register.
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
self.struct_types[struct.name] = struct
|
|
277
|
+
|
|
278
|
+
def resolve(self, tmp_type: TmpType) -> Type:
|
|
279
|
+
"""
|
|
280
|
+
Resolves a TmpType tree into a Type instance.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
tmp_type (TmpType): Parsed type tree to resolve.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Type: The resolved schema type.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
TypeError: On unknown types or invalid generic arity.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
# Non generic type: builtin, home, or struct
|
|
293
|
+
if not tmp_type.children:
|
|
294
|
+
|
|
295
|
+
if tmp_type.name in self.builtin_types:
|
|
296
|
+
return self.builtin_types[tmp_type.name]
|
|
297
|
+
|
|
298
|
+
if tmp_type.name in self.struct_types:
|
|
299
|
+
return cast(Type, self.struct_types[tmp_type.name])
|
|
300
|
+
|
|
301
|
+
# Resolve the struct on demand (raises if truly unknown);
|
|
302
|
+
# Struct duck types as Type through its check_value method
|
|
303
|
+
if self.struct_provider is not None:
|
|
304
|
+
return cast(Type, self.struct_provider(tmp_type.name))
|
|
305
|
+
|
|
306
|
+
raise TypeError(f"Unknown type '{tmp_type.name}'")
|
|
307
|
+
|
|
308
|
+
# GeoGrid takes an element type and an optional integer cell
|
|
309
|
+
# size in arc-seconds
|
|
310
|
+
if tmp_type.name == "GeoGrid":
|
|
311
|
+
if len(tmp_type.children) not in (1, 2):
|
|
312
|
+
raise TypeError(
|
|
313
|
+
"Type 'GeoGrid' takes an element type and an optional "
|
|
314
|
+
"cell size in arc-seconds"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
cell_arcsec: int = 30
|
|
318
|
+
|
|
319
|
+
if len(tmp_type.children) == 2:
|
|
320
|
+
size_child: TmpType = tmp_type.children[1]
|
|
321
|
+
|
|
322
|
+
if size_child.children or not size_child.name.isdigit():
|
|
323
|
+
raise TypeError(
|
|
324
|
+
f"The GeoGrid cell size must be an integer literal "
|
|
325
|
+
f"in arc-seconds, got '{size_child.name}'"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
cell_arcsec = int(size_child.name)
|
|
329
|
+
|
|
330
|
+
return TypeGeoGrid(
|
|
331
|
+
value_type=self.resolve(tmp_type=tmp_type.children[0]),
|
|
332
|
+
cell_arcsec=cell_arcsec,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Tensor takes an element type followed by dimension specifiers:
|
|
336
|
+
# positive integer literals or names of const integer attributes of
|
|
337
|
+
# the owning struct (validated in a post-pass once all attributes
|
|
338
|
+
# are resolved). Dimension children are NOT types.
|
|
339
|
+
if tmp_type.name == "Tensor":
|
|
340
|
+
if len(tmp_type.children) < 2:
|
|
341
|
+
raise TypeError(
|
|
342
|
+
"Type 'Tensor' takes an element type and at least one "
|
|
343
|
+
"dimension"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
dims: list[int | str] = []
|
|
347
|
+
|
|
348
|
+
for dim_child in tmp_type.children[1:]:
|
|
349
|
+
|
|
350
|
+
# Integer literal dimension
|
|
351
|
+
if dim_child.name.isdigit():
|
|
352
|
+
dims.append(int(dim_child.name))
|
|
353
|
+
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
# Symbolic dimension: a const attribute name of the owner
|
|
357
|
+
if dim_child.children:
|
|
358
|
+
raise TypeError(
|
|
359
|
+
f"Tensor dimensions must be integer literals or "
|
|
360
|
+
f"constant names, got '{dim_child.name}'"
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
dims.append(dim_child.name)
|
|
364
|
+
|
|
365
|
+
return TypeTensor(
|
|
366
|
+
value_type=self.resolve(tmp_type=tmp_type.children[0]),
|
|
367
|
+
dims=dims,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Resolve every type argument first
|
|
371
|
+
resolved_children: list[Type] = [
|
|
372
|
+
self.resolve(tmp_type=child) for child in tmp_type.children
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
# Array takes exactly one element type
|
|
376
|
+
if tmp_type.name == "Array":
|
|
377
|
+
if len(resolved_children) != 1:
|
|
378
|
+
raise TypeError("Type 'Array' takes exactly one type argument")
|
|
379
|
+
|
|
380
|
+
return TypeArray(value_type=resolved_children[0])
|
|
381
|
+
|
|
382
|
+
# Dict takes exactly a key type and a value type (Dict[K, V])
|
|
383
|
+
if tmp_type.name == "Dict":
|
|
384
|
+
if len(resolved_children) != 2:
|
|
385
|
+
raise TypeError("Type 'Dict' takes exactly two type arguments")
|
|
386
|
+
|
|
387
|
+
return TypeDict(
|
|
388
|
+
value_type=resolved_children[1],
|
|
389
|
+
key_type=resolved_children[0]
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Set takes exactly one element type
|
|
393
|
+
if tmp_type.name == "Set":
|
|
394
|
+
if len(resolved_children) != 1:
|
|
395
|
+
raise TypeError("Type 'Set' takes exactly one type argument")
|
|
396
|
+
|
|
397
|
+
return TypeSet(value_type=resolved_children[0])
|
|
398
|
+
|
|
399
|
+
# Queue takes exactly one element type
|
|
400
|
+
if tmp_type.name == "Queue":
|
|
401
|
+
if len(resolved_children) != 1:
|
|
402
|
+
raise TypeError("Type 'Queue' takes exactly one type argument")
|
|
403
|
+
|
|
404
|
+
return TypeQueue(value_type=resolved_children[0])
|
|
405
|
+
|
|
406
|
+
# Sparse spatial grids take exactly one cell type; the topology is
|
|
407
|
+
# carried by the type name (GridSquare2D / GridHex2D / GridCube3D)
|
|
408
|
+
if tmp_type.name in SPATIAL_GRID_NAMES:
|
|
409
|
+
if len(resolved_children) != 1:
|
|
410
|
+
raise TypeError(
|
|
411
|
+
f"Type '{tmp_type.name}' takes exactly one type argument"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
return TypeSpatialGrid(
|
|
415
|
+
value_type=resolved_children[0],
|
|
416
|
+
topology=SPATIAL_GRID_NAMES[tmp_type.name],
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# PriorityQueue takes an element type and an optional basic numeric
|
|
420
|
+
# priority type (Int64 default)
|
|
421
|
+
if tmp_type.name == "PriorityQueue":
|
|
422
|
+
if len(resolved_children) not in (1, 2):
|
|
423
|
+
raise TypeError(
|
|
424
|
+
"Type 'PriorityQueue' takes one or two type arguments"
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
priority_type: Type = (
|
|
428
|
+
resolved_children[1] if len(resolved_children) == 2 else Int64
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return TypePriorityQueue(
|
|
432
|
+
value_type=resolved_children[0],
|
|
433
|
+
priority_type=priority_type,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
raise TypeError(f"Unknown generic type '{tmp_type.name}'")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# Identifiers naming attributes, parameters, and variables must be
|
|
440
|
+
# lower_snake_case; upper camel case is reserved for types (structs and
|
|
441
|
+
# builtin type names)
|
|
442
|
+
NAME_PATTERN: re.Pattern[str] = re.compile(r"^[a-z_][a-z0-9_]*$")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# Names that produce pending tensor initializations (materialized
|
|
446
|
+
# against the target's declared type)
|
|
447
|
+
def _is_tensor_conversion_default(expression: Any) -> bool:
|
|
448
|
+
"""
|
|
449
|
+
Returns whether an expression is an array_to_tensor default call.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
expression (Any): Candidate default expression.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
bool: True for array_to_tensor(...) calls; the result carries
|
|
456
|
+
autograd flags that per-instance evaluation must preserve.
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
return (
|
|
460
|
+
isinstance(expression, CallExpr)
|
|
461
|
+
and isinstance(expression.callee, IdentifierExpr)
|
|
462
|
+
and expression.callee.name == "array_to_tensor"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _is_tensor_init_default(expression: Any) -> bool:
|
|
467
|
+
"""
|
|
468
|
+
Returns whether an expression is a bare tensor initializer call.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
expression (Any): Candidate default expression.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
bool: True for 'zeros()', 'fill(...)', 'eye()', and 'randn()'
|
|
475
|
+
calls.
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
isinstance(expression, CallExpr)
|
|
480
|
+
and isinstance(expression.callee, IdentifierExpr)
|
|
481
|
+
and expression.callee.name in TENSOR_INIT_NAMES
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _validate_identifier_name(name: str, kind: str, context: str) -> None:
|
|
486
|
+
"""
|
|
487
|
+
Rejects attribute, parameter, or variable names starting uppercase.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
name (str): The declared identifier name.
|
|
491
|
+
kind (str): Declaration kind for the error message (e.g.
|
|
492
|
+
'attribute', 'parameter', 'variable').
|
|
493
|
+
context (str): Where the declaration appears (e.g. struct name).
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
SyntaxError: If the name does not start with a lowercase letter
|
|
497
|
+
or underscore.
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
if not NAME_PATTERN.match(name):
|
|
501
|
+
raise SyntaxError(
|
|
502
|
+
f"{kind.capitalize()} '{name}' in '{context}' must not start "
|
|
503
|
+
f"with an uppercase letter (lower_snake_case is reserved for "
|
|
504
|
+
f"attributes, parameters, and variables; types are "
|
|
505
|
+
f"UpperCamelCase)"
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class SchemaResolver:
|
|
510
|
+
"""
|
|
511
|
+
Resolves the intermediate representation of a parsing session into the
|
|
512
|
+
database: structs, attributes, methods, and standalone functions.
|
|
513
|
+
"""
|
|
514
|
+
|
|
515
|
+
def __init__(self, parser_state: NaStructParserState) -> None:
|
|
516
|
+
"""
|
|
517
|
+
Initializes the SchemaResolver instance.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
parser_state (NaStructParserState): Parsing session to resolve.
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
self.parser_state: NaStructParserState = parser_state
|
|
524
|
+
self.database: NaDataDatabase = parser_state.database
|
|
525
|
+
self.type_resolver: TypeResolver = TypeResolver()
|
|
526
|
+
self.type_globals: dict[str, Any] = build_type_globals()
|
|
527
|
+
|
|
528
|
+
# Resolved structs by name (also detects parent / type cycles)
|
|
529
|
+
self.resolved_structs: dict[str, Struct] = {}
|
|
530
|
+
self.in_progress: set[str] = set()
|
|
531
|
+
|
|
532
|
+
# Let the type resolver resolve struct types on demand
|
|
533
|
+
self.type_resolver.struct_provider = self.resolve_struct_by_name
|
|
534
|
+
|
|
535
|
+
def resolve(self) -> NaDataDatabase:
|
|
536
|
+
"""
|
|
537
|
+
Resolves every tmp struct and function into the database.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
NaDataDatabase: The populated database.
|
|
541
|
+
"""
|
|
542
|
+
|
|
543
|
+
# Resolve structs (dependency order via recursive resolution)
|
|
544
|
+
for struct_name in self.parser_state.tmp_structs:
|
|
545
|
+
self.resolve_struct_by_name(name=struct_name)
|
|
546
|
+
|
|
547
|
+
# Resolve standalone functions (all struct types are available now)
|
|
548
|
+
for tmp_function in self.parser_state.tmp_functions.values():
|
|
549
|
+
self.database.register_function(
|
|
550
|
+
function=self.resolve_callable(tmp_function=tmp_function)
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
return self.database
|
|
554
|
+
|
|
555
|
+
def resolve_struct_by_name(self, name: str) -> Struct:
|
|
556
|
+
"""
|
|
557
|
+
Resolves a single struct by name, resolving its dependencies first.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
name (str): Name of the struct to resolve.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Struct: The resolved struct.
|
|
564
|
+
|
|
565
|
+
Raises:
|
|
566
|
+
TypeError: On unknown structs or inheritance cycles.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
# Already resolved
|
|
570
|
+
if name in self.resolved_structs:
|
|
571
|
+
return self.resolved_structs[name]
|
|
572
|
+
|
|
573
|
+
# Check the struct exists in the intermediate representation
|
|
574
|
+
if name not in self.parser_state.tmp_structs:
|
|
575
|
+
raise TypeError(f"Unknown struct or type '{name}'")
|
|
576
|
+
|
|
577
|
+
# Detect inheritance and type cycles
|
|
578
|
+
if name in self.in_progress:
|
|
579
|
+
raise TypeError(f"Circular struct dependency involving '{name}'")
|
|
580
|
+
|
|
581
|
+
self.in_progress.add(name)
|
|
582
|
+
|
|
583
|
+
tmp_struct: TmpStruct = self.parser_state.tmp_structs[name]
|
|
584
|
+
|
|
585
|
+
# Resolve the parent first (depth first)
|
|
586
|
+
parent: Optional[Struct] = (
|
|
587
|
+
self.resolve_struct_by_name(name=tmp_struct.struct_parent)
|
|
588
|
+
if tmp_struct.struct_parent is not None
|
|
589
|
+
else None
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# Merge mode: re-declaring an existing struct reuses its shell —
|
|
593
|
+
# the instance table, allocated TypeId, and all existing rows
|
|
594
|
+
# stay untouched; only the member set is updated
|
|
595
|
+
merge_existing: bool = (
|
|
596
|
+
self.parser_state.merge
|
|
597
|
+
and name in self.database.tables_by_struct_name
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if merge_existing:
|
|
601
|
+
|
|
602
|
+
# Track exactly which members this re-declaration carries,
|
|
603
|
+
# so schema pruning can drop members removed from the source
|
|
604
|
+
declared: set[str] = (
|
|
605
|
+
self.parser_state.redeclared_members.setdefault(name, set())
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
declared.update(tmp_struct.attributes.keys())
|
|
609
|
+
declared.update(tmp_struct.methods.keys())
|
|
610
|
+
|
|
611
|
+
struct: Struct = (
|
|
612
|
+
self.database.tables_by_struct_name[name].struct
|
|
613
|
+
if merge_existing
|
|
614
|
+
else Struct(name=name, parent=parent)
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
if not merge_existing:
|
|
618
|
+
|
|
619
|
+
# Register before attributes so self references (e.g. Array[Entity]
|
|
620
|
+
# inside Entity) resolve to this very struct object
|
|
621
|
+
self.resolved_structs[name] = struct
|
|
622
|
+
self.type_resolver.register_struct_type(struct=struct)
|
|
623
|
+
self.database.register_struct(struct=struct)
|
|
624
|
+
|
|
625
|
+
else:
|
|
626
|
+
|
|
627
|
+
# Merge mode: same reason as above - self references in the
|
|
628
|
+
# re-declared body must resolve to the existing struct shell
|
|
629
|
+
self.resolved_structs[name] = struct
|
|
630
|
+
self.type_resolver.register_struct_type(struct=struct)
|
|
631
|
+
|
|
632
|
+
# Resolve attributes and methods
|
|
633
|
+
for tmp_attribute in tmp_struct.attributes.values():
|
|
634
|
+
self.resolve_attribute(struct=struct, tmp_attribute=tmp_attribute)
|
|
635
|
+
|
|
636
|
+
for tmp_function in tmp_struct.methods.values():
|
|
637
|
+
|
|
638
|
+
resolved_method: StructMethod = self.resolve_callable(
|
|
639
|
+
tmp_function=tmp_function,
|
|
640
|
+
struct=struct,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
# Merge mode: a re-declared method replaces its previous body
|
|
644
|
+
if (
|
|
645
|
+
self.parser_state.merge
|
|
646
|
+
and resolved_method.name in struct.methods
|
|
647
|
+
):
|
|
648
|
+
struct.methods[resolved_method.name] = resolved_method
|
|
649
|
+
|
|
650
|
+
else:
|
|
651
|
+
struct.add_method(method=resolved_method)
|
|
652
|
+
|
|
653
|
+
# Validate symbolic tensor dimensions against the resolved attributes
|
|
654
|
+
self._validate_symbolic_dims(struct=struct)
|
|
655
|
+
|
|
656
|
+
# Validate derived const references against the resolved attributes
|
|
657
|
+
self._validate_derived_consts(struct=struct)
|
|
658
|
+
|
|
659
|
+
self.check_identifier_shadowing(tmp_struct=tmp_struct, struct=struct)
|
|
660
|
+
|
|
661
|
+
self.in_progress.discard(name)
|
|
662
|
+
|
|
663
|
+
return struct
|
|
664
|
+
|
|
665
|
+
def _validate_derived_consts(self, struct: Struct) -> None:
|
|
666
|
+
"""
|
|
667
|
+
Validates derived const expressions against the struct's attributes.
|
|
668
|
+
|
|
669
|
+
A derived const's expression may only reference `self.<member>`
|
|
670
|
+
members that name `const` attributes of the struct (any declared
|
|
671
|
+
attribute order). The expression is evaluated per instance at
|
|
672
|
+
construction in declaration order, so referencing a later-declared
|
|
673
|
+
derived const fails at creation time.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
struct (Struct): The resolved struct.
|
|
677
|
+
|
|
678
|
+
Raises:
|
|
679
|
+
TypeError: If a `self.<member>` reference names a missing or
|
|
680
|
+
non-const attribute.
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
for attribute in struct.get_all_attributes().values():
|
|
684
|
+
|
|
685
|
+
if attribute.default_expr is None:
|
|
686
|
+
continue
|
|
687
|
+
|
|
688
|
+
for member_name in _self_member_names(
|
|
689
|
+
expression=attribute.default_expr
|
|
690
|
+
):
|
|
691
|
+
referenced: Optional[StructAttribute] = struct.get_attribute(
|
|
692
|
+
name=member_name
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
if referenced is None:
|
|
696
|
+
raise TypeError(
|
|
697
|
+
f"Derived const '{struct.name}.{attribute.name}' "
|
|
698
|
+
f"references unknown attribute '{member_name}'"
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
if not referenced.is_const:
|
|
702
|
+
raise TypeError(
|
|
703
|
+
f"Derived const '{struct.name}.{attribute.name}' "
|
|
704
|
+
f"references non-const attribute '{member_name}'"
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
def check_identifier_shadowing(self, tmp_struct: TmpStruct, struct: Struct) -> None:
|
|
708
|
+
"""
|
|
709
|
+
Rejects method parameters or locals that shadow struct attributes.
|
|
710
|
+
|
|
711
|
+
Bare names in method bodies resolve to parameters, locals, and
|
|
712
|
+
globals only; attributes are reached through explicit 'self.'. A
|
|
713
|
+
parameter or local sharing a name with an attribute would silently
|
|
714
|
+
take precedence over it, so it is a resolution error.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
tmp_struct (TmpStruct): Intermediate struct with method bodies.
|
|
718
|
+
struct (Struct): Resolved struct owning the attributes.
|
|
719
|
+
|
|
720
|
+
Raises:
|
|
721
|
+
SyntaxError: If a method parameter or local variable name
|
|
722
|
+
collides with an attribute of the struct or its parents.
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
attribute_names: set[str] = set(struct.attributes.keys())
|
|
726
|
+
|
|
727
|
+
parent: Optional[Struct] = struct.parent
|
|
728
|
+
|
|
729
|
+
while parent is not None:
|
|
730
|
+
attribute_names.update(parent.attributes.keys())
|
|
731
|
+
parent = parent.parent
|
|
732
|
+
|
|
733
|
+
for tmp_function in tmp_struct.methods.values():
|
|
734
|
+
self._resolve_body_statement_types(
|
|
735
|
+
statements=tmp_function.fn_body,
|
|
736
|
+
fn_name=tmp_function.fn_name,
|
|
737
|
+
struct_name=struct.name,
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
declared_names: list[str] = list(tmp_function.fn_argument.keys())
|
|
741
|
+
|
|
742
|
+
for statement in tmp_function.fn_body:
|
|
743
|
+
self._collect_declared_names(statement=statement, names=declared_names)
|
|
744
|
+
|
|
745
|
+
for declared_name in declared_names:
|
|
746
|
+
|
|
747
|
+
# Naming rule: locals and loop variables are lower_snake_case
|
|
748
|
+
_validate_identifier_name(
|
|
749
|
+
name=declared_name,
|
|
750
|
+
kind="variable",
|
|
751
|
+
context=f"method '{tmp_function.fn_name}' of "
|
|
752
|
+
f"'{struct.name}'",
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
if declared_name in attribute_names:
|
|
756
|
+
raise SyntaxError(
|
|
757
|
+
f"'{declared_name}' in method '{tmp_function.fn_name}' "
|
|
758
|
+
f"of struct '{struct.name}' shadows an attribute: "
|
|
759
|
+
f"access attributes through 'self'"
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Loop variables must be fresh in their lexical scope
|
|
763
|
+
self._check_loop_variable_freshness(
|
|
764
|
+
statements=tmp_function.fn_body,
|
|
765
|
+
visible=set(tmp_function.fn_argument.keys()),
|
|
766
|
+
fn_name=tmp_function.fn_name,
|
|
767
|
+
struct_name=struct.name,
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
def _resolve_body_statement_types(
|
|
771
|
+
self,
|
|
772
|
+
statements: list[Statement],
|
|
773
|
+
fn_name: str,
|
|
774
|
+
struct_name: str,
|
|
775
|
+
) -> None:
|
|
776
|
+
"""
|
|
777
|
+
Resolves the raw type annotations inside a method body statement tree.
|
|
778
|
+
|
|
779
|
+
Covers typed local declarations and loop variables (recursing into
|
|
780
|
+
nested blocks, branches, and loop bodies).
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
statements (list[Statement]): Statement tree to resolve in place.
|
|
784
|
+
fn_name (str): Method name, for error messages.
|
|
785
|
+
struct_name (str): Struct name, for error messages.
|
|
786
|
+
|
|
787
|
+
Raises:
|
|
788
|
+
SyntaxError: If a type annotation cannot be resolved.
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
for statement in statements:
|
|
792
|
+
if isinstance(statement, VarDeclStmt) and statement.var_type is None:
|
|
793
|
+
if statement.var_type_name is not None:
|
|
794
|
+
statement.var_type = self._resolve_annotation(
|
|
795
|
+
type_name=statement.var_type_name,
|
|
796
|
+
fn_name=fn_name,
|
|
797
|
+
struct_name=struct_name,
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
elif isinstance(statement, ForInStmt) and statement.var_type is None:
|
|
801
|
+
if statement.var_type_name is not None:
|
|
802
|
+
statement.var_type = self._resolve_annotation(
|
|
803
|
+
type_name=statement.var_type_name,
|
|
804
|
+
fn_name=fn_name,
|
|
805
|
+
struct_name=struct_name,
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
self._resolve_body_statement_types(
|
|
809
|
+
statements=statement.body.statements,
|
|
810
|
+
fn_name=fn_name,
|
|
811
|
+
struct_name=struct_name,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
elif isinstance(statement, ForStmt):
|
|
815
|
+
self._resolve_body_statement_types(
|
|
816
|
+
statements=statement.init_statements,
|
|
817
|
+
fn_name=fn_name,
|
|
818
|
+
struct_name=struct_name,
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
self._resolve_body_statement_types(
|
|
822
|
+
statements=statement.body.statements,
|
|
823
|
+
fn_name=fn_name,
|
|
824
|
+
struct_name=struct_name,
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
elif isinstance(statement, WhileStmt):
|
|
828
|
+
self._resolve_body_statement_types(
|
|
829
|
+
statements=statement.body.statements,
|
|
830
|
+
fn_name=fn_name,
|
|
831
|
+
struct_name=struct_name,
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
elif isinstance(statement, IfStmt):
|
|
835
|
+
self._resolve_body_statement_types(
|
|
836
|
+
statements=statement.then_block.statements,
|
|
837
|
+
fn_name=fn_name,
|
|
838
|
+
struct_name=struct_name,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
if statement.else_block is not None:
|
|
842
|
+
self._resolve_body_statement_types(
|
|
843
|
+
statements=statement.else_block.statements,
|
|
844
|
+
fn_name=fn_name,
|
|
845
|
+
struct_name=struct_name,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
elif isinstance(statement, BlockStmt):
|
|
849
|
+
self._resolve_body_statement_types(
|
|
850
|
+
statements=statement.statements,
|
|
851
|
+
fn_name=fn_name,
|
|
852
|
+
struct_name=struct_name,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
def _resolve_annotation(self, type_name: str, fn_name: str, struct_name: str) -> Type:
|
|
856
|
+
"""
|
|
857
|
+
Resolves a raw type annotation from a method body.
|
|
858
|
+
|
|
859
|
+
Args:
|
|
860
|
+
type_name (str): Raw annotation text.
|
|
861
|
+
fn_name (str): Method name, for error messages.
|
|
862
|
+
struct_name (str): Struct name, for error messages.
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
Type: The resolved schema type.
|
|
866
|
+
|
|
867
|
+
Raises:
|
|
868
|
+
SyntaxError: If the annotation cannot be parsed or resolved.
|
|
869
|
+
"""
|
|
870
|
+
|
|
871
|
+
try:
|
|
872
|
+
tmp_type: TmpType = TypeParser.parse_text(text=type_name)
|
|
873
|
+
|
|
874
|
+
return self.type_resolver.resolve(tmp_type=tmp_type)
|
|
875
|
+
|
|
876
|
+
except SyntaxError as error:
|
|
877
|
+
raise SyntaxError(
|
|
878
|
+
f"Unknown type '{type_name}' for local in method '{fn_name}' "
|
|
879
|
+
f"of struct '{struct_name}': {error}"
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
def _check_loop_variable_freshness(
|
|
883
|
+
self,
|
|
884
|
+
statements: list[Statement],
|
|
885
|
+
visible: set[str],
|
|
886
|
+
fn_name: str,
|
|
887
|
+
struct_name: str,
|
|
888
|
+
) -> None:
|
|
889
|
+
"""
|
|
890
|
+
Rejects loop variables that shadow a name visible at the loop.
|
|
891
|
+
|
|
892
|
+
The walk is flow sensitive: only parameters and locals declared
|
|
893
|
+
lexically before the loop are in scope. Loop variables themselves go
|
|
894
|
+
out of scope after their loop, so sequential loops may reuse a name.
|
|
895
|
+
|
|
896
|
+
Args:
|
|
897
|
+
statements (list[Statement]): Statement sequence to walk.
|
|
898
|
+
visible (set[str]): Names visible at the current position.
|
|
899
|
+
fn_name (str): Method name, for error messages.
|
|
900
|
+
struct_name (str): Struct name, for error messages.
|
|
901
|
+
|
|
902
|
+
Raises:
|
|
903
|
+
SyntaxError: If a loop variable shadows a visible name.
|
|
904
|
+
"""
|
|
905
|
+
|
|
906
|
+
for statement in statements:
|
|
907
|
+
if isinstance(statement, VarDeclStmt):
|
|
908
|
+
visible.add(statement.name)
|
|
909
|
+
|
|
910
|
+
elif isinstance(statement, ForInStmt):
|
|
911
|
+
if statement.var_name in visible:
|
|
912
|
+
raise SyntaxError(
|
|
913
|
+
f"Loop variable '{statement.var_name}' in method "
|
|
914
|
+
f"'{fn_name}' of struct '{struct_name}' shadows an "
|
|
915
|
+
f"existing variable in scope"
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
self._check_loop_variable_freshness(
|
|
919
|
+
statements=statement.body.statements,
|
|
920
|
+
visible=visible | {statement.var_name},
|
|
921
|
+
fn_name=fn_name,
|
|
922
|
+
struct_name=struct_name,
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
elif isinstance(statement, ForStmt):
|
|
926
|
+
init_names: set[str] = {
|
|
927
|
+
init.name
|
|
928
|
+
for init in statement.init_statements
|
|
929
|
+
if isinstance(init, VarDeclStmt)
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
for init_name in init_names:
|
|
933
|
+
if init_name in visible:
|
|
934
|
+
raise SyntaxError(
|
|
935
|
+
f"Loop variable '{init_name}' in method "
|
|
936
|
+
f"'{fn_name}' of struct '{struct_name}' shadows an "
|
|
937
|
+
f"existing variable in scope"
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
self._check_loop_variable_freshness(
|
|
941
|
+
statements=statement.body.statements,
|
|
942
|
+
visible=visible | init_names,
|
|
943
|
+
fn_name=fn_name,
|
|
944
|
+
struct_name=struct_name,
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
elif isinstance(statement, WhileStmt):
|
|
948
|
+
self._check_loop_variable_freshness(
|
|
949
|
+
statements=statement.body.statements,
|
|
950
|
+
visible=set(visible),
|
|
951
|
+
fn_name=fn_name,
|
|
952
|
+
struct_name=struct_name,
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
elif isinstance(statement, IfStmt):
|
|
956
|
+
self._check_loop_variable_freshness(
|
|
957
|
+
statements=statement.then_block.statements,
|
|
958
|
+
visible=set(visible),
|
|
959
|
+
fn_name=fn_name,
|
|
960
|
+
struct_name=struct_name,
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
if statement.else_block is not None:
|
|
964
|
+
self._check_loop_variable_freshness(
|
|
965
|
+
statements=statement.else_block.statements,
|
|
966
|
+
visible=set(visible),
|
|
967
|
+
fn_name=fn_name,
|
|
968
|
+
struct_name=struct_name,
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
elif isinstance(statement, BlockStmt):
|
|
972
|
+
self._check_loop_variable_freshness(
|
|
973
|
+
statements=statement.statements,
|
|
974
|
+
visible=set(visible),
|
|
975
|
+
fn_name=fn_name,
|
|
976
|
+
struct_name=struct_name,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
def _collect_declared_names(self, statement: Statement, names: list[str]) -> None:
|
|
980
|
+
"""
|
|
981
|
+
Appends the names of variables declared by a statement tree.
|
|
982
|
+
|
|
983
|
+
Args:
|
|
984
|
+
statement (Statement): Statement to inspect (recursed into
|
|
985
|
+
blocks and branches).
|
|
986
|
+
names (list[str]): Accumulator for declared variable names.
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
if isinstance(statement, VarDeclStmt):
|
|
990
|
+
names.append(statement.name)
|
|
991
|
+
|
|
992
|
+
return
|
|
993
|
+
|
|
994
|
+
if isinstance(statement, ForInStmt):
|
|
995
|
+
names.append(statement.var_name)
|
|
996
|
+
|
|
997
|
+
for nested in statement.body.statements:
|
|
998
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
999
|
+
|
|
1000
|
+
return
|
|
1001
|
+
|
|
1002
|
+
if isinstance(statement, ForStmt):
|
|
1003
|
+
for init in statement.init_statements:
|
|
1004
|
+
self._collect_declared_names(statement=init, names=names)
|
|
1005
|
+
|
|
1006
|
+
for nested in statement.body.statements:
|
|
1007
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
1008
|
+
|
|
1009
|
+
return
|
|
1010
|
+
|
|
1011
|
+
if isinstance(statement, WhileStmt):
|
|
1012
|
+
for nested in statement.body.statements:
|
|
1013
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
1014
|
+
|
|
1015
|
+
return
|
|
1016
|
+
|
|
1017
|
+
if isinstance(statement, BlockStmt):
|
|
1018
|
+
for nested in statement.statements:
|
|
1019
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
1020
|
+
|
|
1021
|
+
return
|
|
1022
|
+
|
|
1023
|
+
if isinstance(statement, IfStmt):
|
|
1024
|
+
for nested in statement.then_block.statements:
|
|
1025
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
1026
|
+
|
|
1027
|
+
if statement.else_block is not None:
|
|
1028
|
+
for nested in statement.else_block.statements:
|
|
1029
|
+
self._collect_declared_names(statement=nested, names=names)
|
|
1030
|
+
|
|
1031
|
+
def resolve_attribute(
|
|
1032
|
+
self,
|
|
1033
|
+
struct: Struct,
|
|
1034
|
+
tmp_attribute: TmpAttribute
|
|
1035
|
+
) -> None:
|
|
1036
|
+
"""
|
|
1037
|
+
Resolves a single attribute declaration and adds it to the struct.
|
|
1038
|
+
|
|
1039
|
+
Args:
|
|
1040
|
+
struct (Struct): Owning struct (already registered).
|
|
1041
|
+
tmp_attribute (TmpAttribute): Attribute to resolve.
|
|
1042
|
+
"""
|
|
1043
|
+
|
|
1044
|
+
attr_type: Type = self.type_resolver.resolve(tmp_type=tmp_attribute.attribute_type)
|
|
1045
|
+
|
|
1046
|
+
# Naming rule: attributes are lower_snake_case
|
|
1047
|
+
_validate_identifier_name(
|
|
1048
|
+
name=tmp_attribute.attribute_name,
|
|
1049
|
+
kind="attribute",
|
|
1050
|
+
context=struct.name,
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
default_value: Any = None
|
|
1054
|
+
has_default: bool = False
|
|
1055
|
+
default_expr: Optional[Expression] = None
|
|
1056
|
+
|
|
1057
|
+
# A const default referencing `self` is a DERIVED constant: its
|
|
1058
|
+
# value is computed per instance from the other bound constants
|
|
1059
|
+
# (validated in the post-pass), not evaluated statically here
|
|
1060
|
+
if (
|
|
1061
|
+
tmp_attribute.attribute_default_value is not None
|
|
1062
|
+
and tmp_attribute.is_const
|
|
1063
|
+
and "self" in tmp_attribute.dependencies
|
|
1064
|
+
):
|
|
1065
|
+
default_expr = tmp_attribute.attribute_default_value
|
|
1066
|
+
has_default = True
|
|
1067
|
+
|
|
1068
|
+
# Tensor initializer defaults (zeros()/fill()/eye()) under a
|
|
1069
|
+
# tensor declaration evaluate per instance so symbolic dims
|
|
1070
|
+
# resolve from the bound constants
|
|
1071
|
+
elif (
|
|
1072
|
+
isinstance(attr_type, TypeTensor)
|
|
1073
|
+
and (
|
|
1074
|
+
_is_tensor_init_default(
|
|
1075
|
+
expression=tmp_attribute.attribute_default_value
|
|
1076
|
+
)
|
|
1077
|
+
or _is_tensor_conversion_default(
|
|
1078
|
+
expression=tmp_attribute.attribute_default_value
|
|
1079
|
+
)
|
|
1080
|
+
)
|
|
1081
|
+
):
|
|
1082
|
+
default_expr = tmp_attribute.attribute_default_value
|
|
1083
|
+
has_default = True
|
|
1084
|
+
|
|
1085
|
+
# Evaluate the default expression under the declared type
|
|
1086
|
+
elif tmp_attribute.attribute_default_value is not None:
|
|
1087
|
+
default_value = self.evaluate_default(
|
|
1088
|
+
expression=tmp_attribute.attribute_default_value,
|
|
1089
|
+
resolved_type=attr_type
|
|
1090
|
+
)
|
|
1091
|
+
has_default = True
|
|
1092
|
+
|
|
1093
|
+
# Merge mode: a re-declared attribute replaces its previous
|
|
1094
|
+
# definition in place (same semantics as persistence merge)
|
|
1095
|
+
resolved_attribute: StructAttribute = StructAttribute(
|
|
1096
|
+
name=tmp_attribute.attribute_name,
|
|
1097
|
+
attr_type=attr_type,
|
|
1098
|
+
default_value=default_value,
|
|
1099
|
+
has_default=has_default,
|
|
1100
|
+
is_const=tmp_attribute.is_const,
|
|
1101
|
+
default_expr=default_expr
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
if (
|
|
1105
|
+
self.parser_state.merge
|
|
1106
|
+
and tmp_attribute.attribute_name in struct.attributes
|
|
1107
|
+
):
|
|
1108
|
+
struct.attributes[tmp_attribute.attribute_name] = resolved_attribute
|
|
1109
|
+
|
|
1110
|
+
else:
|
|
1111
|
+
struct.add_attribute(attribute=resolved_attribute)
|
|
1112
|
+
|
|
1113
|
+
def _validate_symbolic_dims(self, struct: Struct) -> None:
|
|
1114
|
+
"""
|
|
1115
|
+
Validates symbolic tensor dimensions against the struct's attributes.
|
|
1116
|
+
|
|
1117
|
+
Every symbolic dimension must name a `const` attribute of the struct
|
|
1118
|
+
(or an inherited one) whose type is a basic integer; the attribute
|
|
1119
|
+
may be declared anywhere in the body regardless of ordering. Tensor
|
|
1120
|
+
attributes with a default value are additionally length-checked
|
|
1121
|
+
against the const defaults when all dims are bound by them.
|
|
1122
|
+
|
|
1123
|
+
Args:
|
|
1124
|
+
struct (Struct): The resolved struct.
|
|
1125
|
+
|
|
1126
|
+
Raises:
|
|
1127
|
+
TypeError: If a symbolic dimension has no matching const
|
|
1128
|
+
integer attribute, or a default payload length mismatches.
|
|
1129
|
+
"""
|
|
1130
|
+
|
|
1131
|
+
for attribute in struct.get_all_attributes().values():
|
|
1132
|
+
|
|
1133
|
+
if not isinstance(attribute.attr_type, TypeTensor):
|
|
1134
|
+
continue
|
|
1135
|
+
|
|
1136
|
+
tensor_type: TypeTensor = attribute.attr_type
|
|
1137
|
+
|
|
1138
|
+
# Resolve the concrete extents implied by const defaults
|
|
1139
|
+
bound: dict[str, int] = {}
|
|
1140
|
+
all_bound: bool = True
|
|
1141
|
+
|
|
1142
|
+
for dim_name in tensor_type.symbolic_dims():
|
|
1143
|
+
dim_attr: Optional[StructAttribute] = struct.get_attribute(
|
|
1144
|
+
name=dim_name
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
if dim_attr is None:
|
|
1148
|
+
raise TypeError(
|
|
1149
|
+
f"Tensor dimension '{dim_name}' on "
|
|
1150
|
+
f"'{struct.name}.{attribute.name}' does not match "
|
|
1151
|
+
f"any attribute of '{struct.name}'"
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
if not dim_attr.is_const:
|
|
1155
|
+
raise TypeError(
|
|
1156
|
+
f"Tensor dimension '{dim_name}' on "
|
|
1157
|
+
f"'{struct.name}.{attribute.name}' must reference "
|
|
1158
|
+
f"a 'const' attribute"
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
if not isinstance(dim_attr.attr_type, TypeInt):
|
|
1162
|
+
raise TypeError(
|
|
1163
|
+
f"Tensor dimension '{dim_name}' on "
|
|
1164
|
+
f"'{struct.name}.{attribute.name}' must reference "
|
|
1165
|
+
f"a basic integer const attribute, got "
|
|
1166
|
+
f"{repr(dim_attr.attr_type)}"
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
# Track default-bound extents for default validation
|
|
1170
|
+
if dim_attr.has_default and isinstance(dim_attr.default_value, int):
|
|
1171
|
+
bound[dim_name] = dim_attr.default_value
|
|
1172
|
+
|
|
1173
|
+
else:
|
|
1174
|
+
all_bound = False
|
|
1175
|
+
|
|
1176
|
+
# Validate a declared default payload when the shape is known
|
|
1177
|
+
if (
|
|
1178
|
+
attribute.has_default
|
|
1179
|
+
and tensor_type.is_symbolic
|
|
1180
|
+
and all_bound
|
|
1181
|
+
and isinstance(attribute.default_value, list)
|
|
1182
|
+
):
|
|
1183
|
+
expected: int = 1
|
|
1184
|
+
|
|
1185
|
+
for dim_value in tensor_type.dims:
|
|
1186
|
+
|
|
1187
|
+
if isinstance(dim_value, str):
|
|
1188
|
+
expected *= bound[dim_value]
|
|
1189
|
+
|
|
1190
|
+
else:
|
|
1191
|
+
expected *= dim_value
|
|
1192
|
+
|
|
1193
|
+
if len(attribute.default_value) != expected:
|
|
1194
|
+
raise TypeError(
|
|
1195
|
+
f"Default for '{struct.name}.{attribute.name}' has "
|
|
1196
|
+
f"{len(attribute.default_value)} elements but shape "
|
|
1197
|
+
f"{tensor_type.dims} with bound dims {bound} "
|
|
1198
|
+
f"requires {expected}"
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
def resolve_callable(
|
|
1202
|
+
self,
|
|
1203
|
+
tmp_function: TmpFunction,
|
|
1204
|
+
struct: Optional[Struct] = None
|
|
1205
|
+
) -> StructMethod:
|
|
1206
|
+
"""
|
|
1207
|
+
Resolves a method or standalone function declaration.
|
|
1208
|
+
|
|
1209
|
+
Symbolic tensor parameter dimensions validate against the struct's
|
|
1210
|
+
const attributes when a struct is given (methods); standalone
|
|
1211
|
+
functions and symbolic return types are rejected.
|
|
1212
|
+
|
|
1213
|
+
Args:
|
|
1214
|
+
tmp_function (TmpFunction): Callable to resolve.
|
|
1215
|
+
struct (Optional[Struct]): Owning struct for methods, None for
|
|
1216
|
+
standalone functions. Defaults to None.
|
|
1217
|
+
|
|
1218
|
+
Returns:
|
|
1219
|
+
StructMethod: The resolved callable.
|
|
1220
|
+
|
|
1221
|
+
Raises:
|
|
1222
|
+
TypeError: If a symbolic dim name has no matching const
|
|
1223
|
+
integer attribute, a standalone function declares symbolic
|
|
1224
|
+
tensor parameters, or the return type is symbolic.
|
|
1225
|
+
"""
|
|
1226
|
+
|
|
1227
|
+
# Resolve parameter types and keep default expressions lazy
|
|
1228
|
+
params: dict[str, Type] = {}
|
|
1229
|
+
default_param_exprs: dict[str, Expression] = {}
|
|
1230
|
+
|
|
1231
|
+
for parameter in tmp_function.fn_argument.values():
|
|
1232
|
+
# Naming rule: parameters are lower_snake_case
|
|
1233
|
+
_validate_identifier_name(
|
|
1234
|
+
name=parameter.attribute_name,
|
|
1235
|
+
kind="parameter",
|
|
1236
|
+
context=tmp_function.fn_name,
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
param_type: Type = self.type_resolver.resolve(
|
|
1240
|
+
tmp_type=parameter.attribute_type
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
params[parameter.attribute_name] = param_type
|
|
1244
|
+
|
|
1245
|
+
# Symbolic tensor parameters bind against the receiver consts
|
|
1246
|
+
if isinstance(param_type, TypeTensor) and param_type.is_symbolic:
|
|
1247
|
+
|
|
1248
|
+
if struct is None:
|
|
1249
|
+
raise TypeError(
|
|
1250
|
+
f"Function '@{tmp_function.fn_name}' parameter "
|
|
1251
|
+
f"'{parameter.attribute_name}' has symbolic tensor "
|
|
1252
|
+
f"dimensions; only struct methods have a receiver"
|
|
1253
|
+
)
|
|
1254
|
+
|
|
1255
|
+
for dim_name in param_type.symbolic_dims():
|
|
1256
|
+
dim_attr: Optional[StructAttribute] = struct.get_attribute(
|
|
1257
|
+
name=dim_name
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
if (
|
|
1261
|
+
dim_attr is None
|
|
1262
|
+
or not dim_attr.is_const
|
|
1263
|
+
or not isinstance(dim_attr.attr_type, TypeInt)
|
|
1264
|
+
):
|
|
1265
|
+
raise TypeError(
|
|
1266
|
+
f"Method '@{tmp_function.fn_name}' parameter "
|
|
1267
|
+
f"'{parameter.attribute_name}' uses tensor "
|
|
1268
|
+
f"dimension '{dim_name}' which is not a basic "
|
|
1269
|
+
f"integer const of '{struct.name}'"
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1272
|
+
if parameter.attribute_default_value is not None:
|
|
1273
|
+
default_param_exprs[parameter.attribute_name] = (
|
|
1274
|
+
parameter.attribute_default_value
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
return_type: Type = self.type_resolver.resolve(
|
|
1278
|
+
tmp_type=tmp_function.fn_output_type
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
if isinstance(return_type, TypeTensor) and return_type.is_symbolic:
|
|
1282
|
+
raise TypeError(
|
|
1283
|
+
f"Method '@{tmp_function.fn_name}' has a symbolic tensor "
|
|
1284
|
+
f"return type; symbolic dims are only supported on "
|
|
1285
|
+
f"parameters and attributes"
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
return StructMethod(
|
|
1289
|
+
name=tmp_function.fn_name,
|
|
1290
|
+
params=params,
|
|
1291
|
+
return_type=return_type,
|
|
1292
|
+
default_param_exprs=default_param_exprs,
|
|
1293
|
+
body_block=BlockStmt(statements=tmp_function.fn_body)
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
def evaluate_default(
|
|
1297
|
+
self,
|
|
1298
|
+
expression: Expression,
|
|
1299
|
+
resolved_type: Type
|
|
1300
|
+
) -> Any:
|
|
1301
|
+
"""
|
|
1302
|
+
Evaluates a default value expression under its declared type.
|
|
1303
|
+
|
|
1304
|
+
Empty collection literals get their declared type injected before
|
|
1305
|
+
evaluation, since their element type cannot be inferred.
|
|
1306
|
+
|
|
1307
|
+
Args:
|
|
1308
|
+
expression (Any): Default value expression AST.
|
|
1309
|
+
resolved_type (Type): Declared attribute type.
|
|
1310
|
+
|
|
1311
|
+
Returns:
|
|
1312
|
+
Any: The raw evaluated default value.
|
|
1313
|
+
|
|
1314
|
+
Raises:
|
|
1315
|
+
TypeError: If the evaluated value violates the declared type.
|
|
1316
|
+
"""
|
|
1317
|
+
|
|
1318
|
+
# Inject the declared type into empty collection literals
|
|
1319
|
+
if (
|
|
1320
|
+
isinstance(expression, ArrayLiteralExpr)
|
|
1321
|
+
and expression.declared_type is None
|
|
1322
|
+
and isinstance(resolved_type, TypeArray)
|
|
1323
|
+
):
|
|
1324
|
+
expression.declared_type = resolved_type
|
|
1325
|
+
|
|
1326
|
+
# An empty array literal under a tensor declaration is the empty
|
|
1327
|
+
# flat payload (symbolic shapes stay unvalidated until binding)
|
|
1328
|
+
if (
|
|
1329
|
+
isinstance(expression, ArrayLiteralExpr)
|
|
1330
|
+
and not expression.elements
|
|
1331
|
+
and isinstance(resolved_type, TypeTensor)
|
|
1332
|
+
):
|
|
1333
|
+
return []
|
|
1334
|
+
|
|
1335
|
+
if (
|
|
1336
|
+
isinstance(expression, DictLiteralExpr)
|
|
1337
|
+
and expression.declared_type is None
|
|
1338
|
+
and isinstance(resolved_type, TypeDict)
|
|
1339
|
+
):
|
|
1340
|
+
expression.declared_type = resolved_type
|
|
1341
|
+
|
|
1342
|
+
# A set declared with '{}' reuses the (otherwise dict) empty braces
|
|
1343
|
+
if (
|
|
1344
|
+
isinstance(expression, DictLiteralExpr)
|
|
1345
|
+
and not expression.entries
|
|
1346
|
+
and isinstance(resolved_type, TypeSet)
|
|
1347
|
+
):
|
|
1348
|
+
expression = SetLiteralExpr(elements=[], declared_type=resolved_type)
|
|
1349
|
+
|
|
1350
|
+
# Queues and sparse grids declared with '{}' take the empty-brace
|
|
1351
|
+
# form too
|
|
1352
|
+
if (
|
|
1353
|
+
isinstance(expression, DictLiteralExpr)
|
|
1354
|
+
and not expression.entries
|
|
1355
|
+
and isinstance(
|
|
1356
|
+
resolved_type,
|
|
1357
|
+
(TypeQueue, TypePriorityQueue, TypeSpatialGrid, TypeGeoGrid),
|
|
1358
|
+
)
|
|
1359
|
+
):
|
|
1360
|
+
expression.declared_type = resolved_type
|
|
1361
|
+
|
|
1362
|
+
if (
|
|
1363
|
+
isinstance(expression, SetLiteralExpr)
|
|
1364
|
+
and expression.declared_type is None
|
|
1365
|
+
and isinstance(resolved_type, TypeSet)
|
|
1366
|
+
):
|
|
1367
|
+
expression.declared_type = resolved_type
|
|
1368
|
+
|
|
1369
|
+
# Evaluate in a scope exposing the type constructors
|
|
1370
|
+
scope: ExecutionScope = ExecutionScope(globals_dict=self.type_globals)
|
|
1371
|
+
|
|
1372
|
+
value: Any = expression.evaluate(scope=scope)
|
|
1373
|
+
|
|
1374
|
+
# Pending tensor initializers are only valid under Tensor
|
|
1375
|
+
# declarations (tensor attributes intercept them earlier)
|
|
1376
|
+
if isinstance(value, TensorInit):
|
|
1377
|
+
raise TypeError(
|
|
1378
|
+
f"Tensor initializer must be assigned to a declared tensor, "
|
|
1379
|
+
f"got '{repr(resolved_type)}'"
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
raw_value: Any = value.get() if hasattr(value, "get") else value
|
|
1383
|
+
|
|
1384
|
+
# Nested array literals flatten into the flat tensor payload
|
|
1385
|
+
if isinstance(resolved_type, TypeTensor) and isinstance(raw_value, list):
|
|
1386
|
+
raw_value = flatten_nested_lists(raw=raw_value)
|
|
1387
|
+
|
|
1388
|
+
# A produced tensor value (e.g. array_to_tensor) is stored as-is:
|
|
1389
|
+
# re-wrapping would drop its autograd flags
|
|
1390
|
+
if isinstance(value, ValueTensor) and isinstance(resolved_type, TypeTensor):
|
|
1391
|
+
resolved_type.check_value(value=raw_value)
|
|
1392
|
+
|
|
1393
|
+
return value
|
|
1394
|
+
|
|
1395
|
+
# Validate against the declared type (Struct is duck typed as Type)
|
|
1396
|
+
resolved_type.check_value(value=raw_value)
|
|
1397
|
+
|
|
1398
|
+
return raw_value
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
def resolve_parser_state(parser_state: NaStructParserState) -> NaDataDatabase:
|
|
1402
|
+
"""
|
|
1403
|
+
Resolves a full parsing session into its database.
|
|
1404
|
+
|
|
1405
|
+
Args:
|
|
1406
|
+
parser_state (NaStructParserState): Parsing session to resolve.
|
|
1407
|
+
|
|
1408
|
+
Returns:
|
|
1409
|
+
NaDataDatabase: The populated database.
|
|
1410
|
+
"""
|
|
1411
|
+
|
|
1412
|
+
return SchemaResolver(parser_state=parser_state).resolve()
|