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,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()