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,706 @@
|
|
|
1
|
+
"""
|
|
2
|
+
StructInstance runtime object for Nadata.
|
|
3
|
+
|
|
4
|
+
Represents an active instance of a Struct schema with typed attributes,
|
|
5
|
+
a globally unique EntityId, and bound method execution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import Modules
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from collections import deque
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from nadata.core.expressions.builtins import (
|
|
14
|
+
TensorInit,
|
|
15
|
+
build_type_globals,
|
|
16
|
+
materialize_tensor_init,
|
|
17
|
+
)
|
|
18
|
+
from nadata.core.expressions.expression import Expression
|
|
19
|
+
from nadata.core.expressions.scope import ExecutionScope
|
|
20
|
+
from nadata.core.types.types import (
|
|
21
|
+
TypeInt,
|
|
22
|
+
TypePriorityQueue,
|
|
23
|
+
TypeQueue,
|
|
24
|
+
TypeTensor,
|
|
25
|
+
flatten_nested_lists,
|
|
26
|
+
)
|
|
27
|
+
from nadata.core.values.values import (
|
|
28
|
+
Value,
|
|
29
|
+
ValueArray,
|
|
30
|
+
ValuePriorityQueue,
|
|
31
|
+
ValueQueue,
|
|
32
|
+
ValueTensor,
|
|
33
|
+
make_value,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
from nadata.core.values.values import Value, make_value
|
|
37
|
+
from nadata.core.struct.struct import Struct
|
|
38
|
+
from nadata.core.struct.struct_attribute import StructAttribute
|
|
39
|
+
from nadata.core.database.entity_id import EntityId
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class StructInstance:
|
|
43
|
+
"""
|
|
44
|
+
Runtime instance of a Struct with attributes and bound methods.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
struct (Struct): Schema struct definition.
|
|
48
|
+
entity_id (EntityId): Globally unique 64-bit entity handle.
|
|
49
|
+
attributes (dict[str, Value]): Stored attribute Values.
|
|
50
|
+
dirty (bool): True when the instance differs from its stored
|
|
51
|
+
(or not-yet-stored) state; set by mutation, cleared by the
|
|
52
|
+
flush write-back. New instances start dirty.
|
|
53
|
+
pinned (bool): True when explicitly kept resident; pinned
|
|
54
|
+
instances are never evicted from the decode cache.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
struct: Struct,
|
|
60
|
+
entity_id: EntityId,
|
|
61
|
+
attributes: Optional[dict[str, Value]] = None
|
|
62
|
+
) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Initializes the StructInstance.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
struct (Struct): Struct schema definition.
|
|
68
|
+
entity_id (EntityId): Globally unique 64-bit entity handle.
|
|
69
|
+
attributes (Optional[dict[str, Value]], optional): Attribute values. Defaults to None.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
self.struct: Struct = struct
|
|
73
|
+
self.entity_id: EntityId = entity_id
|
|
74
|
+
self.attributes: dict[str, Value] = attributes if attributes is not None else {}
|
|
75
|
+
self.dirty: bool = True
|
|
76
|
+
self.pinned: bool = False
|
|
77
|
+
self.nid: Optional[str] = None
|
|
78
|
+
|
|
79
|
+
# Owning table (set by StructTable.insert); maintained hash
|
|
80
|
+
# indexes update through it on writes
|
|
81
|
+
self.table_ref: Optional[Any] = None
|
|
82
|
+
|
|
83
|
+
def get_attribute(self, name: str) -> Value:
|
|
84
|
+
"""
|
|
85
|
+
Retrieves an attribute Value by name.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name (str): Attribute name.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Value: Attribute Value.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
AttributeError: If attribute is not found.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
if name in self.attributes:
|
|
98
|
+
return self.attributes[name]
|
|
99
|
+
|
|
100
|
+
# Check default value in struct schema definition
|
|
101
|
+
attr_def = self.struct.get_attribute(name=name)
|
|
102
|
+
|
|
103
|
+
if attr_def is not None and attr_def.has_default:
|
|
104
|
+
|
|
105
|
+
# Expression-backed defaults (tensor initializers, derived
|
|
106
|
+
# consts) materialize through their expression. Tensor results
|
|
107
|
+
# are cached so identity stays stable across reads: autograd
|
|
108
|
+
# flags, gradients, and version counters live on the tensor
|
|
109
|
+
# object itself.
|
|
110
|
+
if attr_def.default_expr is not None:
|
|
111
|
+
|
|
112
|
+
value: Value = self._evaluate_derived_default(
|
|
113
|
+
attr_def=attr_def
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if isinstance(attr_def.attr_type, TypeTensor):
|
|
117
|
+
self.attributes[name] = value
|
|
118
|
+
|
|
119
|
+
return value
|
|
120
|
+
|
|
121
|
+
if isinstance(attr_def.attr_type, TypeTensor):
|
|
122
|
+
|
|
123
|
+
self.attributes[name] = make_value(
|
|
124
|
+
value_type=attr_def.attr_type,
|
|
125
|
+
raw=attr_def.default_value
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return self.attributes[name]
|
|
129
|
+
|
|
130
|
+
return make_value(
|
|
131
|
+
value_type=attr_def.attr_type,
|
|
132
|
+
raw=attr_def.default_value
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
raise AttributeError(f"Instance of '{self.struct.name}' has no attribute '{name}'")
|
|
136
|
+
|
|
137
|
+
def ensure_attribute(self, name: str) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Materializes a lazily-defaulted attribute onto this instance.
|
|
140
|
+
|
|
141
|
+
Copies container defaults (lists, dicts) so mutation through an
|
|
142
|
+
assignment target never aliases the schema's shared default value.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
name (str): Attribute name.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if name in self.attributes:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
attr_def: Any = self.struct.get_attribute(name=name)
|
|
152
|
+
|
|
153
|
+
if attr_def is None:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
# Derived constants evaluate their expression against the bound
|
|
157
|
+
# constants (used when creation paths were bypassed)
|
|
158
|
+
if attr_def.default_expr is not None:
|
|
159
|
+
self.attributes[name] = self._evaluate_derived_default(
|
|
160
|
+
attr_def=attr_def
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if not attr_def.has_default:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
raw_default: Any = attr_def.default_value
|
|
169
|
+
|
|
170
|
+
# Copy container defaults to avoid mutating the schema's shared value
|
|
171
|
+
if isinstance(raw_default, (list, dict, set, deque)):
|
|
172
|
+
raw_default = raw_default.copy()
|
|
173
|
+
|
|
174
|
+
self.attributes[name] = make_value(
|
|
175
|
+
value_type=attr_def.attr_type,
|
|
176
|
+
raw=raw_default,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Stamp resolved extents onto symbolic tensor defaults when every
|
|
180
|
+
# constant has a schema default (otherwise extents stay unbound)
|
|
181
|
+
stamped: Value = self.attributes[name]
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
isinstance(attr_def.attr_type, TypeTensor)
|
|
185
|
+
and attr_def.attr_type.is_symbolic
|
|
186
|
+
and isinstance(stamped, ValueTensor)
|
|
187
|
+
):
|
|
188
|
+
self._stamp_default_dims(
|
|
189
|
+
tensor_type=attr_def.attr_type,
|
|
190
|
+
tensor_value=stamped,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def _stamp_default_dims(self, tensor_type: TypeTensor, tensor_value: ValueTensor) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Stamps concrete extents onto a symbolic tensor default value.
|
|
196
|
+
|
|
197
|
+
Extents resolve from the const attributes' schema defaults; values
|
|
198
|
+
already bound on the instance take precedence.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
tensor_type (TypeTensor): Declared tensor type.
|
|
202
|
+
tensor_value (ValueTensor): The freshly materialized value.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
extents: dict[str, int] = {}
|
|
206
|
+
|
|
207
|
+
for dim_name in tensor_type.symbolic_dims():
|
|
208
|
+
dim_attr: Optional[StructAttribute] = self.struct.get_attribute(
|
|
209
|
+
name=dim_name
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if dim_attr is None or not dim_attr.has_default:
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
self.ensure_attribute(name=dim_name)
|
|
216
|
+
|
|
217
|
+
bound_value: Optional[Value] = self.attributes.get(dim_name)
|
|
218
|
+
|
|
219
|
+
if bound_value is None:
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
raw_dim: Any = bound_value.get()
|
|
223
|
+
|
|
224
|
+
if isinstance(raw_dim, bool) or not isinstance(raw_dim, int):
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
extents[dim_name] = raw_dim
|
|
228
|
+
|
|
229
|
+
tensor_value.concrete_dims = [
|
|
230
|
+
dim if isinstance(dim, int) else extents[dim]
|
|
231
|
+
for dim in tensor_type.dims
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
def set_attribute(self, name: str, value: Value, allow_const: bool = False) -> None:
|
|
235
|
+
"""
|
|
236
|
+
Sets an attribute Value by name after verifying schema constraints.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
name (str): Attribute name.
|
|
240
|
+
value (Value): Value to store.
|
|
241
|
+
allow_const (bool): Whether constant attributes may be written
|
|
242
|
+
(creation-time binding only). Defaults to False.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
AttributeError: If attribute is not declared in struct.
|
|
246
|
+
TypeError: If value violates declared attribute type, the
|
|
247
|
+
attribute is immutable, or a symbolic tensor shape does not
|
|
248
|
+
match the instance's bound constants.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
attr_def = self.struct.get_attribute(name=name)
|
|
252
|
+
|
|
253
|
+
if attr_def is None:
|
|
254
|
+
raise AttributeError(
|
|
255
|
+
f"Cannot set undeclared attribute '{name}' on Struct '{self.struct.name}'"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Immutable after construction: binding happens in the constructor
|
|
259
|
+
if attr_def.is_const and not allow_const:
|
|
260
|
+
raise TypeError(
|
|
261
|
+
f"Cannot assign to immutable attribute '{name}' on Struct "
|
|
262
|
+
f"'{self.struct.name}': const attributes bind at construction"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Pending tensor initializers (zeros()/fill()/eye()) materialize
|
|
266
|
+
# against the declared type, resolving symbolic dims from the
|
|
267
|
+
# instance's bound constants
|
|
268
|
+
if isinstance(attr_def.attr_type, TypeTensor) and isinstance(value, TensorInit):
|
|
269
|
+
value = self._materialize_attribute_init(
|
|
270
|
+
tensor_type=attr_def.attr_type,
|
|
271
|
+
init=value,
|
|
272
|
+
attribute_name=name,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Flatten nested array literals into the flat row-major payload of
|
|
276
|
+
# a tensor declaration (shape checked by the type below)
|
|
277
|
+
if isinstance(attr_def.attr_type, TypeTensor) and isinstance(value, ValueArray):
|
|
278
|
+
value = make_value(
|
|
279
|
+
value_type=attr_def.attr_type,
|
|
280
|
+
raw=flatten_nested_lists(raw=value.get()),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Validate type
|
|
284
|
+
attr_def.attr_type.check_value(value=value.get())
|
|
285
|
+
|
|
286
|
+
# Symbolic tensor shapes validate against the instance's constants
|
|
287
|
+
if (
|
|
288
|
+
isinstance(attr_def.attr_type, TypeTensor)
|
|
289
|
+
and attr_def.attr_type.is_symbolic
|
|
290
|
+
):
|
|
291
|
+
self._validate_symbolic_shape(
|
|
292
|
+
tensor_type=attr_def.attr_type,
|
|
293
|
+
value=value,
|
|
294
|
+
attribute_name=name,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Re-wrap raw payloads passed under a queue declaration (e.g. a
|
|
298
|
+
# plain array literal in a constructor call)
|
|
299
|
+
if isinstance(attr_def.attr_type, (TypeQueue, TypePriorityQueue)) and not isinstance(
|
|
300
|
+
value, (ValueQueue, ValuePriorityQueue)
|
|
301
|
+
):
|
|
302
|
+
value = make_value(value_type=attr_def.attr_type, raw=value.get())
|
|
303
|
+
|
|
304
|
+
# Capture the previously indexed value before overwriting
|
|
305
|
+
tracks_index: bool = (
|
|
306
|
+
self.table_ref is not None and name in self.table_ref.indexes
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
old_indexed: Optional[Any] = None
|
|
310
|
+
|
|
311
|
+
if tracks_index and name in self.attributes:
|
|
312
|
+
old_indexed = self.attributes[name].get()
|
|
313
|
+
|
|
314
|
+
# Spatial indexes re-index on any write to their attribute
|
|
315
|
+
tracks_spatial: bool = (
|
|
316
|
+
self.table_ref is not None
|
|
317
|
+
and name in self.table_ref.spatial_indexes
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
self.attributes[name] = value
|
|
321
|
+
self.dirty = True
|
|
322
|
+
|
|
323
|
+
# Keep hash indexes consistent with the write
|
|
324
|
+
if tracks_index and self.table_ref is not None:
|
|
325
|
+
self.table_ref.index_update(
|
|
326
|
+
attribute=name,
|
|
327
|
+
old_raw=old_indexed,
|
|
328
|
+
new_raw=value.get(),
|
|
329
|
+
local_id=self.entity_id.local_id,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Keep spatial indexes consistent with the write
|
|
333
|
+
if tracks_spatial and self.table_ref is not None:
|
|
334
|
+
self.table_ref.spatial_index_update(
|
|
335
|
+
attribute=name,
|
|
336
|
+
local_id=self.entity_id.local_id,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Keep routing networks consistent with the write
|
|
340
|
+
if self.table_ref is not None and self.table_ref.networks:
|
|
341
|
+
for network in self.table_ref.networks:
|
|
342
|
+
network.on_update(instance=self)
|
|
343
|
+
|
|
344
|
+
def apply_upsert(self, values: dict[str, Value]) -> None:
|
|
345
|
+
"""
|
|
346
|
+
Re-binds creation attributes onto an existing (nid'd) instance.
|
|
347
|
+
|
|
348
|
+
Partial update semantics: passed attributes are re-written
|
|
349
|
+
(constants included — upsert is re-construction); attributes
|
|
350
|
+
not passed keep their current value. Derived constants cannot
|
|
351
|
+
be set directly, matching creation semantics.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
values (dict[str, Value]): Attribute values by name.
|
|
355
|
+
|
|
356
|
+
Raises:
|
|
357
|
+
AttributeError: If a name is not declared in the schema.
|
|
358
|
+
TypeError: If a derived constant is passed directly.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
for name, value in values.items():
|
|
362
|
+
|
|
363
|
+
attr_def: Optional[StructAttribute] = self.struct.get_attribute(
|
|
364
|
+
name=name
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if attr_def is None:
|
|
368
|
+
raise AttributeError(
|
|
369
|
+
f"Cannot set undeclared attribute '{name}' on Struct "
|
|
370
|
+
f"'{self.struct.name}'"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if attr_def.is_const and attr_def.default_expr is not None:
|
|
374
|
+
raise TypeError(
|
|
375
|
+
f"Cannot set '{name}' directly: it is "
|
|
376
|
+
f"derived from other constants"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
self.set_attribute(name=name, value=value, allow_const=True)
|
|
380
|
+
|
|
381
|
+
def set_creation_attributes(self, values: dict[str, Value]) -> None:
|
|
382
|
+
"""
|
|
383
|
+
Binds construction-time attributes, constants first.
|
|
384
|
+
|
|
385
|
+
Constants are written before everything else so symbolic tensor
|
|
386
|
+
shapes validate against already-bound extents regardless of the
|
|
387
|
+
order the arguments appear in the constructor call. Derived
|
|
388
|
+
constants (expression defaults referencing `self.<const>`) are
|
|
389
|
+
evaluated after the plain constants, in declaration order.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
values (dict[str, Value]): Attribute values by name.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
# Constants first (they seed the symbolic tensor shapes)
|
|
396
|
+
for const_name, const_value in values.items():
|
|
397
|
+
attr_def = self.struct.get_attribute(name=const_name)
|
|
398
|
+
|
|
399
|
+
if attr_def is not None and attr_def.is_const:
|
|
400
|
+
self.set_attribute(
|
|
401
|
+
name=const_name, value=const_value, allow_const=True
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Derived constants next (in declaration order; each may reference
|
|
405
|
+
# the constants bound above or earlier-derived ones)
|
|
406
|
+
for attr_def in self.struct.get_all_attributes().values():
|
|
407
|
+
|
|
408
|
+
if attr_def.is_const and attr_def.default_expr is not None:
|
|
409
|
+
|
|
410
|
+
if attr_def.name in values:
|
|
411
|
+
raise TypeError(
|
|
412
|
+
f"Cannot set '{attr_def.name}' directly: it is "
|
|
413
|
+
f"derived from other constants"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
self.set_attribute(
|
|
417
|
+
name=attr_def.name,
|
|
418
|
+
value=self._evaluate_derived_default(attr_def=attr_def),
|
|
419
|
+
allow_const=True,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# Everything else (unknown names raise in set_attribute)
|
|
423
|
+
for other_name, other_value in values.items():
|
|
424
|
+
attr_def = self.struct.get_attribute(name=other_name)
|
|
425
|
+
|
|
426
|
+
if attr_def is None or not attr_def.is_const:
|
|
427
|
+
self.set_attribute(name=other_name, value=other_value)
|
|
428
|
+
|
|
429
|
+
def _evaluate_derived_default(self, attr_def: StructAttribute) -> Value:
|
|
430
|
+
"""
|
|
431
|
+
Evaluates a derived const's expression against the bound constants.
|
|
432
|
+
|
|
433
|
+
The expression evaluates in a fresh scope exposing `self` and every
|
|
434
|
+
currently bound attribute value by name (so both `self.Dim / 2`
|
|
435
|
+
and bare `Dim / 2` forms work).
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
attr_def (StructAttribute): The derived const definition.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
Value: The evaluated value.
|
|
442
|
+
|
|
443
|
+
Raises:
|
|
444
|
+
TypeError: If a referenced constant is not yet bound.
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
parameters: dict[str, Value] = {}
|
|
448
|
+
|
|
449
|
+
for name, bound_value in self.attributes.items():
|
|
450
|
+
attr: Optional[StructAttribute] = self.struct.get_attribute(name=name)
|
|
451
|
+
|
|
452
|
+
if attr is not None and attr.is_const:
|
|
453
|
+
parameters[name] = bound_value
|
|
454
|
+
|
|
455
|
+
scope: ExecutionScope = ExecutionScope(
|
|
456
|
+
self_instance=self,
|
|
457
|
+
parameters=parameters,
|
|
458
|
+
globals_dict=build_type_globals(),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
expression: Optional[Expression] = attr_def.default_expr
|
|
462
|
+
assert expression is not None
|
|
463
|
+
|
|
464
|
+
value: Value = expression.evaluate(scope=scope)
|
|
465
|
+
|
|
466
|
+
# Pending tensor initializers materialize against the declared
|
|
467
|
+
# type (symbolic dims resolve from the bound constants)
|
|
468
|
+
if isinstance(attr_def.attr_type, TypeTensor) and isinstance(value, TensorInit):
|
|
469
|
+
return self._materialize_attribute_init(
|
|
470
|
+
tensor_type=attr_def.attr_type,
|
|
471
|
+
init=value,
|
|
472
|
+
attribute_name=attr_def.name,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Python's '/' yields floats; an integral float binding to a basic
|
|
476
|
+
# integer const coerces (2.0 -> 2). Non-integral values still fail
|
|
477
|
+
# the declared type check in set_attribute.
|
|
478
|
+
raw_value: Any = value.get()
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
isinstance(attr_def.attr_type, TypeInt)
|
|
482
|
+
and isinstance(raw_value, float)
|
|
483
|
+
and raw_value.is_integer()
|
|
484
|
+
):
|
|
485
|
+
return make_value(value_type=attr_def.attr_type, raw=int(raw_value))
|
|
486
|
+
|
|
487
|
+
return value
|
|
488
|
+
|
|
489
|
+
def _materialize_attribute_init(
|
|
490
|
+
self,
|
|
491
|
+
tensor_type: TypeTensor,
|
|
492
|
+
init: TensorInit,
|
|
493
|
+
attribute_name: str,
|
|
494
|
+
) -> ValueTensor:
|
|
495
|
+
"""
|
|
496
|
+
Materializes a pending tensor initializer for an attribute.
|
|
497
|
+
|
|
498
|
+
Symbolic dimensions resolve from the instance's bound constants
|
|
499
|
+
(schema defaults are materialized first).
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
tensor_type (TypeTensor): Declared tensor type.
|
|
503
|
+
init (TensorInit): Pending initialization request.
|
|
504
|
+
attribute_name (str): Attribute being set (for errors).
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
ValueTensor: The initialized tensor.
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
TypeError: If a symbolic dimension is not bound on this
|
|
511
|
+
instance.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
extents: dict[str, int] = {}
|
|
515
|
+
|
|
516
|
+
for dim_name in tensor_type.symbolic_dims():
|
|
517
|
+
|
|
518
|
+
# Materialize schema defaults of unbound constants
|
|
519
|
+
self.ensure_attribute(name=dim_name)
|
|
520
|
+
|
|
521
|
+
dim_value: Optional[Value] = self.attributes.get(dim_name)
|
|
522
|
+
|
|
523
|
+
if dim_value is None:
|
|
524
|
+
raise TypeError(
|
|
525
|
+
f"Cannot initialize '{attribute_name}': tensor dimension "
|
|
526
|
+
f"'{dim_name}' is not bound on this instance"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
raw_dim: Any = dim_value.get()
|
|
530
|
+
|
|
531
|
+
if isinstance(raw_dim, bool) or not isinstance(raw_dim, int):
|
|
532
|
+
raise TypeError(
|
|
533
|
+
f"Cannot initialize '{attribute_name}': tensor dimension "
|
|
534
|
+
f"'{dim_name}' must be an integer"
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
extents[dim_name] = raw_dim
|
|
538
|
+
|
|
539
|
+
return materialize_tensor_init(
|
|
540
|
+
init=init,
|
|
541
|
+
tensor_type=tensor_type,
|
|
542
|
+
extents=extents or None,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
def _validate_symbolic_shape(
|
|
546
|
+
self,
|
|
547
|
+
tensor_type: TypeTensor,
|
|
548
|
+
value: Value,
|
|
549
|
+
attribute_name: str,
|
|
550
|
+
) -> None:
|
|
551
|
+
"""
|
|
552
|
+
Validates a symbolic tensor payload against the instance constants.
|
|
553
|
+
|
|
554
|
+
Every symbolic dimension must be bound on this instance (explicitly
|
|
555
|
+
or through its schema default); the product of the resolved extents
|
|
556
|
+
must equal the payload length. On success the resolved shape is
|
|
557
|
+
stamped onto the value for shape() introspection.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
tensor_type (TypeTensor): Declared tensor type.
|
|
561
|
+
value (Value): The (flat) tensor value being stored.
|
|
562
|
+
attribute_name (str): Attribute being set (for error messages).
|
|
563
|
+
|
|
564
|
+
Raises:
|
|
565
|
+
TypeError: If a constant is unbound, non-integer, or the shape
|
|
566
|
+
mismatches the payload length.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
extents: dict[str, int] = {}
|
|
570
|
+
|
|
571
|
+
for dim_name in tensor_type.symbolic_dims():
|
|
572
|
+
|
|
573
|
+
# Materialize schema defaults of unbound constants
|
|
574
|
+
self.ensure_attribute(name=dim_name)
|
|
575
|
+
|
|
576
|
+
dim_value: Optional[Value] = self.attributes.get(dim_name)
|
|
577
|
+
|
|
578
|
+
if dim_value is None:
|
|
579
|
+
raise TypeError(
|
|
580
|
+
f"Cannot set '{attribute_name}': tensor dimension "
|
|
581
|
+
f"'{dim_name}' is not bound on this instance"
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
raw_dim: Any = dim_value.get()
|
|
585
|
+
|
|
586
|
+
if isinstance(raw_dim, bool) or not isinstance(raw_dim, int):
|
|
587
|
+
raise TypeError(
|
|
588
|
+
f"Cannot set '{attribute_name}': tensor dimension "
|
|
589
|
+
f"'{dim_name}' must be an integer, got "
|
|
590
|
+
f"{type(raw_dim).__name__}"
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
extents[dim_name] = raw_dim
|
|
594
|
+
|
|
595
|
+
expected: int = 1
|
|
596
|
+
|
|
597
|
+
for dim in tensor_type.dims:
|
|
598
|
+
|
|
599
|
+
if isinstance(dim, str):
|
|
600
|
+
expected *= extents[dim]
|
|
601
|
+
|
|
602
|
+
else:
|
|
603
|
+
expected *= dim
|
|
604
|
+
|
|
605
|
+
payload: list[Any] = value.get()
|
|
606
|
+
|
|
607
|
+
if len(payload) != expected:
|
|
608
|
+
raise TypeError(
|
|
609
|
+
f"Cannot set '{attribute_name}': shape "
|
|
610
|
+
f"{tensor_type.dims} with bound dims {extents} requires "
|
|
611
|
+
f"{expected} elements, got {len(payload)}"
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Stamp the resolved shape onto the value (used by shape())
|
|
615
|
+
if isinstance(value, ValueTensor):
|
|
616
|
+
value.concrete_dims = [
|
|
617
|
+
dim if isinstance(dim, int) else extents[dim]
|
|
618
|
+
for dim in tensor_type.dims
|
|
619
|
+
]
|
|
620
|
+
|
|
621
|
+
def call_method(
|
|
622
|
+
self,
|
|
623
|
+
name: str,
|
|
624
|
+
args: Optional[list[Any]] = None,
|
|
625
|
+
kwargs: Optional[dict[str, Any]] = None,
|
|
626
|
+
globals_dict: Optional[dict[str, Any]] = None
|
|
627
|
+
) -> Optional[Value]:
|
|
628
|
+
"""
|
|
629
|
+
Invokes a struct method bound to this instance.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
name (str): Method name.
|
|
633
|
+
args (Optional[list[Any]], optional): Positional arguments. Defaults to None.
|
|
634
|
+
kwargs (Optional[dict[str, Any]], optional): Keyword arguments. Defaults to None.
|
|
635
|
+
globals_dict (Optional[dict[str, Any]], optional): Global scope. Defaults to None.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Optional[Value]: Method execution return Value.
|
|
639
|
+
|
|
640
|
+
Raises:
|
|
641
|
+
AttributeError: If method is not declared on struct.
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
method = self.struct.get_method(name=name)
|
|
645
|
+
|
|
646
|
+
if method is None:
|
|
647
|
+
raise AttributeError(
|
|
648
|
+
f"Struct '{self.struct.name}' has no method '@{name}'"
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
resolved_args: list[Any] = args if args is not None else []
|
|
652
|
+
resolved_kwargs: dict[str, Any] = kwargs if kwargs is not None else {}
|
|
653
|
+
resolved_globals: dict[str, Any] = globals_dict if globals_dict is not None else {}
|
|
654
|
+
|
|
655
|
+
return method.execute(
|
|
656
|
+
self_instance=self,
|
|
657
|
+
args=resolved_args,
|
|
658
|
+
kwargs=resolved_kwargs,
|
|
659
|
+
globals_dict=resolved_globals
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def __getattr__(self, name: str) -> Any:
|
|
664
|
+
"""
|
|
665
|
+
Dynamic fallback to access attributes or bind methods directly.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
name (str): Member name.
|
|
669
|
+
|
|
670
|
+
Returns:
|
|
671
|
+
Any: Value or callable bound method.
|
|
672
|
+
|
|
673
|
+
Raises:
|
|
674
|
+
AttributeError: If member is not found.
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
if name in self.attributes:
|
|
678
|
+
return self.attributes[name]
|
|
679
|
+
|
|
680
|
+
# Check struct methods for direct python invocation
|
|
681
|
+
method = self.struct.get_method(name=name)
|
|
682
|
+
|
|
683
|
+
if method is not None:
|
|
684
|
+
return lambda *args, **kwargs: self.call_method(
|
|
685
|
+
name=name,
|
|
686
|
+
args=list(args),
|
|
687
|
+
kwargs=kwargs
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
# Check default attribute value
|
|
691
|
+
attr_def = self.struct.get_attribute(name=name)
|
|
692
|
+
|
|
693
|
+
if attr_def is not None and attr_def.has_default:
|
|
694
|
+
return self.get_attribute(name=name)
|
|
695
|
+
|
|
696
|
+
raise AttributeError(f"'{self.struct.name}' instance has no member '{name}'")
|
|
697
|
+
|
|
698
|
+
def __repr__(self) -> str:
|
|
699
|
+
"""
|
|
700
|
+
Returns string representation of StructInstance.
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
str: Struct name with entity_id.
|
|
704
|
+
"""
|
|
705
|
+
|
|
706
|
+
return f"<{self.struct.name} id={self.entity_id.local_id}>"
|