yuho 5.0.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 (91) hide show
  1. yuho/__init__.py +16 -0
  2. yuho/ast/__init__.py +196 -0
  3. yuho/ast/builder.py +926 -0
  4. yuho/ast/constant_folder.py +280 -0
  5. yuho/ast/dead_code.py +199 -0
  6. yuho/ast/exhaustiveness.py +503 -0
  7. yuho/ast/nodes.py +907 -0
  8. yuho/ast/overlap.py +291 -0
  9. yuho/ast/reachability.py +293 -0
  10. yuho/ast/scope_analysis.py +490 -0
  11. yuho/ast/transformer.py +490 -0
  12. yuho/ast/type_check.py +471 -0
  13. yuho/ast/type_inference.py +425 -0
  14. yuho/ast/visitor.py +239 -0
  15. yuho/cli/__init__.py +14 -0
  16. yuho/cli/commands/__init__.py +1 -0
  17. yuho/cli/commands/api.py +431 -0
  18. yuho/cli/commands/ast_viz.py +334 -0
  19. yuho/cli/commands/check.py +218 -0
  20. yuho/cli/commands/config.py +311 -0
  21. yuho/cli/commands/contribute.py +122 -0
  22. yuho/cli/commands/diff.py +487 -0
  23. yuho/cli/commands/explain.py +240 -0
  24. yuho/cli/commands/fmt.py +253 -0
  25. yuho/cli/commands/generate.py +316 -0
  26. yuho/cli/commands/graph.py +410 -0
  27. yuho/cli/commands/init.py +120 -0
  28. yuho/cli/commands/library.py +656 -0
  29. yuho/cli/commands/lint.py +503 -0
  30. yuho/cli/commands/lsp.py +36 -0
  31. yuho/cli/commands/preview.py +377 -0
  32. yuho/cli/commands/repl.py +444 -0
  33. yuho/cli/commands/serve.py +44 -0
  34. yuho/cli/commands/test.py +528 -0
  35. yuho/cli/commands/transpile.py +121 -0
  36. yuho/cli/commands/wizard.py +370 -0
  37. yuho/cli/completions.py +182 -0
  38. yuho/cli/error_formatter.py +193 -0
  39. yuho/cli/main.py +1064 -0
  40. yuho/config/__init__.py +46 -0
  41. yuho/config/loader.py +235 -0
  42. yuho/config/mask.py +194 -0
  43. yuho/config/schema.py +147 -0
  44. yuho/library/__init__.py +84 -0
  45. yuho/library/index.py +328 -0
  46. yuho/library/install.py +699 -0
  47. yuho/library/lockfile.py +330 -0
  48. yuho/library/package.py +421 -0
  49. yuho/library/resolver.py +791 -0
  50. yuho/library/signature.py +335 -0
  51. yuho/llm/__init__.py +45 -0
  52. yuho/llm/config.py +75 -0
  53. yuho/llm/factory.py +123 -0
  54. yuho/llm/prompts.py +146 -0
  55. yuho/llm/providers.py +383 -0
  56. yuho/llm/utils.py +470 -0
  57. yuho/lsp/__init__.py +14 -0
  58. yuho/lsp/code_action_handler.py +518 -0
  59. yuho/lsp/completion_handler.py +85 -0
  60. yuho/lsp/diagnostics.py +100 -0
  61. yuho/lsp/hover_handler.py +130 -0
  62. yuho/lsp/server.py +1425 -0
  63. yuho/mcp/__init__.py +10 -0
  64. yuho/mcp/server.py +1452 -0
  65. yuho/parser/__init__.py +8 -0
  66. yuho/parser/source_location.py +108 -0
  67. yuho/parser/wrapper.py +311 -0
  68. yuho/testing/__init__.py +48 -0
  69. yuho/testing/coverage.py +274 -0
  70. yuho/testing/fixtures.py +263 -0
  71. yuho/transpile/__init__.py +52 -0
  72. yuho/transpile/alloy_transpiler.py +546 -0
  73. yuho/transpile/base.py +100 -0
  74. yuho/transpile/blocks_transpiler.py +338 -0
  75. yuho/transpile/english_transpiler.py +470 -0
  76. yuho/transpile/graphql_transpiler.py +404 -0
  77. yuho/transpile/json_transpiler.py +217 -0
  78. yuho/transpile/jsonld_transpiler.py +250 -0
  79. yuho/transpile/latex_preamble.py +161 -0
  80. yuho/transpile/latex_transpiler.py +406 -0
  81. yuho/transpile/latex_utils.py +206 -0
  82. yuho/transpile/mermaid_transpiler.py +357 -0
  83. yuho/transpile/registry.py +275 -0
  84. yuho/verify/__init__.py +43 -0
  85. yuho/verify/alloy.py +352 -0
  86. yuho/verify/combined.py +218 -0
  87. yuho/verify/z3_solver.py +1155 -0
  88. yuho-5.0.0.dist-info/METADATA +186 -0
  89. yuho-5.0.0.dist-info/RECORD +91 -0
  90. yuho-5.0.0.dist-info/WHEEL +4 -0
  91. yuho-5.0.0.dist-info/entry_points.txt +2 -0
yuho/ast/builder.py ADDED
@@ -0,0 +1,926 @@
1
+ """
2
+ ASTBuilder class that walks a tree-sitter Tree and constructs AST nodes.
3
+ """
4
+
5
+ from datetime import date
6
+ from decimal import Decimal
7
+ from typing import Optional, List, Tuple
8
+
9
+ from yuho.ast import nodes
10
+ from yuho.parser.source_location import SourceLocation
11
+
12
+
13
+ class ASTBuilder:
14
+ """
15
+ Builds Yuho AST from a tree-sitter parse tree.
16
+
17
+ Usage:
18
+ from yuho.parser import Parser
19
+ from yuho.ast import ASTBuilder
20
+
21
+ parser = Parser()
22
+ result = parser.parse(source_code)
23
+
24
+ builder = ASTBuilder(source_code, file_path)
25
+ module = builder.build(result.tree.root_node)
26
+ """
27
+
28
+ def __init__(self, source: str, file: str = "<string>"):
29
+ """
30
+ Initialize the builder.
31
+
32
+ Args:
33
+ source: The original source code (needed to extract text from nodes)
34
+ file: File path for source locations
35
+ """
36
+ self.source = source
37
+ self.source_bytes = source.encode("utf-8")
38
+ self.file = file
39
+
40
+ def build(self, root_node) -> nodes.ModuleNode:
41
+ """
42
+ Build a ModuleNode from a tree-sitter root node.
43
+
44
+ Args:
45
+ root_node: The root node from tree-sitter (should be 'source_file')
46
+
47
+ Returns:
48
+ A ModuleNode representing the complete AST
49
+ """
50
+ return self._build_module(root_node)
51
+
52
+ def _loc(self, node) -> SourceLocation:
53
+ """Extract source location from a tree-sitter node."""
54
+ return SourceLocation.from_tree_sitter_node(node, self.file)
55
+
56
+ def _text(self, node) -> str:
57
+ """Extract text content from a tree-sitter node."""
58
+ return self.source_bytes[node.start_byte:node.end_byte].decode("utf-8")
59
+
60
+ def _children_by_type(self, node, *types: str):
61
+ """Get all children matching the given types."""
62
+ return [c for c in node.children if c.type in types]
63
+
64
+ def _child_by_type(self, node, *types: str):
65
+ """Get first child matching the given types, or None."""
66
+ for c in node.children:
67
+ if c.type in types:
68
+ return c
69
+ return None
70
+
71
+ def _child_by_field(self, node, field_name: str):
72
+ """Get child by field name."""
73
+ return node.child_by_field_name(field_name)
74
+
75
+ # =========================================================================
76
+ # Module and top-level declarations
77
+ # =========================================================================
78
+
79
+ def _build_module(self, node) -> nodes.ModuleNode:
80
+ """Build ModuleNode from source_file node."""
81
+ imports: List[nodes.ImportNode] = []
82
+ type_defs: List[nodes.StructDefNode] = []
83
+ function_defs: List[nodes.FunctionDefNode] = []
84
+ statutes: List[nodes.StatuteNode] = []
85
+ variables: List[nodes.VariableDecl] = []
86
+ references: List[nodes.ReferencingStmt] = []
87
+ assertions: List[nodes.AssertStmt] = []
88
+
89
+ for child in node.children:
90
+ if child.type == "import_statement":
91
+ imports.append(self._build_import(child))
92
+ elif child.type == "referencing_statement":
93
+ references.append(self._build_referencing(child))
94
+ elif child.type == "struct_definition":
95
+ type_defs.append(self._build_struct_def(child))
96
+ elif child.type == "function_definition":
97
+ function_defs.append(self._build_function_def(child))
98
+ elif child.type == "statute_block":
99
+ statutes.append(self._build_statute(child))
100
+ elif child.type == "variable_declaration":
101
+ variables.append(self._build_variable_decl(child))
102
+ elif child.type == "assert_statement":
103
+ assertions.append(self._build_assert(child))
104
+
105
+ return nodes.ModuleNode(
106
+ imports=tuple(imports),
107
+ type_defs=tuple(type_defs),
108
+ function_defs=tuple(function_defs),
109
+ statutes=tuple(statutes),
110
+ variables=tuple(variables),
111
+ references=tuple(references),
112
+ assertions=tuple(assertions),
113
+ source_location=self._loc(node),
114
+ )
115
+
116
+ def _build_referencing(self, node) -> nodes.ReferencingStmt:
117
+ """Build ReferencingStmt from referencing_statement node."""
118
+ path_node = self._child_by_field(node, "path")
119
+ path = self._text(path_node) if path_node else ""
120
+ return nodes.ReferencingStmt(
121
+ path=path,
122
+ source_location=self._loc(node),
123
+ )
124
+
125
+ def _build_assert(self, node) -> nodes.AssertStmt:
126
+ """Build AssertStmt from assert_statement node."""
127
+ condition_node = self._child_by_field(node, "condition")
128
+ message_node = self._child_by_field(node, "message")
129
+
130
+ condition = self._build_expression(condition_node) if condition_node else nodes.BoolLit(value=True)
131
+ message = self._build_string_lit(message_node) if message_node else None
132
+
133
+ return nodes.AssertStmt(
134
+ condition=condition,
135
+ message=message,
136
+ source_location=self._loc(node),
137
+ )
138
+
139
+ def _build_import(self, node) -> nodes.ImportNode:
140
+ """Build ImportNode from import_statement node."""
141
+ path_node = self._child_by_type(node, "import_path")
142
+ path = self._text(path_node).strip('"') if path_node else ""
143
+
144
+ # Check for named imports
145
+ names: List[str] = []
146
+ for child in node.children:
147
+ if child.type == "identifier":
148
+ names.append(self._text(child))
149
+ elif child.type == "*":
150
+ names.append("*")
151
+
152
+ return nodes.ImportNode(
153
+ path=path,
154
+ imported_names=tuple(names),
155
+ source_location=self._loc(node),
156
+ )
157
+
158
+ # =========================================================================
159
+ # Struct definition
160
+ # =========================================================================
161
+
162
+ def _build_struct_def(self, node) -> nodes.StructDefNode:
163
+ """Build StructDefNode from struct_definition node."""
164
+ name_node = self._child_by_field(node, "name")
165
+ name = self._text(name_node) if name_node else ""
166
+
167
+ # Type parameters
168
+ type_params: List[str] = []
169
+ tp_node = self._child_by_type(node, "type_parameters")
170
+ if tp_node:
171
+ for child in tp_node.children:
172
+ if child.type == "identifier":
173
+ type_params.append(self._text(child))
174
+
175
+ # Fields
176
+ fields: List[nodes.FieldDef] = []
177
+ for child in node.children:
178
+ if child.type == "field_definition":
179
+ fields.append(self._build_field_def(child))
180
+
181
+ return nodes.StructDefNode(
182
+ name=name,
183
+ fields=tuple(fields),
184
+ type_params=tuple(type_params),
185
+ source_location=self._loc(node),
186
+ )
187
+
188
+ def _build_field_def(self, node) -> nodes.FieldDef:
189
+ """Build FieldDef from field_definition node."""
190
+ type_node = self._child_by_field(node, "type")
191
+ name_node = self._child_by_field(node, "name")
192
+
193
+ type_ann = self._build_type(type_node) if type_node else nodes.BuiltinType(name="void")
194
+ name = self._text(name_node) if name_node else ""
195
+
196
+ return nodes.FieldDef(
197
+ type_annotation=type_ann,
198
+ name=name,
199
+ source_location=self._loc(node),
200
+ )
201
+
202
+ # =========================================================================
203
+ # Function definition
204
+ # =========================================================================
205
+
206
+ def _build_function_def(self, node) -> nodes.FunctionDefNode:
207
+ """Build FunctionDefNode from function_definition node."""
208
+ name_node = self._child_by_field(node, "name")
209
+ name = self._text(name_node) if name_node else ""
210
+
211
+ # Parameters
212
+ params: List[nodes.ParamDef] = []
213
+ param_list = self._child_by_type(node, "parameter_list")
214
+ if param_list:
215
+ for child in param_list.children:
216
+ if child.type == "parameter":
217
+ params.append(self._build_param_def(child))
218
+
219
+ # Return type
220
+ return_type_node = self._child_by_field(node, "return_type")
221
+ return_type = self._build_type(return_type_node) if return_type_node else None
222
+
223
+ # Body
224
+ body_node = self._child_by_type(node, "block")
225
+ body = self._build_block(body_node) if body_node else nodes.Block(statements=())
226
+
227
+ return nodes.FunctionDefNode(
228
+ name=name,
229
+ params=tuple(params),
230
+ return_type=return_type,
231
+ body=body,
232
+ source_location=self._loc(node),
233
+ )
234
+
235
+ def _build_param_def(self, node) -> nodes.ParamDef:
236
+ """Build ParamDef from parameter node."""
237
+ type_node = self._child_by_field(node, "type")
238
+ name_node = self._child_by_field(node, "name")
239
+
240
+ type_ann = self._build_type(type_node) if type_node else nodes.BuiltinType(name="void")
241
+ name = self._text(name_node) if name_node else ""
242
+
243
+ return nodes.ParamDef(
244
+ type_annotation=type_ann,
245
+ name=name,
246
+ source_location=self._loc(node),
247
+ )
248
+
249
+ def _build_block(self, node) -> nodes.Block:
250
+ """Build Block from block node."""
251
+ statements: List[nodes.ASTNode] = []
252
+ for child in node.children:
253
+ stmt = self._build_statement(child)
254
+ if stmt:
255
+ statements.append(stmt)
256
+ return nodes.Block(
257
+ statements=tuple(statements),
258
+ source_location=self._loc(node),
259
+ )
260
+
261
+ # =========================================================================
262
+ # Statements
263
+ # =========================================================================
264
+
265
+ def _build_statement(self, node) -> Optional[nodes.ASTNode]:
266
+ """Build a statement node."""
267
+ if node.type == "variable_declaration":
268
+ return self._build_variable_decl(node)
269
+ elif node.type == "assignment_statement":
270
+ return self._build_assignment_stmt(node)
271
+ elif node.type == "return_statement":
272
+ return self._build_return_stmt(node)
273
+ elif node.type == "pass_statement":
274
+ return nodes.PassStmt(source_location=self._loc(node))
275
+ elif node.type == "expression_statement":
276
+ expr_node = node.children[0] if node.children else None
277
+ if expr_node:
278
+ return nodes.ExpressionStmt(
279
+ expression=self._build_expression(expr_node),
280
+ source_location=self._loc(node),
281
+ )
282
+ elif node.type in ("{", "}", ";"):
283
+ return None
284
+ else:
285
+ # Try to build as expression
286
+ expr = self._build_expression(node)
287
+ if expr:
288
+ return nodes.ExpressionStmt(
289
+ expression=expr,
290
+ source_location=self._loc(node),
291
+ )
292
+ return None
293
+
294
+ def _build_variable_decl(self, node) -> nodes.VariableDecl:
295
+ """Build VariableDecl from variable_declaration node."""
296
+ type_node = self._child_by_field(node, "type")
297
+ name_node = self._child_by_field(node, "name")
298
+ value_node = self._child_by_field(node, "value")
299
+
300
+ type_ann = self._build_type(type_node) if type_node else nodes.BuiltinType(name="void")
301
+ name = self._text(name_node) if name_node else ""
302
+ value = self._build_expression(value_node) if value_node else None
303
+
304
+ return nodes.VariableDecl(
305
+ type_annotation=type_ann,
306
+ name=name,
307
+ value=value,
308
+ source_location=self._loc(node),
309
+ )
310
+
311
+ def _build_assignment_stmt(self, node) -> nodes.AssignmentStmt:
312
+ """Build AssignmentStmt from assignment_statement node."""
313
+ target_node = self._child_by_field(node, "target")
314
+ value_node = self._child_by_field(node, "value")
315
+
316
+ target = self._build_expression(target_node) if target_node else nodes.IdentifierNode(name="")
317
+ value = self._build_expression(value_node) if value_node else nodes.PassExprNode()
318
+
319
+ return nodes.AssignmentStmt(
320
+ target=target,
321
+ value=value,
322
+ source_location=self._loc(node),
323
+ )
324
+
325
+ def _build_return_stmt(self, node) -> nodes.ReturnStmt:
326
+ """Build ReturnStmt from return_statement node."""
327
+ value = None
328
+ for child in node.children:
329
+ if child.type not in ("return", ";"):
330
+ value = self._build_expression(child)
331
+ break
332
+ return nodes.ReturnStmt(
333
+ value=value,
334
+ source_location=self._loc(node),
335
+ )
336
+
337
+ # =========================================================================
338
+ # Expressions
339
+ # =========================================================================
340
+
341
+ def _build_expression(self, node) -> nodes.ASTNode:
342
+ """Build an expression node."""
343
+ if node is None:
344
+ return nodes.PassExprNode()
345
+
346
+ node_type = node.type
347
+
348
+ # Literals
349
+ if node_type == "integer_literal":
350
+ return self._build_int_lit(node)
351
+ elif node_type == "float_literal":
352
+ return self._build_float_lit(node)
353
+ elif node_type == "boolean_literal":
354
+ return self._build_bool_lit(node)
355
+ elif node_type == "string_literal":
356
+ return self._build_string_lit(node)
357
+ elif node_type == "money_literal":
358
+ return self._build_money(node)
359
+ elif node_type == "percent_literal":
360
+ return self._build_percent(node)
361
+ elif node_type == "date_literal":
362
+ return self._build_date(node)
363
+ elif node_type == "duration_literal":
364
+ return self._build_duration(node)
365
+
366
+ # Expressions
367
+ elif node_type == "identifier":
368
+ return nodes.IdentifierNode(
369
+ name=self._text(node),
370
+ source_location=self._loc(node),
371
+ )
372
+ elif node_type == "field_access":
373
+ return self._build_field_access(node)
374
+ elif node_type == "index_access":
375
+ return self._build_index_access(node)
376
+ elif node_type == "function_call":
377
+ return self._build_function_call(node)
378
+ elif node_type == "binary_expression":
379
+ return self._build_binary_expr(node)
380
+ elif node_type == "unary_expression":
381
+ return self._build_unary_expr(node)
382
+ elif node_type == "parenthesized_expression":
383
+ # Unwrap parentheses
384
+ for child in node.children:
385
+ if child.type not in ("(", ")"):
386
+ return self._build_expression(child)
387
+ elif node_type == "match_expression":
388
+ return self._build_match_expr(node)
389
+ elif node_type == "struct_literal":
390
+ return self._build_struct_literal(node)
391
+ elif node_type in ("pass_expression", "pass"):
392
+ return nodes.PassExprNode(source_location=self._loc(node))
393
+
394
+ # Fallback - try to find first non-trivial child
395
+ for child in node.children:
396
+ if child.type not in ("{", "}", "(", ")", ",", ";", ":="):
397
+ return self._build_expression(child)
398
+
399
+ return nodes.PassExprNode(source_location=self._loc(node))
400
+
401
+ def _build_int_lit(self, node) -> nodes.IntLit:
402
+ """Build IntLit from integer_literal node."""
403
+ text = self._text(node)
404
+ return nodes.IntLit(
405
+ value=int(text),
406
+ source_location=self._loc(node),
407
+ )
408
+
409
+ def _build_float_lit(self, node) -> nodes.FloatLit:
410
+ """Build FloatLit from float_literal node."""
411
+ text = self._text(node)
412
+ return nodes.FloatLit(
413
+ value=float(text),
414
+ source_location=self._loc(node),
415
+ )
416
+
417
+ def _build_bool_lit(self, node) -> nodes.BoolLit:
418
+ """Build BoolLit from boolean_literal node."""
419
+ text = self._text(node)
420
+ return nodes.BoolLit(
421
+ value=(text == "TRUE"),
422
+ source_location=self._loc(node),
423
+ )
424
+
425
+ def _build_string_lit(self, node) -> nodes.StringLit:
426
+ """Build StringLit from string_literal node."""
427
+ text = self._text(node)
428
+ # Remove quotes and process escape sequences
429
+ if text.startswith('"') and text.endswith('"'):
430
+ text = text[1:-1]
431
+ # Process common escape sequences
432
+ text = (text
433
+ .replace("\\n", "\n")
434
+ .replace("\\t", "\t")
435
+ .replace("\\r", "\r")
436
+ .replace('\\"', '"')
437
+ .replace("\\\\", "\\"))
438
+ return nodes.StringLit(
439
+ value=text,
440
+ source_location=self._loc(node),
441
+ )
442
+
443
+ def _build_money(self, node) -> nodes.MoneyNode:
444
+ """Build MoneyNode from money_literal node."""
445
+ currency_node = self._child_by_type(node, "currency_symbol")
446
+ amount_node = self._child_by_type(node, "money_amount")
447
+
448
+ currency_text = self._text(currency_node) if currency_node else "$"
449
+ currency = nodes.Currency.from_symbol(currency_text)
450
+
451
+ amount_text = self._text(amount_node).replace(",", "") if amount_node else "0"
452
+ amount = Decimal(amount_text)
453
+
454
+ return nodes.MoneyNode(
455
+ currency=currency,
456
+ amount=amount,
457
+ source_location=self._loc(node),
458
+ )
459
+
460
+ def _build_percent(self, node) -> nodes.PercentNode:
461
+ """Build PercentNode from percent_literal node."""
462
+ int_node = self._child_by_type(node, "integer_literal")
463
+ value = int(self._text(int_node)) if int_node else 0
464
+ return nodes.PercentNode(
465
+ value=Decimal(value),
466
+ source_location=self._loc(node),
467
+ )
468
+
469
+ def _build_date(self, node) -> nodes.DateNode:
470
+ """Build DateNode from date_literal node."""
471
+ text = self._text(node)
472
+ return nodes.DateNode.from_iso8601(text, self._loc(node))
473
+
474
+ def _build_duration(self, node) -> nodes.DurationNode:
475
+ """Build DurationNode from duration_literal node."""
476
+ years = months = days = hours = minutes = seconds = 0
477
+
478
+ i = 0
479
+ children = node.children
480
+ while i < len(children):
481
+ child = children[i]
482
+ if child.type == "integer_literal":
483
+ value = int(self._text(child))
484
+ # Look for unit
485
+ if i + 1 < len(children) and children[i + 1].type == "duration_unit":
486
+ unit = self._text(children[i + 1])
487
+ if unit in ("year", "years"):
488
+ years += value
489
+ elif unit in ("month", "months"):
490
+ months += value
491
+ elif unit in ("day", "days"):
492
+ days += value
493
+ elif unit in ("hour", "hours"):
494
+ hours += value
495
+ elif unit in ("minute", "minutes"):
496
+ minutes += value
497
+ elif unit in ("second", "seconds"):
498
+ seconds += value
499
+ i += 1
500
+ i += 1
501
+
502
+ return nodes.DurationNode(
503
+ years=years,
504
+ months=months,
505
+ days=days,
506
+ hours=hours,
507
+ minutes=minutes,
508
+ seconds=seconds,
509
+ source_location=self._loc(node),
510
+ )
511
+
512
+ def _build_field_access(self, node) -> nodes.FieldAccessNode:
513
+ """Build FieldAccessNode from field_access node."""
514
+ base_node = self._child_by_field(node, "base")
515
+ field_node = self._child_by_field(node, "field")
516
+
517
+ base = self._build_expression(base_node) if base_node else nodes.PassExprNode()
518
+ field_name = self._text(field_node) if field_node else ""
519
+
520
+ return nodes.FieldAccessNode(
521
+ base=base,
522
+ field_name=field_name,
523
+ source_location=self._loc(node),
524
+ )
525
+
526
+ def _build_index_access(self, node) -> nodes.IndexAccessNode:
527
+ """Build IndexAccessNode from index_access node."""
528
+ base_node = self._child_by_field(node, "base")
529
+ index_node = self._child_by_field(node, "index")
530
+
531
+ base = self._build_expression(base_node) if base_node else nodes.PassExprNode()
532
+ index = self._build_expression(index_node) if index_node else nodes.PassExprNode()
533
+
534
+ return nodes.IndexAccessNode(
535
+ base=base,
536
+ index=index,
537
+ source_location=self._loc(node),
538
+ )
539
+
540
+ def _build_function_call(self, node) -> nodes.FunctionCallNode:
541
+ """Build FunctionCallNode from function_call node."""
542
+ callee_node = self._child_by_field(node, "callee")
543
+ callee = self._build_expression(callee_node) if callee_node else nodes.IdentifierNode(name="")
544
+
545
+ args: List[nodes.ASTNode] = []
546
+ arg_list = self._child_by_type(node, "argument_list")
547
+ if arg_list:
548
+ for child in arg_list.children:
549
+ if child.type not in (",", "(", ")"):
550
+ args.append(self._build_expression(child))
551
+
552
+ return nodes.FunctionCallNode(
553
+ callee=callee,
554
+ args=tuple(args),
555
+ source_location=self._loc(node),
556
+ )
557
+
558
+ def _build_binary_expr(self, node) -> nodes.BinaryExprNode:
559
+ """Build BinaryExprNode from binary_expression node."""
560
+ children = [c for c in node.children if c.type not in ("(", ")")]
561
+
562
+ if len(children) < 3:
563
+ return nodes.BinaryExprNode(
564
+ left=nodes.PassExprNode(),
565
+ operator="?",
566
+ right=nodes.PassExprNode(),
567
+ source_location=self._loc(node),
568
+ )
569
+
570
+ left = self._build_expression(children[0])
571
+ operator = self._text(children[1])
572
+ right = self._build_expression(children[2])
573
+
574
+ return nodes.BinaryExprNode(
575
+ left=left,
576
+ operator=operator,
577
+ right=right,
578
+ source_location=self._loc(node),
579
+ )
580
+
581
+ def _build_unary_expr(self, node) -> nodes.UnaryExprNode:
582
+ """Build UnaryExprNode from unary_expression node."""
583
+ children = [c for c in node.children if c.type not in ("(", ")")]
584
+
585
+ if len(children) < 2:
586
+ return nodes.UnaryExprNode(
587
+ operator="?",
588
+ operand=nodes.PassExprNode(),
589
+ source_location=self._loc(node),
590
+ )
591
+
592
+ operator = self._text(children[0])
593
+ operand = self._build_expression(children[1])
594
+
595
+ return nodes.UnaryExprNode(
596
+ operator=operator,
597
+ operand=operand,
598
+ source_location=self._loc(node),
599
+ )
600
+
601
+ # =========================================================================
602
+ # Match expression
603
+ # =========================================================================
604
+
605
+ def _build_match_expr(self, node) -> nodes.MatchExprNode:
606
+ """Build MatchExprNode from match_expression node."""
607
+ scrutinee_node = self._child_by_field(node, "scrutinee")
608
+ scrutinee = self._build_expression(scrutinee_node) if scrutinee_node else None
609
+
610
+ arms: List[nodes.MatchArm] = []
611
+ for child in node.children:
612
+ if child.type == "match_arm":
613
+ arms.append(self._build_match_arm(child))
614
+
615
+ return nodes.MatchExprNode(
616
+ scrutinee=scrutinee,
617
+ arms=tuple(arms),
618
+ ensure_exhaustiveness=True,
619
+ source_location=self._loc(node),
620
+ )
621
+
622
+ def _build_match_arm(self, node) -> nodes.MatchArm:
623
+ """Build MatchArm from match_arm node."""
624
+ pattern_node = self._child_by_field(node, "pattern")
625
+ guard_node = self._child_by_field(node, "guard")
626
+ body_node = self._child_by_field(node, "body")
627
+
628
+ pattern = self._build_pattern(pattern_node) if pattern_node else nodes.WildcardPattern()
629
+ guard = self._build_expression(guard_node) if guard_node else None
630
+ body = self._build_expression(body_node) if body_node else nodes.PassExprNode()
631
+
632
+ return nodes.MatchArm(
633
+ pattern=pattern,
634
+ guard=guard,
635
+ body=body,
636
+ source_location=self._loc(node),
637
+ )
638
+
639
+ def _build_pattern(self, node) -> nodes.PatternNode:
640
+ """Build a pattern node."""
641
+ if node.type == "wildcard_pattern" or self._text(node) == "_":
642
+ return nodes.WildcardPattern(source_location=self._loc(node))
643
+ elif node.type == "literal_pattern":
644
+ literal_child = node.children[0] if node.children else None
645
+ return nodes.LiteralPattern(
646
+ literal=self._build_expression(literal_child) if literal_child else nodes.PassExprNode(),
647
+ source_location=self._loc(node),
648
+ )
649
+ elif node.type == "binding_pattern" or node.type == "identifier":
650
+ return nodes.BindingPattern(
651
+ name=self._text(node),
652
+ source_location=self._loc(node),
653
+ )
654
+ elif node.type == "struct_pattern":
655
+ type_name_node = self._child_by_field(node, "type_name")
656
+ type_name = self._text(type_name_node) if type_name_node else ""
657
+
658
+ field_patterns: List[nodes.FieldPattern] = []
659
+ for child in node.children:
660
+ if child.type == "field_pattern":
661
+ fp = self._build_field_pattern(child)
662
+ field_patterns.append(fp)
663
+
664
+ return nodes.StructPattern(
665
+ type_name=type_name,
666
+ fields=tuple(field_patterns),
667
+ source_location=self._loc(node),
668
+ )
669
+ else:
670
+ # Try to match as literal or identifier
671
+ text = self._text(node)
672
+ if text == "_":
673
+ return nodes.WildcardPattern(source_location=self._loc(node))
674
+ elif text in ("TRUE", "FALSE"):
675
+ return nodes.LiteralPattern(
676
+ literal=nodes.BoolLit(value=(text == "TRUE"), source_location=self._loc(node)),
677
+ source_location=self._loc(node),
678
+ )
679
+ else:
680
+ return nodes.BindingPattern(
681
+ name=text,
682
+ source_location=self._loc(node),
683
+ )
684
+
685
+ def _build_field_pattern(self, node) -> nodes.FieldPattern:
686
+ """Build FieldPattern from field_pattern node."""
687
+ name_node = self._child_by_field(node, "name")
688
+ pattern_node = self._child_by_field(node, "pattern")
689
+
690
+ name = self._text(name_node) if name_node else ""
691
+ pattern = self._build_pattern(pattern_node) if pattern_node else None
692
+
693
+ return nodes.FieldPattern(
694
+ name=name,
695
+ pattern=pattern,
696
+ source_location=self._loc(node),
697
+ )
698
+
699
+ # =========================================================================
700
+ # Struct literal
701
+ # =========================================================================
702
+
703
+ def _build_struct_literal(self, node) -> nodes.StructLiteralNode:
704
+ """Build StructLiteralNode from struct_literal node."""
705
+ type_name_node = self._child_by_field(node, "type_name")
706
+ struct_name = self._text(type_name_node) if type_name_node else None
707
+
708
+ field_values: List[nodes.FieldAssignment] = []
709
+ for child in node.children:
710
+ if child.type == "field_assignment":
711
+ field_values.append(self._build_field_assignment(child))
712
+
713
+ return nodes.StructLiteralNode(
714
+ struct_name=struct_name,
715
+ field_values=tuple(field_values),
716
+ source_location=self._loc(node),
717
+ )
718
+
719
+ def _build_field_assignment(self, node) -> nodes.FieldAssignment:
720
+ """Build FieldAssignment from field_assignment node."""
721
+ name_node = self._child_by_field(node, "name")
722
+ value_node = self._child_by_field(node, "value")
723
+
724
+ name = self._text(name_node) if name_node else ""
725
+ value = self._build_expression(value_node) if value_node else nodes.PassExprNode()
726
+
727
+ return nodes.FieldAssignment(
728
+ name=name,
729
+ value=value,
730
+ source_location=self._loc(node),
731
+ )
732
+
733
+ # =========================================================================
734
+ # Types
735
+ # =========================================================================
736
+
737
+ def _build_type(self, node) -> nodes.TypeNode:
738
+ """Build a type node."""
739
+ if node is None:
740
+ return nodes.BuiltinType(name="void")
741
+
742
+ node_type = node.type
743
+
744
+ if node_type == "builtin_type":
745
+ return nodes.BuiltinType(
746
+ name=self._text(node),
747
+ source_location=self._loc(node),
748
+ )
749
+ elif node_type == "identifier":
750
+ text = self._text(node)
751
+ if text in ("int", "float", "bool", "string", "money", "percent", "date", "duration", "void"):
752
+ return nodes.BuiltinType(name=text, source_location=self._loc(node))
753
+ return nodes.NamedType(
754
+ name=text,
755
+ source_location=self._loc(node),
756
+ )
757
+ elif node_type == "generic_type":
758
+ base_node = node.children[0] if node.children else None
759
+ base = self._text(base_node) if base_node else ""
760
+
761
+ type_args: List[nodes.TypeNode] = []
762
+ for child in node.children:
763
+ if child.type not in ("<", ">", ",") and child != base_node:
764
+ type_args.append(self._build_type(child))
765
+
766
+ return nodes.GenericType(
767
+ base=base,
768
+ type_args=tuple(type_args),
769
+ source_location=self._loc(node),
770
+ )
771
+ elif node_type == "optional_type":
772
+ inner = node.children[0] if node.children else None
773
+ return nodes.OptionalType(
774
+ inner=self._build_type(inner),
775
+ source_location=self._loc(node),
776
+ )
777
+ elif node_type == "array_type":
778
+ elem = None
779
+ for child in node.children:
780
+ if child.type not in ("[", "]"):
781
+ elem = child
782
+ break
783
+ return nodes.ArrayType(
784
+ element_type=self._build_type(elem),
785
+ source_location=self._loc(node),
786
+ )
787
+ else:
788
+ # Fallback - treat as identifier type
789
+ text = self._text(node)
790
+ if text in ("int", "float", "bool", "string", "money", "percent", "date", "duration", "void"):
791
+ return nodes.BuiltinType(name=text, source_location=self._loc(node))
792
+ return nodes.NamedType(name=text, source_location=self._loc(node))
793
+
794
+ # =========================================================================
795
+ # Statute blocks
796
+ # =========================================================================
797
+
798
+ def _build_statute(self, node) -> nodes.StatuteNode:
799
+ """Build StatuteNode from statute_block node."""
800
+ section_node = self._child_by_field(node, "section_number")
801
+ title_node = self._child_by_field(node, "title")
802
+
803
+ section_number = self._text(section_node) if section_node else ""
804
+ title = self._build_string_lit(title_node) if title_node else None
805
+
806
+ definitions: List[nodes.DefinitionEntry] = []
807
+ elements: List[nodes.ElementNode] = []
808
+ penalty: Optional[nodes.PenaltyNode] = None
809
+ illustrations: List[nodes.IllustrationNode] = []
810
+
811
+ for child in node.children:
812
+ if child.type == "definitions_block":
813
+ definitions.extend(self._build_definitions_block(child))
814
+ elif child.type == "elements_block":
815
+ elements.extend(self._build_elements_block(child))
816
+ elif child.type == "penalty_block":
817
+ penalty = self._build_penalty_block(child)
818
+ elif child.type == "illustration_block":
819
+ illustrations.append(self._build_illustration(child))
820
+
821
+ return nodes.StatuteNode(
822
+ section_number=section_number,
823
+ title=title,
824
+ definitions=tuple(definitions),
825
+ elements=tuple(elements),
826
+ penalty=penalty,
827
+ illustrations=tuple(illustrations),
828
+ source_location=self._loc(node),
829
+ )
830
+
831
+ def _build_definitions_block(self, node) -> List[nodes.DefinitionEntry]:
832
+ """Build list of DefinitionEntry from definitions_block node."""
833
+ entries: List[nodes.DefinitionEntry] = []
834
+ for child in node.children:
835
+ if child.type == "definition_entry":
836
+ term_node = self._child_by_field(child, "term")
837
+ def_node = self._child_by_field(child, "definition")
838
+
839
+ term = self._text(term_node) if term_node else ""
840
+ definition = self._build_string_lit(def_node) if def_node else nodes.StringLit(value="")
841
+
842
+ entries.append(nodes.DefinitionEntry(
843
+ term=term,
844
+ definition=definition,
845
+ source_location=self._loc(child),
846
+ ))
847
+ return entries
848
+
849
+ def _build_elements_block(self, node) -> List[nodes.ElementNode]:
850
+ """Build list of ElementNode from elements_block node."""
851
+ elements: List[nodes.ElementNode] = []
852
+ for child in node.children:
853
+ if child.type == "element_entry":
854
+ type_node = self._child_by_field(child, "element_type")
855
+ name_node = self._child_by_field(child, "name")
856
+ desc_node = self._child_by_field(child, "description")
857
+
858
+ elem_type = self._text(type_node) if type_node else "actus_reus"
859
+ name = self._text(name_node) if name_node else ""
860
+ description = self._build_expression(desc_node) if desc_node else nodes.StringLit(value="")
861
+
862
+ elements.append(nodes.ElementNode(
863
+ element_type=elem_type,
864
+ name=name,
865
+ description=description,
866
+ source_location=self._loc(child),
867
+ ))
868
+ return elements
869
+
870
+ def _build_penalty_block(self, node) -> nodes.PenaltyNode:
871
+ """Build PenaltyNode from penalty_block node."""
872
+ imprisonment_min = imprisonment_max = None
873
+ fine_min = fine_max = None
874
+ supplementary = None
875
+
876
+ for child in node.children:
877
+ if child.type == "imprisonment_clause":
878
+ # Check for range or single value
879
+ duration_nodes = self._children_by_type(child, "duration_literal")
880
+ range_node = self._child_by_type(child, "duration_range")
881
+ if range_node:
882
+ durs = self._children_by_type(range_node, "duration_literal")
883
+ if len(durs) >= 2:
884
+ imprisonment_min = self._build_duration(durs[0])
885
+ imprisonment_max = self._build_duration(durs[1])
886
+ elif duration_nodes:
887
+ imprisonment_max = self._build_duration(duration_nodes[0])
888
+
889
+ elif child.type == "fine_clause":
890
+ money_nodes = self._children_by_type(child, "money_literal")
891
+ range_node = self._child_by_type(child, "money_range")
892
+ if range_node:
893
+ moneys = self._children_by_type(range_node, "money_literal")
894
+ if len(moneys) >= 2:
895
+ fine_min = self._build_money(moneys[0])
896
+ fine_max = self._build_money(moneys[1])
897
+ elif money_nodes:
898
+ fine_max = self._build_money(money_nodes[0])
899
+
900
+ elif child.type == "supplementary_clause":
901
+ string_node = self._child_by_type(child, "string_literal")
902
+ if string_node:
903
+ supplementary = self._build_string_lit(string_node)
904
+
905
+ return nodes.PenaltyNode(
906
+ imprisonment_min=imprisonment_min,
907
+ imprisonment_max=imprisonment_max,
908
+ fine_min=fine_min,
909
+ fine_max=fine_max,
910
+ supplementary=supplementary,
911
+ source_location=self._loc(node),
912
+ )
913
+
914
+ def _build_illustration(self, node) -> nodes.IllustrationNode:
915
+ """Build IllustrationNode from illustration_block node."""
916
+ label_node = self._child_by_field(node, "label")
917
+ desc_node = self._child_by_field(node, "description")
918
+
919
+ label = self._text(label_node) if label_node else None
920
+ description = self._build_string_lit(desc_node) if desc_node else nodes.StringLit(value="")
921
+
922
+ return nodes.IllustrationNode(
923
+ label=label,
924
+ description=description,
925
+ source_location=self._loc(node),
926
+ )