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.
- yuho/__init__.py +16 -0
- yuho/ast/__init__.py +196 -0
- yuho/ast/builder.py +926 -0
- yuho/ast/constant_folder.py +280 -0
- yuho/ast/dead_code.py +199 -0
- yuho/ast/exhaustiveness.py +503 -0
- yuho/ast/nodes.py +907 -0
- yuho/ast/overlap.py +291 -0
- yuho/ast/reachability.py +293 -0
- yuho/ast/scope_analysis.py +490 -0
- yuho/ast/transformer.py +490 -0
- yuho/ast/type_check.py +471 -0
- yuho/ast/type_inference.py +425 -0
- yuho/ast/visitor.py +239 -0
- yuho/cli/__init__.py +14 -0
- yuho/cli/commands/__init__.py +1 -0
- yuho/cli/commands/api.py +431 -0
- yuho/cli/commands/ast_viz.py +334 -0
- yuho/cli/commands/check.py +218 -0
- yuho/cli/commands/config.py +311 -0
- yuho/cli/commands/contribute.py +122 -0
- yuho/cli/commands/diff.py +487 -0
- yuho/cli/commands/explain.py +240 -0
- yuho/cli/commands/fmt.py +253 -0
- yuho/cli/commands/generate.py +316 -0
- yuho/cli/commands/graph.py +410 -0
- yuho/cli/commands/init.py +120 -0
- yuho/cli/commands/library.py +656 -0
- yuho/cli/commands/lint.py +503 -0
- yuho/cli/commands/lsp.py +36 -0
- yuho/cli/commands/preview.py +377 -0
- yuho/cli/commands/repl.py +444 -0
- yuho/cli/commands/serve.py +44 -0
- yuho/cli/commands/test.py +528 -0
- yuho/cli/commands/transpile.py +121 -0
- yuho/cli/commands/wizard.py +370 -0
- yuho/cli/completions.py +182 -0
- yuho/cli/error_formatter.py +193 -0
- yuho/cli/main.py +1064 -0
- yuho/config/__init__.py +46 -0
- yuho/config/loader.py +235 -0
- yuho/config/mask.py +194 -0
- yuho/config/schema.py +147 -0
- yuho/library/__init__.py +84 -0
- yuho/library/index.py +328 -0
- yuho/library/install.py +699 -0
- yuho/library/lockfile.py +330 -0
- yuho/library/package.py +421 -0
- yuho/library/resolver.py +791 -0
- yuho/library/signature.py +335 -0
- yuho/llm/__init__.py +45 -0
- yuho/llm/config.py +75 -0
- yuho/llm/factory.py +123 -0
- yuho/llm/prompts.py +146 -0
- yuho/llm/providers.py +383 -0
- yuho/llm/utils.py +470 -0
- yuho/lsp/__init__.py +14 -0
- yuho/lsp/code_action_handler.py +518 -0
- yuho/lsp/completion_handler.py +85 -0
- yuho/lsp/diagnostics.py +100 -0
- yuho/lsp/hover_handler.py +130 -0
- yuho/lsp/server.py +1425 -0
- yuho/mcp/__init__.py +10 -0
- yuho/mcp/server.py +1452 -0
- yuho/parser/__init__.py +8 -0
- yuho/parser/source_location.py +108 -0
- yuho/parser/wrapper.py +311 -0
- yuho/testing/__init__.py +48 -0
- yuho/testing/coverage.py +274 -0
- yuho/testing/fixtures.py +263 -0
- yuho/transpile/__init__.py +52 -0
- yuho/transpile/alloy_transpiler.py +546 -0
- yuho/transpile/base.py +100 -0
- yuho/transpile/blocks_transpiler.py +338 -0
- yuho/transpile/english_transpiler.py +470 -0
- yuho/transpile/graphql_transpiler.py +404 -0
- yuho/transpile/json_transpiler.py +217 -0
- yuho/transpile/jsonld_transpiler.py +250 -0
- yuho/transpile/latex_preamble.py +161 -0
- yuho/transpile/latex_transpiler.py +406 -0
- yuho/transpile/latex_utils.py +206 -0
- yuho/transpile/mermaid_transpiler.py +357 -0
- yuho/transpile/registry.py +275 -0
- yuho/verify/__init__.py +43 -0
- yuho/verify/alloy.py +352 -0
- yuho/verify/combined.py +218 -0
- yuho/verify/z3_solver.py +1155 -0
- yuho-5.0.0.dist-info/METADATA +186 -0
- yuho-5.0.0.dist-info/RECORD +91 -0
- yuho-5.0.0.dist-info/WHEEL +4 -0
- 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
|
+
)
|