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/nodes.py
ADDED
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST node definitions for Yuho v5.
|
|
3
|
+
|
|
4
|
+
All nodes are immutable dataclasses with source_location tracking
|
|
5
|
+
and an accept(visitor) method for the Visitor pattern.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import date
|
|
12
|
+
from decimal import Decimal
|
|
13
|
+
from enum import Enum, auto
|
|
14
|
+
from typing import TYPE_CHECKING, Optional, List, Dict, Tuple, Union
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from yuho.ast.visitor import Visitor
|
|
18
|
+
from yuho.parser.source_location import SourceLocation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# =============================================================================
|
|
22
|
+
# Currency Enum
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Currency(Enum):
|
|
27
|
+
"""Supported currency types for MoneyNode."""
|
|
28
|
+
|
|
29
|
+
SGD = auto()
|
|
30
|
+
USD = auto()
|
|
31
|
+
EUR = auto()
|
|
32
|
+
GBP = auto()
|
|
33
|
+
JPY = auto()
|
|
34
|
+
CNY = auto()
|
|
35
|
+
INR = auto()
|
|
36
|
+
AUD = auto()
|
|
37
|
+
CAD = auto()
|
|
38
|
+
CHF = auto()
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_symbol(cls, symbol: str) -> "Currency":
|
|
42
|
+
"""Convert currency symbol to Currency enum."""
|
|
43
|
+
mapping = {
|
|
44
|
+
"$": cls.SGD, # Default $ to SGD for Singapore context
|
|
45
|
+
"£": cls.GBP,
|
|
46
|
+
"€": cls.EUR,
|
|
47
|
+
"¥": cls.JPY,
|
|
48
|
+
"₹": cls.INR,
|
|
49
|
+
"SGD": cls.SGD,
|
|
50
|
+
"USD": cls.USD,
|
|
51
|
+
"EUR": cls.EUR,
|
|
52
|
+
"GBP": cls.GBP,
|
|
53
|
+
"JPY": cls.JPY,
|
|
54
|
+
"CNY": cls.CNY,
|
|
55
|
+
"INR": cls.INR,
|
|
56
|
+
"AUD": cls.AUD,
|
|
57
|
+
"CAD": cls.CAD,
|
|
58
|
+
"CHF": cls.CHF,
|
|
59
|
+
}
|
|
60
|
+
return mapping.get(symbol, cls.USD)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# =============================================================================
|
|
64
|
+
# Base AST Node
|
|
65
|
+
# =============================================================================
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class ASTNode(ABC):
|
|
70
|
+
"""
|
|
71
|
+
Base class for all AST nodes.
|
|
72
|
+
|
|
73
|
+
All nodes are immutable (frozen dataclass) and carry source location
|
|
74
|
+
information for error reporting and IDE features.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
source_location: Optional["SourceLocation"] = field(default=None, compare=False)
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def accept(self, visitor: "Visitor"):
|
|
81
|
+
"""Accept a visitor for double-dispatch traversal."""
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def children(self) -> List["ASTNode"]:
|
|
85
|
+
"""Return child nodes for generic traversal. Override in subclasses."""
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Type Nodes
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class TypeNode(ASTNode):
|
|
96
|
+
"""Base class for type annotations."""
|
|
97
|
+
|
|
98
|
+
def accept(self, visitor: "Visitor"):
|
|
99
|
+
return visitor.visit_type(self)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class BuiltinType(TypeNode):
|
|
104
|
+
"""Built-in primitive type (int, float, bool, string, money, etc.)."""
|
|
105
|
+
|
|
106
|
+
name: str # "int", "float", "bool", "string", "money", "percent", "date", "duration", "void"
|
|
107
|
+
|
|
108
|
+
def accept(self, visitor: "Visitor"):
|
|
109
|
+
return visitor.visit_builtin_type(self)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class NamedType(TypeNode):
|
|
114
|
+
"""User-defined type reference (struct name)."""
|
|
115
|
+
|
|
116
|
+
name: str
|
|
117
|
+
|
|
118
|
+
def accept(self, visitor: "Visitor"):
|
|
119
|
+
return visitor.visit_named_type(self)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class GenericType(TypeNode):
|
|
124
|
+
"""Generic type application, e.g., List<int>."""
|
|
125
|
+
|
|
126
|
+
base: str
|
|
127
|
+
type_args: Tuple[TypeNode, ...]
|
|
128
|
+
|
|
129
|
+
def accept(self, visitor: "Visitor"):
|
|
130
|
+
return visitor.visit_generic_type(self)
|
|
131
|
+
|
|
132
|
+
def children(self) -> List[ASTNode]:
|
|
133
|
+
return list(self.type_args)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class OptionalType(TypeNode):
|
|
138
|
+
"""Optional type, e.g., int?."""
|
|
139
|
+
|
|
140
|
+
inner: TypeNode
|
|
141
|
+
|
|
142
|
+
def accept(self, visitor: "Visitor"):
|
|
143
|
+
return visitor.visit_optional_type(self)
|
|
144
|
+
|
|
145
|
+
def children(self) -> List[ASTNode]:
|
|
146
|
+
return [self.inner]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class ArrayType(TypeNode):
|
|
151
|
+
"""Array type, e.g., [int]."""
|
|
152
|
+
|
|
153
|
+
element_type: TypeNode
|
|
154
|
+
|
|
155
|
+
def accept(self, visitor: "Visitor"):
|
|
156
|
+
return visitor.visit_array_type(self)
|
|
157
|
+
|
|
158
|
+
def children(self) -> List[ASTNode]:
|
|
159
|
+
return [self.element_type]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# =============================================================================
|
|
163
|
+
# Literal Nodes
|
|
164
|
+
# =============================================================================
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass(frozen=True)
|
|
168
|
+
class IntLit(ASTNode):
|
|
169
|
+
"""Integer literal."""
|
|
170
|
+
|
|
171
|
+
value: int
|
|
172
|
+
|
|
173
|
+
def accept(self, visitor: "Visitor"):
|
|
174
|
+
return visitor.visit_int_lit(self)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass(frozen=True)
|
|
178
|
+
class FloatLit(ASTNode):
|
|
179
|
+
"""Floating-point literal."""
|
|
180
|
+
|
|
181
|
+
value: float
|
|
182
|
+
|
|
183
|
+
def accept(self, visitor: "Visitor"):
|
|
184
|
+
return visitor.visit_float_lit(self)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class BoolLit(ASTNode):
|
|
189
|
+
"""Boolean literal (TRUE/FALSE)."""
|
|
190
|
+
|
|
191
|
+
value: bool
|
|
192
|
+
|
|
193
|
+
def accept(self, visitor: "Visitor"):
|
|
194
|
+
return visitor.visit_bool_lit(self)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True)
|
|
198
|
+
class StringLit(ASTNode):
|
|
199
|
+
"""String literal with escape sequences processed."""
|
|
200
|
+
|
|
201
|
+
value: str
|
|
202
|
+
|
|
203
|
+
def accept(self, visitor: "Visitor"):
|
|
204
|
+
return visitor.visit_string_lit(self)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass(frozen=True)
|
|
208
|
+
class MoneyNode(ASTNode):
|
|
209
|
+
"""
|
|
210
|
+
Money literal with currency and amount.
|
|
211
|
+
|
|
212
|
+
The amount is stored as Decimal for precise financial calculations.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
currency: Currency
|
|
216
|
+
amount: Decimal
|
|
217
|
+
|
|
218
|
+
def __post_init__(self):
|
|
219
|
+
if not isinstance(self.amount, Decimal):
|
|
220
|
+
object.__setattr__(self, "amount", Decimal(str(self.amount)))
|
|
221
|
+
|
|
222
|
+
def accept(self, visitor: "Visitor"):
|
|
223
|
+
return visitor.visit_money(self)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass(frozen=True)
|
|
227
|
+
class PercentNode(ASTNode):
|
|
228
|
+
"""
|
|
229
|
+
Percentage literal.
|
|
230
|
+
|
|
231
|
+
The value is stored as Decimal and validated to be in 0-100 range.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
value: Decimal
|
|
235
|
+
|
|
236
|
+
def __post_init__(self):
|
|
237
|
+
if not isinstance(self.value, Decimal):
|
|
238
|
+
object.__setattr__(self, "value", Decimal(str(self.value)))
|
|
239
|
+
if not (0 <= self.value <= 100):
|
|
240
|
+
raise ValueError(f"Percent value must be 0-100, got {self.value}")
|
|
241
|
+
|
|
242
|
+
def accept(self, visitor: "Visitor"):
|
|
243
|
+
return visitor.visit_percent(self)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True)
|
|
247
|
+
class DateNode(ASTNode):
|
|
248
|
+
"""
|
|
249
|
+
Date literal in ISO8601 format (YYYY-MM-DD).
|
|
250
|
+
|
|
251
|
+
Wraps datetime.date for date operations.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
value: date
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def from_iso8601(cls, date_str: str, source_location=None) -> "DateNode":
|
|
258
|
+
"""Parse ISO8601 date string."""
|
|
259
|
+
return cls(value=date.fromisoformat(date_str), source_location=source_location)
|
|
260
|
+
|
|
261
|
+
def accept(self, visitor: "Visitor"):
|
|
262
|
+
return visitor.visit_date(self)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass(frozen=True)
|
|
266
|
+
class DurationNode(ASTNode):
|
|
267
|
+
"""
|
|
268
|
+
Duration literal with years, months, days, hours, minutes, seconds.
|
|
269
|
+
|
|
270
|
+
Any component can be 0. The total duration is the sum of all components.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
years: int = 0
|
|
274
|
+
months: int = 0
|
|
275
|
+
days: int = 0
|
|
276
|
+
hours: int = 0
|
|
277
|
+
minutes: int = 0
|
|
278
|
+
seconds: int = 0
|
|
279
|
+
|
|
280
|
+
def accept(self, visitor: "Visitor"):
|
|
281
|
+
return visitor.visit_duration(self)
|
|
282
|
+
|
|
283
|
+
def total_days(self) -> int:
|
|
284
|
+
"""Approximate total days (assumes 30 days/month, 365 days/year)."""
|
|
285
|
+
return self.years * 365 + self.months * 30 + self.days
|
|
286
|
+
|
|
287
|
+
def __str__(self) -> str:
|
|
288
|
+
parts = []
|
|
289
|
+
if self.years:
|
|
290
|
+
parts.append(f"{self.years} year{'s' if self.years != 1 else ''}")
|
|
291
|
+
if self.months:
|
|
292
|
+
parts.append(f"{self.months} month{'s' if self.months != 1 else ''}")
|
|
293
|
+
if self.days:
|
|
294
|
+
parts.append(f"{self.days} day{'s' if self.days != 1 else ''}")
|
|
295
|
+
if self.hours:
|
|
296
|
+
parts.append(f"{self.hours} hour{'s' if self.hours != 1 else ''}")
|
|
297
|
+
if self.minutes:
|
|
298
|
+
parts.append(f"{self.minutes} minute{'s' if self.minutes != 1 else ''}")
|
|
299
|
+
if self.seconds:
|
|
300
|
+
parts.append(f"{self.seconds} second{'s' if self.seconds != 1 else ''}")
|
|
301
|
+
return ", ".join(parts) if parts else "0 days"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# =============================================================================
|
|
305
|
+
# Expression Nodes
|
|
306
|
+
# =============================================================================
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@dataclass(frozen=True)
|
|
310
|
+
class IdentifierNode(ASTNode):
|
|
311
|
+
"""Identifier reference."""
|
|
312
|
+
|
|
313
|
+
name: str
|
|
314
|
+
|
|
315
|
+
def accept(self, visitor: "Visitor"):
|
|
316
|
+
return visitor.visit_identifier(self)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@dataclass(frozen=True)
|
|
320
|
+
class FieldAccessNode(ASTNode):
|
|
321
|
+
"""Field access expression: base.field."""
|
|
322
|
+
|
|
323
|
+
base: ASTNode # ExprNode
|
|
324
|
+
field_name: str
|
|
325
|
+
|
|
326
|
+
def accept(self, visitor: "Visitor"):
|
|
327
|
+
return visitor.visit_field_access(self)
|
|
328
|
+
|
|
329
|
+
def children(self) -> List[ASTNode]:
|
|
330
|
+
return [self.base]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@dataclass(frozen=True)
|
|
334
|
+
class IndexAccessNode(ASTNode):
|
|
335
|
+
"""Index access expression: base[index]."""
|
|
336
|
+
|
|
337
|
+
base: ASTNode # ExprNode
|
|
338
|
+
index: ASTNode # ExprNode
|
|
339
|
+
|
|
340
|
+
def accept(self, visitor: "Visitor"):
|
|
341
|
+
return visitor.visit_index_access(self)
|
|
342
|
+
|
|
343
|
+
def children(self) -> List[ASTNode]:
|
|
344
|
+
return [self.base, self.index]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@dataclass(frozen=True)
|
|
348
|
+
class FunctionCallNode(ASTNode):
|
|
349
|
+
"""Function call expression: callee(args...)."""
|
|
350
|
+
|
|
351
|
+
callee: Union[IdentifierNode, FieldAccessNode]
|
|
352
|
+
args: Tuple[ASTNode, ...]
|
|
353
|
+
|
|
354
|
+
def accept(self, visitor: "Visitor"):
|
|
355
|
+
return visitor.visit_function_call(self)
|
|
356
|
+
|
|
357
|
+
def children(self) -> List[ASTNode]:
|
|
358
|
+
return [self.callee] + list(self.args)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@dataclass(frozen=True)
|
|
362
|
+
class BinaryExprNode(ASTNode):
|
|
363
|
+
"""Binary expression: left op right."""
|
|
364
|
+
|
|
365
|
+
left: ASTNode
|
|
366
|
+
operator: str # "+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=", "&&", "||"
|
|
367
|
+
right: ASTNode
|
|
368
|
+
|
|
369
|
+
def accept(self, visitor: "Visitor"):
|
|
370
|
+
return visitor.visit_binary_expr(self)
|
|
371
|
+
|
|
372
|
+
def children(self) -> List[ASTNode]:
|
|
373
|
+
return [self.left, self.right]
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@dataclass(frozen=True)
|
|
377
|
+
class UnaryExprNode(ASTNode):
|
|
378
|
+
"""Unary expression: op operand."""
|
|
379
|
+
|
|
380
|
+
operator: str # "!", "-"
|
|
381
|
+
operand: ASTNode
|
|
382
|
+
|
|
383
|
+
def accept(self, visitor: "Visitor"):
|
|
384
|
+
return visitor.visit_unary_expr(self)
|
|
385
|
+
|
|
386
|
+
def children(self) -> List[ASTNode]:
|
|
387
|
+
return [self.operand]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@dataclass(frozen=True)
|
|
391
|
+
class PassExprNode(ASTNode):
|
|
392
|
+
"""Pass expression (null/none equivalent)."""
|
|
393
|
+
|
|
394
|
+
def accept(self, visitor: "Visitor"):
|
|
395
|
+
return visitor.visit_pass_expr(self)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# =============================================================================
|
|
399
|
+
# Pattern Nodes
|
|
400
|
+
# =============================================================================
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@dataclass(frozen=True)
|
|
404
|
+
class PatternNode(ASTNode):
|
|
405
|
+
"""Base class for match patterns."""
|
|
406
|
+
|
|
407
|
+
def accept(self, visitor: "Visitor"):
|
|
408
|
+
return visitor.visit_pattern(self)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@dataclass(frozen=True)
|
|
412
|
+
class WildcardPattern(PatternNode):
|
|
413
|
+
"""Wildcard pattern (_) that matches anything."""
|
|
414
|
+
|
|
415
|
+
def accept(self, visitor: "Visitor"):
|
|
416
|
+
return visitor.visit_wildcard_pattern(self)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@dataclass(frozen=True)
|
|
420
|
+
class LiteralPattern(PatternNode):
|
|
421
|
+
"""Literal pattern that matches a specific value."""
|
|
422
|
+
|
|
423
|
+
literal: ASTNode # IntLit, StringLit, BoolLit, etc.
|
|
424
|
+
|
|
425
|
+
def accept(self, visitor: "Visitor"):
|
|
426
|
+
return visitor.visit_literal_pattern(self)
|
|
427
|
+
|
|
428
|
+
def children(self) -> List[ASTNode]:
|
|
429
|
+
return [self.literal]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@dataclass(frozen=True)
|
|
433
|
+
class BindingPattern(PatternNode):
|
|
434
|
+
"""Binding pattern that captures the matched value."""
|
|
435
|
+
|
|
436
|
+
name: str
|
|
437
|
+
|
|
438
|
+
def accept(self, visitor: "Visitor"):
|
|
439
|
+
return visitor.visit_binding_pattern(self)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@dataclass(frozen=True)
|
|
443
|
+
class FieldPattern(ASTNode):
|
|
444
|
+
"""Field pattern within a struct pattern."""
|
|
445
|
+
|
|
446
|
+
name: str
|
|
447
|
+
pattern: Optional[PatternNode] = None # None means just match by name
|
|
448
|
+
|
|
449
|
+
def accept(self, visitor: "Visitor"):
|
|
450
|
+
return visitor.visit_field_pattern(self)
|
|
451
|
+
|
|
452
|
+
def children(self) -> List[ASTNode]:
|
|
453
|
+
return [self.pattern] if self.pattern else []
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@dataclass(frozen=True)
|
|
457
|
+
class StructPattern(PatternNode):
|
|
458
|
+
"""Struct pattern for destructuring."""
|
|
459
|
+
|
|
460
|
+
type_name: str
|
|
461
|
+
fields: Tuple[FieldPattern, ...]
|
|
462
|
+
|
|
463
|
+
def accept(self, visitor: "Visitor"):
|
|
464
|
+
return visitor.visit_struct_pattern(self)
|
|
465
|
+
|
|
466
|
+
def children(self) -> List[ASTNode]:
|
|
467
|
+
return list(self.fields)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
# =============================================================================
|
|
471
|
+
# Match Expression
|
|
472
|
+
# =============================================================================
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@dataclass(frozen=True)
|
|
476
|
+
class MatchArm(ASTNode):
|
|
477
|
+
"""A single arm in a match expression."""
|
|
478
|
+
|
|
479
|
+
pattern: PatternNode
|
|
480
|
+
guard: Optional[ASTNode] = None # Optional guard expression
|
|
481
|
+
body: ASTNode = field(default_factory=PassExprNode) # ExprNode
|
|
482
|
+
|
|
483
|
+
def accept(self, visitor: "Visitor"):
|
|
484
|
+
return visitor.visit_match_arm(self)
|
|
485
|
+
|
|
486
|
+
def children(self) -> List[ASTNode]:
|
|
487
|
+
result = [self.pattern]
|
|
488
|
+
if self.guard:
|
|
489
|
+
result.append(self.guard)
|
|
490
|
+
result.append(self.body)
|
|
491
|
+
return result
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@dataclass(frozen=True)
|
|
495
|
+
class MatchExprNode(ASTNode):
|
|
496
|
+
"""
|
|
497
|
+
Match expression with scrutinee and arms.
|
|
498
|
+
|
|
499
|
+
The ensure_exhaustiveness flag indicates whether the type checker
|
|
500
|
+
should verify that all cases are covered.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
scrutinee: Optional[ASTNode] # None for bare match blocks
|
|
504
|
+
arms: Tuple[MatchArm, ...]
|
|
505
|
+
ensure_exhaustiveness: bool = True
|
|
506
|
+
|
|
507
|
+
def accept(self, visitor: "Visitor"):
|
|
508
|
+
return visitor.visit_match_expr(self)
|
|
509
|
+
|
|
510
|
+
def children(self) -> List[ASTNode]:
|
|
511
|
+
result = []
|
|
512
|
+
if self.scrutinee:
|
|
513
|
+
result.append(self.scrutinee)
|
|
514
|
+
result.extend(self.arms)
|
|
515
|
+
return result
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# =============================================================================
|
|
519
|
+
# Struct Definition and Literal
|
|
520
|
+
# =============================================================================
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@dataclass(frozen=True)
|
|
524
|
+
class FieldDef(ASTNode):
|
|
525
|
+
"""Field definition within a struct."""
|
|
526
|
+
|
|
527
|
+
type_annotation: TypeNode
|
|
528
|
+
name: str
|
|
529
|
+
|
|
530
|
+
def accept(self, visitor: "Visitor"):
|
|
531
|
+
return visitor.visit_field_def(self)
|
|
532
|
+
|
|
533
|
+
def children(self) -> List[ASTNode]:
|
|
534
|
+
return [self.type_annotation]
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@dataclass(frozen=True)
|
|
538
|
+
class StructDefNode(ASTNode):
|
|
539
|
+
"""Struct type definition."""
|
|
540
|
+
|
|
541
|
+
name: str
|
|
542
|
+
fields: Tuple[FieldDef, ...]
|
|
543
|
+
type_params: Tuple[str, ...] = () # Generic type parameters
|
|
544
|
+
|
|
545
|
+
def accept(self, visitor: "Visitor"):
|
|
546
|
+
return visitor.visit_struct_def(self)
|
|
547
|
+
|
|
548
|
+
def children(self) -> List[ASTNode]:
|
|
549
|
+
return list(self.fields)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@dataclass(frozen=True)
|
|
553
|
+
class FieldAssignment(ASTNode):
|
|
554
|
+
"""Field assignment within a struct literal."""
|
|
555
|
+
|
|
556
|
+
name: str
|
|
557
|
+
value: ASTNode
|
|
558
|
+
|
|
559
|
+
def accept(self, visitor: "Visitor"):
|
|
560
|
+
return visitor.visit_field_assignment(self)
|
|
561
|
+
|
|
562
|
+
def children(self) -> List[ASTNode]:
|
|
563
|
+
return [self.value]
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@dataclass(frozen=True)
|
|
567
|
+
class StructLiteralNode(ASTNode):
|
|
568
|
+
"""Struct literal instantiation."""
|
|
569
|
+
|
|
570
|
+
struct_name: Optional[str] # None for anonymous struct literals
|
|
571
|
+
field_values: Tuple[FieldAssignment, ...]
|
|
572
|
+
|
|
573
|
+
def accept(self, visitor: "Visitor"):
|
|
574
|
+
return visitor.visit_struct_literal(self)
|
|
575
|
+
|
|
576
|
+
def children(self) -> List[ASTNode]:
|
|
577
|
+
return list(self.field_values)
|
|
578
|
+
|
|
579
|
+
def get_field(self, name: str) -> Optional[FieldAssignment]:
|
|
580
|
+
"""Get a field assignment by name."""
|
|
581
|
+
for fa in self.field_values:
|
|
582
|
+
if fa.name == name:
|
|
583
|
+
return fa
|
|
584
|
+
return None
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
# =============================================================================
|
|
588
|
+
# Function Definition
|
|
589
|
+
# =============================================================================
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@dataclass(frozen=True)
|
|
593
|
+
class ParamDef(ASTNode):
|
|
594
|
+
"""Parameter definition in a function."""
|
|
595
|
+
|
|
596
|
+
type_annotation: TypeNode
|
|
597
|
+
name: str
|
|
598
|
+
|
|
599
|
+
def accept(self, visitor: "Visitor"):
|
|
600
|
+
return visitor.visit_param_def(self)
|
|
601
|
+
|
|
602
|
+
def children(self) -> List[ASTNode]:
|
|
603
|
+
return [self.type_annotation]
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
@dataclass(frozen=True)
|
|
607
|
+
class Block(ASTNode):
|
|
608
|
+
"""Block of statements."""
|
|
609
|
+
|
|
610
|
+
statements: Tuple[ASTNode, ...]
|
|
611
|
+
|
|
612
|
+
def accept(self, visitor: "Visitor"):
|
|
613
|
+
return visitor.visit_block(self)
|
|
614
|
+
|
|
615
|
+
def children(self) -> List[ASTNode]:
|
|
616
|
+
return list(self.statements)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
@dataclass(frozen=True)
|
|
620
|
+
class FunctionDefNode(ASTNode):
|
|
621
|
+
"""Function definition."""
|
|
622
|
+
|
|
623
|
+
name: str
|
|
624
|
+
params: Tuple[ParamDef, ...]
|
|
625
|
+
return_type: Optional[TypeNode]
|
|
626
|
+
body: Block
|
|
627
|
+
|
|
628
|
+
def accept(self, visitor: "Visitor"):
|
|
629
|
+
return visitor.visit_function_def(self)
|
|
630
|
+
|
|
631
|
+
def children(self) -> List[ASTNode]:
|
|
632
|
+
result: List[ASTNode] = list(self.params)
|
|
633
|
+
if self.return_type:
|
|
634
|
+
result.append(self.return_type)
|
|
635
|
+
result.append(self.body)
|
|
636
|
+
return result
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# =============================================================================
|
|
640
|
+
# Statements
|
|
641
|
+
# =============================================================================
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@dataclass(frozen=True)
|
|
645
|
+
class VariableDecl(ASTNode):
|
|
646
|
+
"""Variable declaration statement."""
|
|
647
|
+
|
|
648
|
+
type_annotation: TypeNode
|
|
649
|
+
name: str
|
|
650
|
+
value: Optional[ASTNode] = None
|
|
651
|
+
|
|
652
|
+
def accept(self, visitor: "Visitor"):
|
|
653
|
+
return visitor.visit_variable_decl(self)
|
|
654
|
+
|
|
655
|
+
def children(self) -> List[ASTNode]:
|
|
656
|
+
result = [self.type_annotation]
|
|
657
|
+
if self.value:
|
|
658
|
+
result.append(self.value)
|
|
659
|
+
return result
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@dataclass(frozen=True)
|
|
663
|
+
class AssignmentStmt(ASTNode):
|
|
664
|
+
"""Assignment statement."""
|
|
665
|
+
|
|
666
|
+
target: ASTNode # IdentifierNode, FieldAccessNode, or IndexAccessNode
|
|
667
|
+
value: ASTNode
|
|
668
|
+
|
|
669
|
+
def accept(self, visitor: "Visitor"):
|
|
670
|
+
return visitor.visit_assignment_stmt(self)
|
|
671
|
+
|
|
672
|
+
def children(self) -> List[ASTNode]:
|
|
673
|
+
return [self.target, self.value]
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
@dataclass(frozen=True)
|
|
677
|
+
class ReturnStmt(ASTNode):
|
|
678
|
+
"""Return statement."""
|
|
679
|
+
|
|
680
|
+
value: Optional[ASTNode] = None
|
|
681
|
+
|
|
682
|
+
def accept(self, visitor: "Visitor"):
|
|
683
|
+
return visitor.visit_return_stmt(self)
|
|
684
|
+
|
|
685
|
+
def children(self) -> List[ASTNode]:
|
|
686
|
+
return [self.value] if self.value else []
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@dataclass(frozen=True)
|
|
690
|
+
class PassStmt(ASTNode):
|
|
691
|
+
"""Pass statement (no-op)."""
|
|
692
|
+
|
|
693
|
+
def accept(self, visitor: "Visitor"):
|
|
694
|
+
return visitor.visit_pass_stmt(self)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@dataclass(frozen=True)
|
|
698
|
+
class AssertStmt(ASTNode):
|
|
699
|
+
"""Assert statement for test files."""
|
|
700
|
+
|
|
701
|
+
condition: ASTNode # Usually a BinaryExprNode with ==
|
|
702
|
+
message: Optional[StringLit] = None
|
|
703
|
+
|
|
704
|
+
def accept(self, visitor: "Visitor"):
|
|
705
|
+
return visitor.visit_assert_stmt(self)
|
|
706
|
+
|
|
707
|
+
def children(self) -> List[ASTNode]:
|
|
708
|
+
result = [self.condition]
|
|
709
|
+
if self.message:
|
|
710
|
+
result.append(self.message)
|
|
711
|
+
return result
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@dataclass(frozen=True)
|
|
715
|
+
class ReferencingStmt(ASTNode):
|
|
716
|
+
"""Referencing statement for test files to import statutes."""
|
|
717
|
+
|
|
718
|
+
path: str # e.g., "s300_murder/statute"
|
|
719
|
+
|
|
720
|
+
def accept(self, visitor: "Visitor"):
|
|
721
|
+
return visitor.visit_referencing_stmt(self)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@dataclass(frozen=True)
|
|
725
|
+
class ExpressionStmt(ASTNode):
|
|
726
|
+
"""Expression statement."""
|
|
727
|
+
|
|
728
|
+
expression: ASTNode
|
|
729
|
+
|
|
730
|
+
def accept(self, visitor: "Visitor"):
|
|
731
|
+
return visitor.visit_expression_stmt(self)
|
|
732
|
+
|
|
733
|
+
def children(self) -> List[ASTNode]:
|
|
734
|
+
return [self.expression]
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
# =============================================================================
|
|
738
|
+
# Statute-specific Nodes
|
|
739
|
+
# =============================================================================
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@dataclass(frozen=True)
|
|
743
|
+
class DefinitionEntry(ASTNode):
|
|
744
|
+
"""Definition entry within a statute's definitions block."""
|
|
745
|
+
|
|
746
|
+
term: str
|
|
747
|
+
definition: StringLit
|
|
748
|
+
|
|
749
|
+
def accept(self, visitor: "Visitor"):
|
|
750
|
+
return visitor.visit_definition_entry(self)
|
|
751
|
+
|
|
752
|
+
def children(self) -> List[ASTNode]:
|
|
753
|
+
return [self.definition]
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
@dataclass(frozen=True)
|
|
757
|
+
class ElementNode(ASTNode):
|
|
758
|
+
"""
|
|
759
|
+
Element of an offense (actus reus or mens rea).
|
|
760
|
+
|
|
761
|
+
element_type: "actus_reus", "mens_rea", or "circumstance"
|
|
762
|
+
"""
|
|
763
|
+
|
|
764
|
+
element_type: str
|
|
765
|
+
name: str
|
|
766
|
+
description: ASTNode # Usually StringLit or match expression
|
|
767
|
+
|
|
768
|
+
def accept(self, visitor: "Visitor"):
|
|
769
|
+
return visitor.visit_element(self)
|
|
770
|
+
|
|
771
|
+
def children(self) -> List[ASTNode]:
|
|
772
|
+
return [self.description]
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
@dataclass(frozen=True)
|
|
776
|
+
class PenaltyNode(ASTNode):
|
|
777
|
+
"""
|
|
778
|
+
Penalty specification for a statute.
|
|
779
|
+
|
|
780
|
+
Can specify imprisonment, fine, or both, with optional ranges.
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
imprisonment_min: Optional[DurationNode] = None
|
|
784
|
+
imprisonment_max: Optional[DurationNode] = None
|
|
785
|
+
fine_min: Optional[MoneyNode] = None
|
|
786
|
+
fine_max: Optional[MoneyNode] = None
|
|
787
|
+
supplementary: Optional[StringLit] = None
|
|
788
|
+
|
|
789
|
+
def accept(self, visitor: "Visitor"):
|
|
790
|
+
return visitor.visit_penalty(self)
|
|
791
|
+
|
|
792
|
+
def children(self) -> List[ASTNode]:
|
|
793
|
+
result = []
|
|
794
|
+
if self.imprisonment_min:
|
|
795
|
+
result.append(self.imprisonment_min)
|
|
796
|
+
if self.imprisonment_max:
|
|
797
|
+
result.append(self.imprisonment_max)
|
|
798
|
+
if self.fine_min:
|
|
799
|
+
result.append(self.fine_min)
|
|
800
|
+
if self.fine_max:
|
|
801
|
+
result.append(self.fine_max)
|
|
802
|
+
if self.supplementary:
|
|
803
|
+
result.append(self.supplementary)
|
|
804
|
+
return result
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@dataclass(frozen=True)
|
|
808
|
+
class IllustrationNode(ASTNode):
|
|
809
|
+
"""Illustration example within a statute."""
|
|
810
|
+
|
|
811
|
+
label: Optional[str]
|
|
812
|
+
description: StringLit
|
|
813
|
+
|
|
814
|
+
def accept(self, visitor: "Visitor"):
|
|
815
|
+
return visitor.visit_illustration(self)
|
|
816
|
+
|
|
817
|
+
def children(self) -> List[ASTNode]:
|
|
818
|
+
return [self.description]
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@dataclass(frozen=True)
|
|
822
|
+
class StatuteNode(ASTNode):
|
|
823
|
+
"""
|
|
824
|
+
Statute block representing a legal provision.
|
|
825
|
+
|
|
826
|
+
Contains section number, title, definitions, elements, penalties,
|
|
827
|
+
and illustrations.
|
|
828
|
+
"""
|
|
829
|
+
|
|
830
|
+
section_number: str
|
|
831
|
+
title: Optional[StringLit]
|
|
832
|
+
definitions: Tuple[DefinitionEntry, ...]
|
|
833
|
+
elements: Tuple[ElementNode, ...]
|
|
834
|
+
penalty: Optional[PenaltyNode]
|
|
835
|
+
illustrations: Tuple[IllustrationNode, ...]
|
|
836
|
+
|
|
837
|
+
def accept(self, visitor: "Visitor"):
|
|
838
|
+
return visitor.visit_statute(self)
|
|
839
|
+
|
|
840
|
+
def children(self) -> List[ASTNode]:
|
|
841
|
+
result: List[ASTNode] = []
|
|
842
|
+
if self.title:
|
|
843
|
+
result.append(self.title)
|
|
844
|
+
result.extend(self.definitions)
|
|
845
|
+
result.extend(self.elements)
|
|
846
|
+
if self.penalty:
|
|
847
|
+
result.append(self.penalty)
|
|
848
|
+
result.extend(self.illustrations)
|
|
849
|
+
return result
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
# =============================================================================
|
|
853
|
+
# Import Node
|
|
854
|
+
# =============================================================================
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
@dataclass(frozen=True)
|
|
858
|
+
class ImportNode(ASTNode):
|
|
859
|
+
"""
|
|
860
|
+
Import statement for referencing other .yh files.
|
|
861
|
+
|
|
862
|
+
imported_names: List of names to import, or ["*"] for wildcard
|
|
863
|
+
"""
|
|
864
|
+
|
|
865
|
+
path: str
|
|
866
|
+
imported_names: Tuple[str, ...] # Empty tuple means import whole module
|
|
867
|
+
|
|
868
|
+
def accept(self, visitor: "Visitor"):
|
|
869
|
+
return visitor.visit_import(self)
|
|
870
|
+
|
|
871
|
+
@property
|
|
872
|
+
def is_wildcard(self) -> bool:
|
|
873
|
+
return "*" in self.imported_names
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# =============================================================================
|
|
877
|
+
# Module Node (Root)
|
|
878
|
+
# =============================================================================
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
@dataclass(frozen=True)
|
|
882
|
+
class ModuleNode(ASTNode):
|
|
883
|
+
"""
|
|
884
|
+
Root AST node representing a complete Yuho module (.yh file).
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
imports: Tuple[ImportNode, ...]
|
|
888
|
+
type_defs: Tuple[StructDefNode, ...]
|
|
889
|
+
function_defs: Tuple[FunctionDefNode, ...]
|
|
890
|
+
statutes: Tuple[StatuteNode, ...]
|
|
891
|
+
variables: Tuple[VariableDecl, ...]
|
|
892
|
+
references: Tuple["ReferencingStmt", ...] = ()
|
|
893
|
+
assertions: Tuple["AssertStmt", ...] = ()
|
|
894
|
+
|
|
895
|
+
def accept(self, visitor: "Visitor"):
|
|
896
|
+
return visitor.visit_module(self)
|
|
897
|
+
|
|
898
|
+
def children(self) -> List[ASTNode]:
|
|
899
|
+
result: List[ASTNode] = []
|
|
900
|
+
result.extend(self.imports)
|
|
901
|
+
result.extend(self.references)
|
|
902
|
+
result.extend(self.type_defs)
|
|
903
|
+
result.extend(self.function_defs)
|
|
904
|
+
result.extend(self.statutes)
|
|
905
|
+
result.extend(self.variables)
|
|
906
|
+
result.extend(self.assertions)
|
|
907
|
+
return result
|