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.
Files changed (86) hide show
  1. nadata/__init__.py +15 -0
  2. nadata/api.py +894 -0
  3. nadata/cli.py +445 -0
  4. nadata/console.py +486 -0
  5. nadata/core/__init__.py +0 -0
  6. nadata/core/database/__init__.py +38 -0
  7. nadata/core/database/builtins.py +87 -0
  8. nadata/core/database/database.py +958 -0
  9. nadata/core/database/dumper.py +285 -0
  10. nadata/core/database/entity_id.py +142 -0
  11. nadata/core/database/instance.py +706 -0
  12. nadata/core/database/network_index.py +1142 -0
  13. nadata/core/database/page_manager.py +175 -0
  14. nadata/core/database/persistence/__init__.py +28 -0
  15. nadata/core/database/persistence/binary_cells.py +1141 -0
  16. nadata/core/database/persistence/compaction.py +97 -0
  17. nadata/core/database/persistence/dumper.py +798 -0
  18. nadata/core/database/persistence/lazy_instances.py +1537 -0
  19. nadata/core/database/persistence/loader.py +424 -0
  20. nadata/core/database/persistence/record_store.py +228 -0
  21. nadata/core/database/persistence/serialization.py +605 -0
  22. nadata/core/database/query_engine.py +222 -0
  23. nadata/core/database/queryset.py +508 -0
  24. nadata/core/database/reconciliation.py +287 -0
  25. nadata/core/database/slotted_page.py +190 -0
  26. nadata/core/database/spatial_index.py +1080 -0
  27. nadata/core/expressions/__init__.py +44 -0
  28. nadata/core/expressions/autograd.py +2909 -0
  29. nadata/core/expressions/builtins.py +4673 -0
  30. nadata/core/expressions/expression.py +2658 -0
  31. nadata/core/expressions/io_builtins.py +972 -0
  32. nadata/core/expressions/scope.py +336 -0
  33. nadata/core/expressions/statement.py +953 -0
  34. nadata/core/struct/__init__.py +14 -0
  35. nadata/core/struct/struct.py +218 -0
  36. nadata/core/struct/struct_attribute.py +100 -0
  37. nadata/core/struct/struct_method.py +324 -0
  38. nadata/core/types/__init__.py +33 -0
  39. nadata/core/types/bits/__init__.py +0 -0
  40. nadata/core/types/bits/bits.py +686 -0
  41. nadata/core/types/home_types/__init__.py +19 -0
  42. nadata/core/types/home_types/color.py +102 -0
  43. nadata/core/types/home_types/coordinate.py +1008 -0
  44. nadata/core/types/home_types/datetime.py +362 -0
  45. nadata/core/types/home_types/geogrid.py +92 -0
  46. nadata/core/types/home_types/geometry.py +1223 -0
  47. nadata/core/types/home_types/graph.py +0 -0
  48. nadata/core/types/home_types/planet.py +359 -0
  49. nadata/core/types/home_types/sea_level.py +98 -0
  50. nadata/core/types/home_types/vec.py +0 -0
  51. nadata/core/types/types.py +1345 -0
  52. nadata/core/values/__init__.py +14 -0
  53. nadata/core/values/values.py +1568 -0
  54. nadata/parser/__init__.py +0 -0
  55. nadata/parser/expressions/__init__.py +6 -0
  56. nadata/parser/expressions/dependencies.py +318 -0
  57. nadata/parser/expressions/expression_parser.py +993 -0
  58. nadata/parser/expressions/splitter.py +98 -0
  59. nadata/parser/expressions/statement_parser.py +1425 -0
  60. nadata/parser/expressions/tokenizer.py +402 -0
  61. nadata/parser/expressions/type_parser.py +187 -0
  62. nadata/parser/nadata_loader/__init__.py +8 -0
  63. nadata/parser/nadata_loader/loader.py +951 -0
  64. nadata/parser/nastruct_parser/__init__.py +0 -0
  65. nadata/parser/nastruct_parser/declaration.py +105 -0
  66. nadata/parser/nastruct_parser/imports.py +164 -0
  67. nadata/parser/nastruct_parser/nastruct_parser.py +209 -0
  68. nadata/parser/nastruct_parser/parse_attributes.py +149 -0
  69. nadata/parser/nastruct_parser/parse_callables.py +436 -0
  70. nadata/parser/nastruct_parser/parse_expression.py +59 -0
  71. nadata/parser/nastruct_parser/parse_structs.py +206 -0
  72. nadata/parser/nastruct_parser/parser_state.py +84 -0
  73. nadata/parser/nastruct_parser/resolver.py +1412 -0
  74. nadata/parser/nastruct_parser/struct_body_state.py +50 -0
  75. nadata/parser/nastruct_parser/tmp_structs.py +156 -0
  76. nadata/parser/utils/__init__.py +20 -0
  77. nadata/parser/utils/brackets.py +252 -0
  78. nadata/parser/utils/comments_remove.py +149 -0
  79. nadata/parser/utils/identifiers.py +121 -0
  80. nadata/session.py +868 -0
  81. nadata-0.1.0.dist-info/METADATA +191 -0
  82. nadata-0.1.0.dist-info/RECORD +86 -0
  83. nadata-0.1.0.dist-info/WHEEL +5 -0
  84. nadata-0.1.0.dist-info/entry_points.txt +2 -0
  85. nadata-0.1.0.dist-info/licenses/LICENSE +21 -0
  86. 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()