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,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Callable definition parsing for .nastruct schema files.
|
|
3
|
+
|
|
4
|
+
Provides the parsers for struct method declarations and standalone function
|
|
5
|
+
declarations, which share the same 'name(params) -> ReturnType' header grammar.
|
|
6
|
+
Bodies are stored as raw text spans; statement parsing happens in the
|
|
7
|
+
resolution phase.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Import Modules
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from nadata.core.expressions.expression import Expression
|
|
15
|
+
from nadata.core.expressions.statement import Statement
|
|
16
|
+
from nadata.parser.expressions.dependencies import (
|
|
17
|
+
collect_declared_names,
|
|
18
|
+
collect_dependencies,
|
|
19
|
+
collect_statement_dependencies,
|
|
20
|
+
)
|
|
21
|
+
from nadata.parser.expressions.expression_parser import ExpressionParser
|
|
22
|
+
from nadata.parser.expressions.splitter import split_top_level
|
|
23
|
+
from nadata.parser.expressions.statement_parser import StatementParser
|
|
24
|
+
from nadata.parser.expressions.tokenizer import Token, TokenType, Tokenizer
|
|
25
|
+
from nadata.parser.expressions.type_parser import TmpType, TypeParser
|
|
26
|
+
from nadata.parser.utils.brackets import BracketTree
|
|
27
|
+
from nadata.parser.utils.identifiers import IdentifierExtractor
|
|
28
|
+
from nadata.parser.nastruct_parser.declaration import parse_declaration_text
|
|
29
|
+
from nadata.parser.nastruct_parser.tmp_structs import TmpAttribute, TmpFunction, TmpStruct
|
|
30
|
+
from nadata.parser.nastruct_parser.parser_state import NaStructParserState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_struct_method(
|
|
34
|
+
parser_state: NaStructParserState,
|
|
35
|
+
struct_name: str,
|
|
36
|
+
struct_method_name: str,
|
|
37
|
+
header: str,
|
|
38
|
+
method_start: int,
|
|
39
|
+
method_end: int,
|
|
40
|
+
content: str,
|
|
41
|
+
brackets_inside: list[BracketTree]
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Parses a struct method definition block.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
parser_state (NaStructParserState): Shared parsing session state.
|
|
48
|
+
struct_name (str): Name of the struct owning the method.
|
|
49
|
+
struct_method_name (str): Name of the method (without '@').
|
|
50
|
+
header (str): Full header text between '@' and the opening brace
|
|
51
|
+
(e.g. 'age(now: DateTime) -> DateTime').
|
|
52
|
+
method_start (int): Index of the first content character inside the braces.
|
|
53
|
+
method_end (int): Index of the closing brace of the method body.
|
|
54
|
+
content (str): Text of the file.
|
|
55
|
+
brackets_inside (list[Token]): Nested bracket trees in the body.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Parse the shared header grammar (parameters and return type)
|
|
59
|
+
fn_arguments: dict[str, TmpAttribute]
|
|
60
|
+
fn_output_type: TmpType
|
|
61
|
+
fn_arguments, fn_output_type = _parse_callable_header(header_text=header)
|
|
62
|
+
|
|
63
|
+
# Parse the raw body text into statement AST nodes
|
|
64
|
+
fn_body: list[Statement] = StatementParser.parse_body(
|
|
65
|
+
text=content[method_start:method_end]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Check the owning struct exists in the intermediate representation
|
|
69
|
+
tmp_struct: Optional[TmpStruct] = parser_state.tmp_structs.get(struct_name)
|
|
70
|
+
|
|
71
|
+
if tmp_struct is None:
|
|
72
|
+
raise SyntaxError(f"Method '{struct_method_name}' refers to unknown struct '{struct_name}'")
|
|
73
|
+
|
|
74
|
+
# Check the method is not already declared on the struct
|
|
75
|
+
# (merge mode allows re-declaration: the resolved body replaces)
|
|
76
|
+
if (
|
|
77
|
+
struct_method_name in tmp_struct.methods
|
|
78
|
+
and not parser_state.merge
|
|
79
|
+
):
|
|
80
|
+
raise SyntaxError(
|
|
81
|
+
f"Method '{struct_method_name}' is already defined on struct '{struct_name}'"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Register the method in the tmp struct
|
|
85
|
+
tmp_struct.methods[struct_method_name] = TmpFunction(
|
|
86
|
+
fn_name=struct_method_name,
|
|
87
|
+
fn_output_type=fn_output_type,
|
|
88
|
+
fn_argument=fn_arguments,
|
|
89
|
+
fn_body=fn_body,
|
|
90
|
+
dependencies=_collect_callable_dependencies(
|
|
91
|
+
fn_arguments=fn_arguments,
|
|
92
|
+
fn_output_type=fn_output_type,
|
|
93
|
+
fn_body=fn_body
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def parse_fn(
|
|
99
|
+
parser_state: NaStructParserState,
|
|
100
|
+
name: str,
|
|
101
|
+
fn_start: int,
|
|
102
|
+
fn_end: int,
|
|
103
|
+
brackets_inside: list[BracketTree],
|
|
104
|
+
content: str
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Parses a standalone function definition block.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
parser_state (NaStructParserState): Shared parsing session state.
|
|
111
|
+
name (str): Full header text of the function
|
|
112
|
+
(e.g. 'fn is_adult(h: Human) -> Bool').
|
|
113
|
+
fn_start (int): Index of the first content character inside the braces.
|
|
114
|
+
fn_end (int): Index of the closing brace of the block.
|
|
115
|
+
brackets_inside (list[Token]): Nested bracket trees in the body.
|
|
116
|
+
content (str): Text of the file.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Strip the 'fn' keyword from the header
|
|
120
|
+
header_body: str = name[len("fn"):].strip()
|
|
121
|
+
|
|
122
|
+
# Parse the shared header grammar (function name, parameters, return type)
|
|
123
|
+
fn_name: str
|
|
124
|
+
fn_arguments: dict[str, TmpAttribute]
|
|
125
|
+
fn_output_type: TmpType
|
|
126
|
+
fn_name, fn_arguments, fn_output_type = _parse_fn_header(header_text=header_body)
|
|
127
|
+
|
|
128
|
+
# Parse the raw body text into statement AST nodes
|
|
129
|
+
fn_body: list[Statement] = StatementParser.parse_body(
|
|
130
|
+
text=content[fn_start:fn_end]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Check the function is not already defined
|
|
134
|
+
# (merge mode allows re-declaration: the resolved body replaces)
|
|
135
|
+
if fn_name in parser_state.tmp_functions and not parser_state.merge:
|
|
136
|
+
raise SyntaxError(f"Function '{fn_name}' is already defined")
|
|
137
|
+
|
|
138
|
+
# Register the function in the parser state
|
|
139
|
+
parser_state.tmp_functions[fn_name] = TmpFunction(
|
|
140
|
+
fn_name=fn_name,
|
|
141
|
+
fn_output_type=fn_output_type,
|
|
142
|
+
fn_argument=fn_arguments,
|
|
143
|
+
fn_body=fn_body,
|
|
144
|
+
dependencies=_collect_callable_dependencies(
|
|
145
|
+
fn_arguments=fn_arguments,
|
|
146
|
+
fn_output_type=fn_output_type,
|
|
147
|
+
fn_body=fn_body
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _parse_fn_header(
|
|
153
|
+
header_text: str
|
|
154
|
+
) -> tuple[str, dict[str, TmpAttribute], TmpType]:
|
|
155
|
+
"""
|
|
156
|
+
Parses a standalone function header into its name, parameters, and return type.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
header_text (str): Header text without the 'fn' keyword
|
|
160
|
+
(e.g. 'is_adult(h: Human) -> Bool').
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
tuple[str, dict[str, TmpAttribute], TmpType]: The function name, the
|
|
164
|
+
parameters by name, and the raw return type annotation.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
SyntaxError: If the header is malformed.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Check the header is present
|
|
171
|
+
if not header_text:
|
|
172
|
+
raise SyntaxError("Empty function header")
|
|
173
|
+
|
|
174
|
+
# Locate the parameter list opening parenthesis
|
|
175
|
+
paren_index: int = header_text.find("(")
|
|
176
|
+
|
|
177
|
+
# Check the parameter list is present
|
|
178
|
+
if paren_index == -1:
|
|
179
|
+
raise SyntaxError(f"Missing parameter list in function header: '{header_text}'")
|
|
180
|
+
|
|
181
|
+
# The function name is the text before the parameter list
|
|
182
|
+
fn_name: str = header_text[:paren_index].strip()
|
|
183
|
+
|
|
184
|
+
# Check the function name is a single token
|
|
185
|
+
if len(fn_name.split()) != 1:
|
|
186
|
+
raise SyntaxError(f"Malformed function name in header: '{header_text}'")
|
|
187
|
+
|
|
188
|
+
# Parse the shared parameter list and return type
|
|
189
|
+
fn_arguments: dict[str, TmpAttribute]
|
|
190
|
+
fn_output_type: TmpType
|
|
191
|
+
fn_arguments, fn_output_type = _parse_callable_header(header_text=header_text)
|
|
192
|
+
|
|
193
|
+
return fn_name, fn_arguments, fn_output_type
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _parse_callable_header(
|
|
197
|
+
header_text: str
|
|
198
|
+
) -> tuple[dict[str, TmpAttribute], TmpType]:
|
|
199
|
+
"""
|
|
200
|
+
Parses the '(...)' parameter list and '-> ReturnType' of a callable header.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
header_text (str): Header text containing the parameter list
|
|
204
|
+
(e.g. 'age(now: DateTime) -> DateTime').
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
tuple[dict[str, TmpAttribute], TmpType]: The parameters by name and
|
|
208
|
+
the parsed return type tree.
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
SyntaxError: If the header is malformed.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
tokens: list[Token] = Tokenizer(content=header_text).tokenize()
|
|
215
|
+
|
|
216
|
+
# Locate the parameter list boundaries at the token level
|
|
217
|
+
open_index: int
|
|
218
|
+
close_index: int
|
|
219
|
+
open_index, close_index = _find_param_list_boundaries(
|
|
220
|
+
tokens=tokens,
|
|
221
|
+
header_text=header_text
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Parse the comma separated parameter declarations
|
|
225
|
+
param_tokens: list[Token] = tokens[open_index + 1:close_index]
|
|
226
|
+
fn_arguments: dict[str, TmpAttribute] = _parse_parameter_tokens(
|
|
227
|
+
param_tokens=param_tokens,
|
|
228
|
+
header_text=header_text
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Parse the return type annotation after the closing parenthesis
|
|
232
|
+
fn_output_type: TmpType = _parse_return_type(
|
|
233
|
+
tokens=tokens,
|
|
234
|
+
close_index=close_index,
|
|
235
|
+
header_text=header_text
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return fn_arguments, fn_output_type
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _find_param_list_boundaries(
|
|
242
|
+
tokens: list[Token],
|
|
243
|
+
header_text: str
|
|
244
|
+
) -> tuple[int, int]:
|
|
245
|
+
"""
|
|
246
|
+
Finds the token indexes of the parameter list opening and closing parens.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
tokens (list[Token]): Tokenized header text (terminated by EOF).
|
|
250
|
+
header_text (str): Original header text (for error messages).
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
tuple[int, int]: Indexes of the '(' and matching ')' tokens.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
SyntaxError: If the parameter list is missing or unbalanced.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
# Locate the opening parenthesis token
|
|
260
|
+
open_index: int = -1
|
|
261
|
+
|
|
262
|
+
for index, token in enumerate(tokens):
|
|
263
|
+
|
|
264
|
+
if token.token_type == TokenType.PUNCTUATION and token.value == "(":
|
|
265
|
+
open_index = index
|
|
266
|
+
break
|
|
267
|
+
|
|
268
|
+
# Check the opening parenthesis is present
|
|
269
|
+
if open_index == -1:
|
|
270
|
+
raise SyntaxError(f"Missing parameter list in header: '{header_text}'")
|
|
271
|
+
|
|
272
|
+
# Scan for the matching closing parenthesis with depth tracking
|
|
273
|
+
depth: int = 0
|
|
274
|
+
|
|
275
|
+
for index in range(open_index, len(tokens)):
|
|
276
|
+
loop_token: Token = tokens[index]
|
|
277
|
+
|
|
278
|
+
if loop_token.token_type == TokenType.EOF:
|
|
279
|
+
break
|
|
280
|
+
|
|
281
|
+
if loop_token.token_type == TokenType.PUNCTUATION:
|
|
282
|
+
if loop_token.value == "(":
|
|
283
|
+
depth += 1
|
|
284
|
+
|
|
285
|
+
elif loop_token.value == ")":
|
|
286
|
+
depth -= 1
|
|
287
|
+
|
|
288
|
+
# Matching closing parenthesis found
|
|
289
|
+
if depth == 0:
|
|
290
|
+
return open_index, index
|
|
291
|
+
|
|
292
|
+
raise SyntaxError(f"Unbalanced parameter list in header: '{header_text}'")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _parse_parameter_tokens(
|
|
296
|
+
param_tokens: list[Token],
|
|
297
|
+
header_text: str
|
|
298
|
+
) -> dict[str, TmpAttribute]:
|
|
299
|
+
"""
|
|
300
|
+
Parses the parameter declaration tokens into TmpAttribute entries.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
param_tokens (list[Token]): Tokens between the parameter parentheses.
|
|
304
|
+
header_text (str): Original header text (for error messages).
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
dict[str, TmpAttribute]: Parameters by name.
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
SyntaxError: On empty or duplicated parameter declarations.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
fn_arguments: dict[str, TmpAttribute] = {}
|
|
314
|
+
|
|
315
|
+
# Split on top-level commas (nested calls / defaults are kept intact)
|
|
316
|
+
segments: list[list[Token]] = split_top_level(tokens=param_tokens, separator=",")
|
|
317
|
+
|
|
318
|
+
# An empty parameter list is a single empty segment
|
|
319
|
+
if len(segments) == 1 and not segments[0]:
|
|
320
|
+
return fn_arguments
|
|
321
|
+
|
|
322
|
+
for segment in segments:
|
|
323
|
+
|
|
324
|
+
# Check the parameter declaration is not empty
|
|
325
|
+
if not segment:
|
|
326
|
+
raise SyntaxError(f"Empty parameter declaration in header: '{header_text}'")
|
|
327
|
+
|
|
328
|
+
# Rebuild the raw declaration text from the source span
|
|
329
|
+
param_text: str = header_text[segment[0].position:segment[-1].end_position]
|
|
330
|
+
|
|
331
|
+
param_name: str
|
|
332
|
+
param_type: TmpType
|
|
333
|
+
param_default: Optional[Expression]
|
|
334
|
+
param_name, param_type, param_default = parse_declaration_text(
|
|
335
|
+
declaration_text=param_text
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Check the parameter is not duplicated
|
|
339
|
+
if param_name in fn_arguments:
|
|
340
|
+
raise SyntaxError(
|
|
341
|
+
f"Parameter '{param_name}' is declared twice in header: '{header_text}'"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Collect dependencies from the type tree and the default expression
|
|
345
|
+
dependencies: set[str] = param_type.collect_names()
|
|
346
|
+
|
|
347
|
+
if param_default is not None:
|
|
348
|
+
dependencies |= collect_dependencies(expression=param_default)
|
|
349
|
+
|
|
350
|
+
fn_arguments[param_name] = TmpAttribute(
|
|
351
|
+
attribute_name=param_name,
|
|
352
|
+
attribute_type=param_type,
|
|
353
|
+
attribute_default_value=param_default,
|
|
354
|
+
dependencies=dependencies
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return fn_arguments
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _parse_return_type(
|
|
361
|
+
tokens: list[Token],
|
|
362
|
+
close_index: int,
|
|
363
|
+
header_text: str
|
|
364
|
+
) -> TmpType:
|
|
365
|
+
"""
|
|
366
|
+
Parses the '-> ReturnType' annotation following the parameter list.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
tokens (list[Token]): Tokenized header text (terminated by EOF).
|
|
370
|
+
close_index (int): Token index of the parameter list closing paren.
|
|
371
|
+
header_text (str): Original header text (for slicing and errors).
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
str: The raw return type annotation ('' if absent).
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
SyntaxError: If the annotation arrow is present but the type is missing.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
rest_tokens: list[Token] = tokens[close_index + 1:]
|
|
381
|
+
|
|
382
|
+
# No return type annotation is not allowed (kept explicit for clarity)
|
|
383
|
+
if not rest_tokens or rest_tokens[0].token_type == TokenType.EOF:
|
|
384
|
+
raise SyntaxError(f"Missing return type in header: '{header_text}'")
|
|
385
|
+
|
|
386
|
+
# The annotation must start with the '->' arrow operator
|
|
387
|
+
arrow_token: Token = rest_tokens[0]
|
|
388
|
+
|
|
389
|
+
if not (arrow_token.token_type == TokenType.OPERATOR and arrow_token.value == "->"):
|
|
390
|
+
raise SyntaxError(f"Malformed return type in header: '{header_text}'")
|
|
391
|
+
|
|
392
|
+
# Check the return type is present
|
|
393
|
+
if len(rest_tokens) < 2 or rest_tokens[1].token_type == TokenType.EOF:
|
|
394
|
+
raise SyntaxError(f"Missing return type after '->' in header: '{header_text}'")
|
|
395
|
+
|
|
396
|
+
return_type_text: str = header_text[arrow_token.end_position:].strip()
|
|
397
|
+
|
|
398
|
+
return TypeParser.parse_text(text=return_type_text)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _collect_callable_dependencies(
|
|
402
|
+
fn_arguments: dict[str, TmpAttribute],
|
|
403
|
+
fn_output_type: TmpType,
|
|
404
|
+
fn_body: list[Statement]
|
|
405
|
+
) -> set[str]:
|
|
406
|
+
"""
|
|
407
|
+
Collects the dependency identifiers of a callable declaration.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
fn_arguments (dict[str, TmpAttribute]): Parameters by name.
|
|
411
|
+
fn_output_type (TmpType): Parsed return type tree.
|
|
412
|
+
fn_body (list[Statement]): Parsed body statements.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
set[str]: Union of the parameter, return type, and free body
|
|
416
|
+
dependencies (parameters and local variables excluded).
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
dependencies: set[str] = set()
|
|
420
|
+
|
|
421
|
+
# Union the dependencies of every parameter
|
|
422
|
+
for fn_argument in fn_arguments.values():
|
|
423
|
+
dependencies |= fn_argument.dependencies
|
|
424
|
+
|
|
425
|
+
# Add the return type dependencies
|
|
426
|
+
dependencies |= fn_output_type.collect_names()
|
|
427
|
+
|
|
428
|
+
# Add the free body dependencies (subtract parameters and locals)
|
|
429
|
+
body_dependencies: set[str] = collect_statement_dependencies(statements=fn_body)
|
|
430
|
+
bound_names: set[str] = set(fn_arguments.keys()) | collect_declared_names(
|
|
431
|
+
statements=fn_body
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
dependencies |= body_dependencies - bound_names
|
|
435
|
+
|
|
436
|
+
return dependencies
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default value expression parsing for .nastruct attributes.
|
|
3
|
+
|
|
4
|
+
Provides the parse_expression helper converting an attribute default value
|
|
5
|
+
source span into an Expression AST using the shared expression parser.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import Modules
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from nadata.core.expressions.expression import Expression
|
|
13
|
+
from nadata.parser.expressions.expression_parser import ExpressionParser
|
|
14
|
+
from nadata.parser.nastruct_parser.parser_state import NaStructParserState
|
|
15
|
+
from nadata.parser.utils.brackets import BracketTree
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_expression(
|
|
19
|
+
parser_state: NaStructParserState,
|
|
20
|
+
struct_name: str,
|
|
21
|
+
attr_start: int,
|
|
22
|
+
attr_end: int,
|
|
23
|
+
content: str,
|
|
24
|
+
bracket_inside: Optional[BracketTree]
|
|
25
|
+
) -> Expression:
|
|
26
|
+
"""
|
|
27
|
+
Parses the default value expression of an attribute into an AST.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
parser_state (NaStructParserState): Shared parsing session state
|
|
31
|
+
(unused for now, kept for signature parity with the other
|
|
32
|
+
attribute parsers).
|
|
33
|
+
struct_name (str): Name of the struct owning the attribute
|
|
34
|
+
(unused for now, kept for signature parity).
|
|
35
|
+
attr_start (int): Index of the first character of the expression span.
|
|
36
|
+
attr_end (int): Index just after the last character of the span.
|
|
37
|
+
content (str): Text of the file.
|
|
38
|
+
bracket_inside (Optional[BracketTree]): Bracket tree contained in the
|
|
39
|
+
declaration (unused for now, kept for signature parity).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Expression: Parsed expression AST.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
SyntaxError: If the expression span is empty or malformed.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Extract the raw expression text between the span boundaries
|
|
49
|
+
expression_text: str = content[attr_start:attr_end].strip()
|
|
50
|
+
|
|
51
|
+
# Check the expression text is present
|
|
52
|
+
if not expression_text:
|
|
53
|
+
raise SyntaxError(
|
|
54
|
+
f"Empty expression for struct '{struct_name}' "
|
|
55
|
+
f"between indexes {attr_start} and {attr_end}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Delegate to the shared recursive descent expression parser
|
|
59
|
+
return ExpressionParser.parse_text(text=expression_text)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Struct definition parsing for .nastruct schema files.
|
|
3
|
+
|
|
4
|
+
Provides the struct header parsing, the struct body scanning loop, and the
|
|
5
|
+
case dispatchers detecting attribute and method declarations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Import Modules
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from nadata.parser.utils.brackets import BracketTree
|
|
13
|
+
from nadata.parser.nastruct_parser.tmp_structs import TmpStruct
|
|
14
|
+
from nadata.parser.nastruct_parser.struct_body_state import StructBodyScanState
|
|
15
|
+
from nadata.parser.nastruct_parser.parser_state import NaStructParserState
|
|
16
|
+
from nadata.parser.nastruct_parser.parse_attributes import parse_struct_attribute_case
|
|
17
|
+
from nadata.parser.nastruct_parser.parse_callables import parse_struct_method
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_struct_header(
|
|
21
|
+
header_text: str
|
|
22
|
+
) -> tuple[str, Optional[str]]:
|
|
23
|
+
"""
|
|
24
|
+
Extracts the struct name and optional parent name from a class header.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
header_text (str): Full header text (e.g. 'class Human(Entity)').
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
tuple[str, Optional[str]]: The struct name and the parent name
|
|
31
|
+
(None if the struct has no parent).
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
SyntaxError: If the header is malformed.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Strip the 'class' keyword
|
|
38
|
+
header_body: str = header_text[len("class"):].strip()
|
|
39
|
+
|
|
40
|
+
# Case 1: inheritance 'class Human(Entity)'
|
|
41
|
+
if header_body.endswith(")"):
|
|
42
|
+
paren_index: int = header_body.find("(")
|
|
43
|
+
|
|
44
|
+
# Check the opening parenthesis is present and well placed
|
|
45
|
+
if paren_index == -1:
|
|
46
|
+
raise SyntaxError(f"Malformed class header: '{header_text}'")
|
|
47
|
+
|
|
48
|
+
struct_name: str = header_body[:paren_index].strip()
|
|
49
|
+
struct_parent: str = header_body[paren_index + 1:-1].strip()
|
|
50
|
+
|
|
51
|
+
# Check both names are present
|
|
52
|
+
if not struct_name or not struct_parent:
|
|
53
|
+
raise SyntaxError(f"Malformed class header: '{header_text}'")
|
|
54
|
+
|
|
55
|
+
return struct_name, struct_parent
|
|
56
|
+
|
|
57
|
+
# Case 2: no inheritance 'class Human'
|
|
58
|
+
if "(" in header_body or ")" in header_body:
|
|
59
|
+
raise SyntaxError(f"Malformed class header: '{header_text}'")
|
|
60
|
+
|
|
61
|
+
# Check the struct name is present
|
|
62
|
+
if not header_body:
|
|
63
|
+
raise SyntaxError(f"Missing struct name in header: '{header_text}'")
|
|
64
|
+
|
|
65
|
+
# Check the struct name is a single token
|
|
66
|
+
if len(header_body.split()) != 1:
|
|
67
|
+
raise SyntaxError(f"Malformed class header: '{header_text}'")
|
|
68
|
+
|
|
69
|
+
return header_body, None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_struct(
|
|
73
|
+
parser_state: NaStructParserState,
|
|
74
|
+
struct_name: str,
|
|
75
|
+
struct_parent: Optional[str],
|
|
76
|
+
struct_start: int,
|
|
77
|
+
struct_end: int,
|
|
78
|
+
brackets_inside: list[BracketTree],
|
|
79
|
+
content: str
|
|
80
|
+
) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Parses a struct definition block.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
parser_state (NaStructParserState): Shared parsing session state.
|
|
86
|
+
struct_name (str): Name of the struct.
|
|
87
|
+
struct_parent (Optional[str]): Name of the parent struct, if any.
|
|
88
|
+
struct_start (int): Index of the first content character inside the braces.
|
|
89
|
+
struct_end (int): Index of the closing brace of the block.
|
|
90
|
+
brackets_inside (list[BracketTree]): Nested bracket trees inside the block.
|
|
91
|
+
content (str): Text of the file.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
# Check if struct name already exists (tmp and resolved namespaces).
|
|
95
|
+
# In merge mode (session schema re-merge), re-declaring a struct that
|
|
96
|
+
# already lives in the database is allowed and replaces its members
|
|
97
|
+
if (
|
|
98
|
+
struct_name in parser_state.tmp_structs
|
|
99
|
+
or (
|
|
100
|
+
not parser_state.merge
|
|
101
|
+
and struct_name in parser_state.database.tables_by_struct_name
|
|
102
|
+
)
|
|
103
|
+
):
|
|
104
|
+
raise SyntaxError(f"Struct '{struct_name}' is already defined")
|
|
105
|
+
|
|
106
|
+
# Prepare the tmp struct
|
|
107
|
+
parser_state.tmp_structs[struct_name] = TmpStruct(
|
|
108
|
+
struct_name=struct_name,
|
|
109
|
+
struct_parent=struct_parent
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Initialize the mutable scanning state of the struct body
|
|
113
|
+
# (struct_start is only used to initialize the cursor)
|
|
114
|
+
scan_state: StructBodyScanState = StructBodyScanState(
|
|
115
|
+
struct_name=struct_name,
|
|
116
|
+
struct_end=struct_end,
|
|
117
|
+
content=content,
|
|
118
|
+
cursor=struct_start,
|
|
119
|
+
brackets_inside=brackets_inside
|
|
120
|
+
)
|
|
121
|
+
scan_state.next_bracket = scan_state.pop_next_bracket()
|
|
122
|
+
|
|
123
|
+
# While we have something in the struct brackets
|
|
124
|
+
while scan_state.cursor < scan_state.struct_end:
|
|
125
|
+
|
|
126
|
+
# Manage white spaces (spaces, tabs, newlines, and blanked comments)
|
|
127
|
+
if scan_state.content[scan_state.cursor].isspace():
|
|
128
|
+
scan_state.cursor += 1
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# Class method declaration
|
|
132
|
+
if scan_state.content[scan_state.cursor] == "@":
|
|
133
|
+
parse_struct_method_case(
|
|
134
|
+
parser_state=parser_state,
|
|
135
|
+
scan_state=scan_state
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Else, it is an attribut definition
|
|
139
|
+
else:
|
|
140
|
+
parse_struct_attribute_case(
|
|
141
|
+
parser_state=parser_state,
|
|
142
|
+
scan_state=scan_state
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def parse_struct_method_case(
|
|
147
|
+
parser_state: NaStructParserState,
|
|
148
|
+
scan_state: StructBodyScanState
|
|
149
|
+
) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Detects and dispatches a single method declaration of a struct body.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
parser_state (NaStructParserState): Shared parsing session state.
|
|
155
|
+
scan_state (StructBodyScanState): Mutable scanning state (updated in place).
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# Check bracket
|
|
159
|
+
if scan_state.next_bracket is None:
|
|
160
|
+
raise SyntaxError(
|
|
161
|
+
f"Method declaration at index {scan_state.cursor} has no body '{{...}}'"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Get text before (the full method header: name, params, return type)
|
|
165
|
+
method_header: str = (
|
|
166
|
+
scan_state.content[scan_state.cursor + 1:scan_state.next_bracket.open_index].strip()
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Check the text is present
|
|
170
|
+
if not method_header:
|
|
171
|
+
raise SyntaxError(f"Empty method declaration at index {scan_state.cursor}")
|
|
172
|
+
|
|
173
|
+
# A method declaration requires a parameter list '(...)'
|
|
174
|
+
paren_index: int = method_header.find("(")
|
|
175
|
+
|
|
176
|
+
# If there are no parenthesis, it is a syntax error
|
|
177
|
+
if paren_index == -1:
|
|
178
|
+
raise SyntaxError(
|
|
179
|
+
f"Method declaration at index {scan_state.cursor} is missing its "
|
|
180
|
+
f"parameter list '(...)': '@{method_header[:20]}...'"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# The method name is the text before the parameter list
|
|
184
|
+
method_name: str = method_header[:paren_index].strip()
|
|
185
|
+
|
|
186
|
+
# Check the method name is present
|
|
187
|
+
if not method_name:
|
|
188
|
+
raise SyntaxError(f"Missing method name at index {scan_state.cursor}")
|
|
189
|
+
|
|
190
|
+
# Parse the struct method
|
|
191
|
+
parse_struct_method(
|
|
192
|
+
parser_state=parser_state,
|
|
193
|
+
struct_name=scan_state.struct_name,
|
|
194
|
+
struct_method_name=method_name,
|
|
195
|
+
header=method_header,
|
|
196
|
+
method_start=scan_state.next_bracket.open_index + 1,
|
|
197
|
+
method_end=scan_state.next_bracket.close_index,
|
|
198
|
+
content=scan_state.content,
|
|
199
|
+
brackets_inside=scan_state.next_bracket.inside
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Advance the cursor
|
|
203
|
+
scan_state.cursor = scan_state.next_bracket.close_index + 1
|
|
204
|
+
|
|
205
|
+
# Advance the next bracket
|
|
206
|
+
scan_state.next_bracket = scan_state.pop_next_bracket()
|