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
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type inference visitor for Yuho AST.
|
|
3
|
+
|
|
4
|
+
Annotates each expression node with its inferred type based on:
|
|
5
|
+
- Literal types (direct from node type)
|
|
6
|
+
- Variable types (from symbol table)
|
|
7
|
+
- Operator result types (from operand types)
|
|
8
|
+
- Function return types (from function signatures)
|
|
9
|
+
- Field access types (from struct definitions)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, Optional, List
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
from yuho.ast import nodes
|
|
16
|
+
from yuho.ast.visitor import Visitor
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TypeAnnotation:
|
|
21
|
+
"""Represents an inferred type annotation for a node."""
|
|
22
|
+
|
|
23
|
+
type_name: str
|
|
24
|
+
is_optional: bool = False
|
|
25
|
+
is_array: bool = False
|
|
26
|
+
element_type: Optional["TypeAnnotation"] = None
|
|
27
|
+
struct_fields: Dict[str, "TypeAnnotation"] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
if self.is_array:
|
|
31
|
+
return f"[{self.element_type}]"
|
|
32
|
+
if self.is_optional:
|
|
33
|
+
return f"{self.type_name}?"
|
|
34
|
+
return self.type_name
|
|
35
|
+
|
|
36
|
+
def __eq__(self, other: object) -> bool:
|
|
37
|
+
if not isinstance(other, TypeAnnotation):
|
|
38
|
+
return False
|
|
39
|
+
return (
|
|
40
|
+
self.type_name == other.type_name
|
|
41
|
+
and self.is_optional == other.is_optional
|
|
42
|
+
and self.is_array == other.is_array
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Built-in type constants
|
|
47
|
+
INT_TYPE = TypeAnnotation("int")
|
|
48
|
+
FLOAT_TYPE = TypeAnnotation("float")
|
|
49
|
+
BOOL_TYPE = TypeAnnotation("bool")
|
|
50
|
+
STRING_TYPE = TypeAnnotation("string")
|
|
51
|
+
MONEY_TYPE = TypeAnnotation("money")
|
|
52
|
+
PERCENT_TYPE = TypeAnnotation("percent")
|
|
53
|
+
DATE_TYPE = TypeAnnotation("date")
|
|
54
|
+
DURATION_TYPE = TypeAnnotation("duration")
|
|
55
|
+
VOID_TYPE = TypeAnnotation("void")
|
|
56
|
+
PASS_TYPE = TypeAnnotation("pass")
|
|
57
|
+
UNKNOWN_TYPE = TypeAnnotation("unknown")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class TypeInferenceResult:
|
|
62
|
+
"""Result of type inference including all inferred types and any errors."""
|
|
63
|
+
|
|
64
|
+
# Map of AST node id -> inferred type
|
|
65
|
+
node_types: Dict[int, TypeAnnotation] = field(default_factory=dict)
|
|
66
|
+
|
|
67
|
+
# Struct definitions found
|
|
68
|
+
struct_defs: Dict[str, Dict[str, TypeAnnotation]] = field(default_factory=dict)
|
|
69
|
+
|
|
70
|
+
# Function signatures found
|
|
71
|
+
function_sigs: Dict[str, tuple] = field(default_factory=dict)
|
|
72
|
+
|
|
73
|
+
# Variable types in scope
|
|
74
|
+
variable_types: Dict[str, TypeAnnotation] = field(default_factory=dict)
|
|
75
|
+
|
|
76
|
+
# Inference errors
|
|
77
|
+
errors: List[str] = field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
def get_type(self, node: nodes.ASTNode) -> TypeAnnotation:
|
|
80
|
+
"""Get the inferred type for a node."""
|
|
81
|
+
return self.node_types.get(id(node), UNKNOWN_TYPE)
|
|
82
|
+
|
|
83
|
+
def set_type(self, node: nodes.ASTNode, type_ann: TypeAnnotation) -> None:
|
|
84
|
+
"""Set the inferred type for a node."""
|
|
85
|
+
self.node_types[id(node)] = type_ann
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TypeInferenceVisitor(Visitor):
|
|
89
|
+
"""
|
|
90
|
+
Visitor that infers and annotates types for all expression nodes.
|
|
91
|
+
|
|
92
|
+
Usage:
|
|
93
|
+
visitor = TypeInferenceVisitor()
|
|
94
|
+
module.accept(visitor)
|
|
95
|
+
result = visitor.result
|
|
96
|
+
|
|
97
|
+
# Get type of any expression
|
|
98
|
+
expr_type = result.get_type(some_expr_node)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self) -> None:
|
|
102
|
+
self.result = TypeInferenceResult()
|
|
103
|
+
self._current_scope: Dict[str, TypeAnnotation] = {}
|
|
104
|
+
|
|
105
|
+
def _type_node_to_annotation(self, type_node: nodes.TypeNode) -> TypeAnnotation:
|
|
106
|
+
"""Convert a TypeNode to a TypeAnnotation."""
|
|
107
|
+
if isinstance(type_node, nodes.BuiltinType):
|
|
108
|
+
return TypeAnnotation(type_node.name)
|
|
109
|
+
elif isinstance(type_node, nodes.NamedType):
|
|
110
|
+
return TypeAnnotation(type_node.name)
|
|
111
|
+
elif isinstance(type_node, nodes.OptionalType):
|
|
112
|
+
inner = self._type_node_to_annotation(type_node.inner)
|
|
113
|
+
return TypeAnnotation(inner.type_name, is_optional=True)
|
|
114
|
+
elif isinstance(type_node, nodes.ArrayType):
|
|
115
|
+
elem = self._type_node_to_annotation(type_node.element_type)
|
|
116
|
+
return TypeAnnotation("array", is_array=True, element_type=elem)
|
|
117
|
+
elif isinstance(type_node, nodes.GenericType):
|
|
118
|
+
# Handle generic types like List<T>
|
|
119
|
+
return TypeAnnotation(type_node.base)
|
|
120
|
+
return UNKNOWN_TYPE
|
|
121
|
+
|
|
122
|
+
# =========================================================================
|
|
123
|
+
# Literal nodes - direct type inference
|
|
124
|
+
# =========================================================================
|
|
125
|
+
|
|
126
|
+
def visit_int_lit(self, node: nodes.IntLit) -> Any:
|
|
127
|
+
self.result.set_type(node, INT_TYPE)
|
|
128
|
+
return INT_TYPE
|
|
129
|
+
|
|
130
|
+
def visit_float_lit(self, node: nodes.FloatLit) -> Any:
|
|
131
|
+
self.result.set_type(node, FLOAT_TYPE)
|
|
132
|
+
return FLOAT_TYPE
|
|
133
|
+
|
|
134
|
+
def visit_bool_lit(self, node: nodes.BoolLit) -> Any:
|
|
135
|
+
self.result.set_type(node, BOOL_TYPE)
|
|
136
|
+
return BOOL_TYPE
|
|
137
|
+
|
|
138
|
+
def visit_string_lit(self, node: nodes.StringLit) -> Any:
|
|
139
|
+
self.result.set_type(node, STRING_TYPE)
|
|
140
|
+
return STRING_TYPE
|
|
141
|
+
|
|
142
|
+
def visit_money(self, node: nodes.MoneyNode) -> Any:
|
|
143
|
+
self.result.set_type(node, MONEY_TYPE)
|
|
144
|
+
return MONEY_TYPE
|
|
145
|
+
|
|
146
|
+
def visit_percent(self, node: nodes.PercentNode) -> Any:
|
|
147
|
+
self.result.set_type(node, PERCENT_TYPE)
|
|
148
|
+
return PERCENT_TYPE
|
|
149
|
+
|
|
150
|
+
def visit_date(self, node: nodes.DateNode) -> Any:
|
|
151
|
+
self.result.set_type(node, DATE_TYPE)
|
|
152
|
+
return DATE_TYPE
|
|
153
|
+
|
|
154
|
+
def visit_duration(self, node: nodes.DurationNode) -> Any:
|
|
155
|
+
self.result.set_type(node, DURATION_TYPE)
|
|
156
|
+
return DURATION_TYPE
|
|
157
|
+
|
|
158
|
+
def visit_pass_expr(self, node: nodes.PassExprNode) -> Any:
|
|
159
|
+
self.result.set_type(node, PASS_TYPE)
|
|
160
|
+
return PASS_TYPE
|
|
161
|
+
|
|
162
|
+
# =========================================================================
|
|
163
|
+
# Expression nodes
|
|
164
|
+
# =========================================================================
|
|
165
|
+
|
|
166
|
+
def visit_identifier(self, node: nodes.IdentifierNode) -> Any:
|
|
167
|
+
"""Look up identifier in current scope."""
|
|
168
|
+
name = node.name
|
|
169
|
+
if name in self._current_scope:
|
|
170
|
+
inferred_type = self._current_scope[name]
|
|
171
|
+
elif name in self.result.variable_types:
|
|
172
|
+
inferred_type = self.result.variable_types[name]
|
|
173
|
+
elif name in self.result.struct_defs:
|
|
174
|
+
# It's a struct type reference
|
|
175
|
+
inferred_type = TypeAnnotation(name)
|
|
176
|
+
else:
|
|
177
|
+
inferred_type = UNKNOWN_TYPE
|
|
178
|
+
|
|
179
|
+
self.result.set_type(node, inferred_type)
|
|
180
|
+
return inferred_type
|
|
181
|
+
|
|
182
|
+
def visit_field_access(self, node: nodes.FieldAccessNode) -> Any:
|
|
183
|
+
"""Infer type from struct field access."""
|
|
184
|
+
base_type = self.visit(node.base)
|
|
185
|
+
field_name = node.field_name
|
|
186
|
+
|
|
187
|
+
# Check if base is a known struct type
|
|
188
|
+
if base_type.type_name in self.result.struct_defs:
|
|
189
|
+
struct_fields = self.result.struct_defs[base_type.type_name]
|
|
190
|
+
if field_name in struct_fields:
|
|
191
|
+
inferred_type = struct_fields[field_name]
|
|
192
|
+
else:
|
|
193
|
+
# Could be enum variant access
|
|
194
|
+
inferred_type = TypeAnnotation(base_type.type_name)
|
|
195
|
+
else:
|
|
196
|
+
# Enum variant access pattern: EnumType.Variant
|
|
197
|
+
inferred_type = TypeAnnotation(base_type.type_name)
|
|
198
|
+
|
|
199
|
+
self.result.set_type(node, inferred_type)
|
|
200
|
+
return inferred_type
|
|
201
|
+
|
|
202
|
+
def visit_index_access(self, node: nodes.IndexAccessNode) -> Any:
|
|
203
|
+
"""Infer type from array indexing."""
|
|
204
|
+
base_type = self.visit(node.base)
|
|
205
|
+
self.visit(node.index)
|
|
206
|
+
|
|
207
|
+
if base_type.is_array and base_type.element_type:
|
|
208
|
+
inferred_type = base_type.element_type
|
|
209
|
+
else:
|
|
210
|
+
inferred_type = UNKNOWN_TYPE
|
|
211
|
+
|
|
212
|
+
self.result.set_type(node, inferred_type)
|
|
213
|
+
return inferred_type
|
|
214
|
+
|
|
215
|
+
def visit_binary_expr(self, node: nodes.BinaryExprNode) -> Any:
|
|
216
|
+
"""Infer type from binary expression based on operator and operands."""
|
|
217
|
+
left_type = self.visit(node.left)
|
|
218
|
+
right_type = self.visit(node.right)
|
|
219
|
+
|
|
220
|
+
op = node.operator
|
|
221
|
+
|
|
222
|
+
# Comparison operators always return bool
|
|
223
|
+
if op in ("==", "!=", "<", ">", "<=", ">="):
|
|
224
|
+
inferred_type = BOOL_TYPE
|
|
225
|
+
# Logical operators return bool
|
|
226
|
+
elif op in ("&&", "||", "and", "or"):
|
|
227
|
+
inferred_type = BOOL_TYPE
|
|
228
|
+
# Arithmetic operators: result type depends on operands
|
|
229
|
+
elif op in ("+", "-", "*", "/", "%"):
|
|
230
|
+
if left_type == FLOAT_TYPE or right_type == FLOAT_TYPE:
|
|
231
|
+
inferred_type = FLOAT_TYPE
|
|
232
|
+
elif left_type == MONEY_TYPE or right_type == MONEY_TYPE:
|
|
233
|
+
inferred_type = MONEY_TYPE
|
|
234
|
+
elif left_type == DURATION_TYPE or right_type == DURATION_TYPE:
|
|
235
|
+
inferred_type = DURATION_TYPE
|
|
236
|
+
else:
|
|
237
|
+
inferred_type = INT_TYPE
|
|
238
|
+
else:
|
|
239
|
+
inferred_type = UNKNOWN_TYPE
|
|
240
|
+
|
|
241
|
+
self.result.set_type(node, inferred_type)
|
|
242
|
+
return inferred_type
|
|
243
|
+
|
|
244
|
+
def visit_unary_expr(self, node: nodes.UnaryExprNode) -> Any:
|
|
245
|
+
"""Infer type from unary expression."""
|
|
246
|
+
operand_type = self.visit(node.operand)
|
|
247
|
+
|
|
248
|
+
op = node.operator
|
|
249
|
+
if op in ("!", "not"):
|
|
250
|
+
inferred_type = BOOL_TYPE
|
|
251
|
+
elif op == "-":
|
|
252
|
+
inferred_type = operand_type # Negation preserves numeric type
|
|
253
|
+
else:
|
|
254
|
+
inferred_type = operand_type
|
|
255
|
+
|
|
256
|
+
self.result.set_type(node, inferred_type)
|
|
257
|
+
return inferred_type
|
|
258
|
+
|
|
259
|
+
def visit_function_call(self, node: nodes.FunctionCallNode) -> Any:
|
|
260
|
+
"""Infer return type from function signature."""
|
|
261
|
+
# Visit arguments
|
|
262
|
+
for arg in node.arguments:
|
|
263
|
+
self.visit(arg)
|
|
264
|
+
|
|
265
|
+
# Look up function return type
|
|
266
|
+
func_name = node.callee if isinstance(node.callee, str) else getattr(node.callee, "name", "")
|
|
267
|
+
if func_name in self.result.function_sigs:
|
|
268
|
+
_, return_type = self.result.function_sigs[func_name]
|
|
269
|
+
inferred_type = return_type
|
|
270
|
+
else:
|
|
271
|
+
inferred_type = UNKNOWN_TYPE
|
|
272
|
+
|
|
273
|
+
self.result.set_type(node, inferred_type)
|
|
274
|
+
return inferred_type
|
|
275
|
+
|
|
276
|
+
# =========================================================================
|
|
277
|
+
# Match expression
|
|
278
|
+
# =========================================================================
|
|
279
|
+
|
|
280
|
+
def visit_match_expr(self, node: nodes.MatchExprNode) -> Any:
|
|
281
|
+
"""Infer type from match expression arms."""
|
|
282
|
+
if node.scrutinee:
|
|
283
|
+
self.visit(node.scrutinee)
|
|
284
|
+
|
|
285
|
+
arm_types: List[TypeAnnotation] = []
|
|
286
|
+
for arm in node.arms:
|
|
287
|
+
arm_type = self.visit(arm)
|
|
288
|
+
if arm_type:
|
|
289
|
+
arm_types.append(arm_type)
|
|
290
|
+
|
|
291
|
+
# All arms should have the same type; use first non-unknown
|
|
292
|
+
if arm_types:
|
|
293
|
+
inferred_type = next(
|
|
294
|
+
(t for t in arm_types if t != UNKNOWN_TYPE and t != PASS_TYPE),
|
|
295
|
+
arm_types[0]
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
inferred_type = UNKNOWN_TYPE
|
|
299
|
+
|
|
300
|
+
self.result.set_type(node, inferred_type)
|
|
301
|
+
return inferred_type
|
|
302
|
+
|
|
303
|
+
def visit_match_arm(self, node: nodes.MatchArm) -> Any:
|
|
304
|
+
"""Infer type from match arm body."""
|
|
305
|
+
self.visit(node.pattern)
|
|
306
|
+
if node.guard:
|
|
307
|
+
self.visit(node.guard)
|
|
308
|
+
body_type = self.visit(node.body)
|
|
309
|
+
self.result.set_type(node, body_type)
|
|
310
|
+
return body_type
|
|
311
|
+
|
|
312
|
+
# =========================================================================
|
|
313
|
+
# Struct definition and literal
|
|
314
|
+
# =========================================================================
|
|
315
|
+
|
|
316
|
+
def visit_struct_def(self, node: nodes.StructDefNode) -> Any:
|
|
317
|
+
"""Record struct field types."""
|
|
318
|
+
fields: Dict[str, TypeAnnotation] = {}
|
|
319
|
+
for field_def in node.fields:
|
|
320
|
+
if field_def.type_annotation:
|
|
321
|
+
fields[field_def.name] = self._type_node_to_annotation(field_def.type_annotation)
|
|
322
|
+
else:
|
|
323
|
+
# Enum variant (no type)
|
|
324
|
+
fields[field_def.name] = TypeAnnotation(node.name)
|
|
325
|
+
|
|
326
|
+
self.result.struct_defs[node.name] = fields
|
|
327
|
+
return self.generic_visit(node)
|
|
328
|
+
|
|
329
|
+
def visit_struct_literal(self, node: nodes.StructLiteralNode) -> Any:
|
|
330
|
+
"""Infer type from struct literal type name."""
|
|
331
|
+
if node.type_name:
|
|
332
|
+
inferred_type = TypeAnnotation(node.type_name)
|
|
333
|
+
else:
|
|
334
|
+
inferred_type = UNKNOWN_TYPE
|
|
335
|
+
|
|
336
|
+
# Visit field assignments
|
|
337
|
+
for field_assign in node.field_assignments:
|
|
338
|
+
self.visit(field_assign)
|
|
339
|
+
|
|
340
|
+
self.result.set_type(node, inferred_type)
|
|
341
|
+
return inferred_type
|
|
342
|
+
|
|
343
|
+
# =========================================================================
|
|
344
|
+
# Variable and function definitions
|
|
345
|
+
# =========================================================================
|
|
346
|
+
|
|
347
|
+
def visit_variable_decl(self, node: nodes.VariableDecl) -> Any:
|
|
348
|
+
"""Record variable type in scope."""
|
|
349
|
+
if node.type_annotation:
|
|
350
|
+
var_type = self._type_node_to_annotation(node.type_annotation)
|
|
351
|
+
elif node.value:
|
|
352
|
+
var_type = self.visit(node.value)
|
|
353
|
+
else:
|
|
354
|
+
var_type = UNKNOWN_TYPE
|
|
355
|
+
|
|
356
|
+
self._current_scope[node.name] = var_type
|
|
357
|
+
self.result.variable_types[node.name] = var_type
|
|
358
|
+
self.result.set_type(node, var_type)
|
|
359
|
+
return var_type
|
|
360
|
+
|
|
361
|
+
def visit_function_def(self, node: nodes.FunctionDefNode) -> Any:
|
|
362
|
+
"""Record function signature and infer body types."""
|
|
363
|
+
# Record return type
|
|
364
|
+
if node.return_type:
|
|
365
|
+
return_type = self._type_node_to_annotation(node.return_type)
|
|
366
|
+
else:
|
|
367
|
+
return_type = VOID_TYPE
|
|
368
|
+
|
|
369
|
+
# Record parameter types
|
|
370
|
+
param_types: List[TypeAnnotation] = []
|
|
371
|
+
for param in node.parameters:
|
|
372
|
+
if param.type_annotation:
|
|
373
|
+
p_type = self._type_node_to_annotation(param.type_annotation)
|
|
374
|
+
param_types.append(p_type)
|
|
375
|
+
self._current_scope[param.name] = p_type
|
|
376
|
+
|
|
377
|
+
self.result.function_sigs[node.name] = (param_types, return_type)
|
|
378
|
+
|
|
379
|
+
# Visit body
|
|
380
|
+
if node.body:
|
|
381
|
+
self.visit(node.body)
|
|
382
|
+
|
|
383
|
+
self.result.set_type(node, return_type)
|
|
384
|
+
return return_type
|
|
385
|
+
|
|
386
|
+
# =========================================================================
|
|
387
|
+
# Pattern nodes
|
|
388
|
+
# =========================================================================
|
|
389
|
+
|
|
390
|
+
def visit_wildcard_pattern(self, node: nodes.WildcardPattern) -> Any:
|
|
391
|
+
self.result.set_type(node, UNKNOWN_TYPE)
|
|
392
|
+
return UNKNOWN_TYPE
|
|
393
|
+
|
|
394
|
+
def visit_literal_pattern(self, node: nodes.LiteralPattern) -> Any:
|
|
395
|
+
literal_type = self.visit(node.literal)
|
|
396
|
+
self.result.set_type(node, literal_type)
|
|
397
|
+
return literal_type
|
|
398
|
+
|
|
399
|
+
def visit_binding_pattern(self, node: nodes.BindingPattern) -> Any:
|
|
400
|
+
self.result.set_type(node, UNKNOWN_TYPE)
|
|
401
|
+
return UNKNOWN_TYPE
|
|
402
|
+
|
|
403
|
+
# =========================================================================
|
|
404
|
+
# Module entry point
|
|
405
|
+
# =========================================================================
|
|
406
|
+
|
|
407
|
+
def visit_module(self, node: nodes.ModuleNode) -> Any:
|
|
408
|
+
"""Entry point: visit all declarations in module."""
|
|
409
|
+
# First pass: collect struct definitions
|
|
410
|
+
for decl in node.declarations:
|
|
411
|
+
if isinstance(decl, nodes.StructDefNode):
|
|
412
|
+
self.visit_struct_def(decl)
|
|
413
|
+
elif isinstance(decl, nodes.FunctionDefNode):
|
|
414
|
+
# Just record signature
|
|
415
|
+
if decl.return_type:
|
|
416
|
+
return_type = self._type_node_to_annotation(decl.return_type)
|
|
417
|
+
else:
|
|
418
|
+
return_type = VOID_TYPE
|
|
419
|
+
self.result.function_sigs[decl.name] = ([], return_type)
|
|
420
|
+
|
|
421
|
+
# Second pass: full traversal
|
|
422
|
+
for decl in node.declarations:
|
|
423
|
+
self.visit(decl)
|
|
424
|
+
|
|
425
|
+
return self.result
|
yuho/ast/visitor.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Visitor base class for AST traversal.
|
|
3
|
+
|
|
4
|
+
Provides default implementations that traverse all child nodes,
|
|
5
|
+
allowing subclasses to override only the methods they need.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, TypeVar
|
|
9
|
+
|
|
10
|
+
from yuho.ast import nodes
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Visitor:
|
|
16
|
+
"""
|
|
17
|
+
Base visitor class for AST traversal.
|
|
18
|
+
|
|
19
|
+
Default implementations visit all child nodes and return None.
|
|
20
|
+
Subclasses can override specific visit_* methods to implement
|
|
21
|
+
custom behavior.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
class MyVisitor(Visitor):
|
|
25
|
+
def visit_struct_def(self, node):
|
|
26
|
+
print(f"Found struct: {node.name}")
|
|
27
|
+
return self.generic_visit(node)
|
|
28
|
+
|
|
29
|
+
visitor = MyVisitor()
|
|
30
|
+
module.accept(visitor)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def visit(self, node: nodes.ASTNode) -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Dispatch to the appropriate visit_* method.
|
|
36
|
+
|
|
37
|
+
This is the main entry point for visiting a node.
|
|
38
|
+
"""
|
|
39
|
+
return node.accept(self)
|
|
40
|
+
|
|
41
|
+
def generic_visit(self, node: nodes.ASTNode) -> Any:
|
|
42
|
+
"""
|
|
43
|
+
Default visitor that traverses all children.
|
|
44
|
+
|
|
45
|
+
Override this to change default traversal behavior.
|
|
46
|
+
"""
|
|
47
|
+
for child in node.children():
|
|
48
|
+
self.visit(child)
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# =========================================================================
|
|
52
|
+
# Type nodes
|
|
53
|
+
# =========================================================================
|
|
54
|
+
|
|
55
|
+
def visit_type(self, node: nodes.TypeNode) -> Any:
|
|
56
|
+
return self.generic_visit(node)
|
|
57
|
+
|
|
58
|
+
def visit_builtin_type(self, node: nodes.BuiltinType) -> Any:
|
|
59
|
+
return self.generic_visit(node)
|
|
60
|
+
|
|
61
|
+
def visit_named_type(self, node: nodes.NamedType) -> Any:
|
|
62
|
+
return self.generic_visit(node)
|
|
63
|
+
|
|
64
|
+
def visit_generic_type(self, node: nodes.GenericType) -> Any:
|
|
65
|
+
return self.generic_visit(node)
|
|
66
|
+
|
|
67
|
+
def visit_optional_type(self, node: nodes.OptionalType) -> Any:
|
|
68
|
+
return self.generic_visit(node)
|
|
69
|
+
|
|
70
|
+
def visit_array_type(self, node: nodes.ArrayType) -> Any:
|
|
71
|
+
return self.generic_visit(node)
|
|
72
|
+
|
|
73
|
+
# =========================================================================
|
|
74
|
+
# Literal nodes
|
|
75
|
+
# =========================================================================
|
|
76
|
+
|
|
77
|
+
def visit_int_lit(self, node: nodes.IntLit) -> Any:
|
|
78
|
+
return self.generic_visit(node)
|
|
79
|
+
|
|
80
|
+
def visit_float_lit(self, node: nodes.FloatLit) -> Any:
|
|
81
|
+
return self.generic_visit(node)
|
|
82
|
+
|
|
83
|
+
def visit_bool_lit(self, node: nodes.BoolLit) -> Any:
|
|
84
|
+
return self.generic_visit(node)
|
|
85
|
+
|
|
86
|
+
def visit_string_lit(self, node: nodes.StringLit) -> Any:
|
|
87
|
+
return self.generic_visit(node)
|
|
88
|
+
|
|
89
|
+
def visit_money(self, node: nodes.MoneyNode) -> Any:
|
|
90
|
+
return self.generic_visit(node)
|
|
91
|
+
|
|
92
|
+
def visit_percent(self, node: nodes.PercentNode) -> Any:
|
|
93
|
+
return self.generic_visit(node)
|
|
94
|
+
|
|
95
|
+
def visit_date(self, node: nodes.DateNode) -> Any:
|
|
96
|
+
return self.generic_visit(node)
|
|
97
|
+
|
|
98
|
+
def visit_duration(self, node: nodes.DurationNode) -> Any:
|
|
99
|
+
return self.generic_visit(node)
|
|
100
|
+
|
|
101
|
+
# =========================================================================
|
|
102
|
+
# Expression nodes
|
|
103
|
+
# =========================================================================
|
|
104
|
+
|
|
105
|
+
def visit_identifier(self, node: nodes.IdentifierNode) -> Any:
|
|
106
|
+
return self.generic_visit(node)
|
|
107
|
+
|
|
108
|
+
def visit_field_access(self, node: nodes.FieldAccessNode) -> Any:
|
|
109
|
+
return self.generic_visit(node)
|
|
110
|
+
|
|
111
|
+
def visit_index_access(self, node: nodes.IndexAccessNode) -> Any:
|
|
112
|
+
return self.generic_visit(node)
|
|
113
|
+
|
|
114
|
+
def visit_function_call(self, node: nodes.FunctionCallNode) -> Any:
|
|
115
|
+
return self.generic_visit(node)
|
|
116
|
+
|
|
117
|
+
def visit_binary_expr(self, node: nodes.BinaryExprNode) -> Any:
|
|
118
|
+
return self.generic_visit(node)
|
|
119
|
+
|
|
120
|
+
def visit_unary_expr(self, node: nodes.UnaryExprNode) -> Any:
|
|
121
|
+
return self.generic_visit(node)
|
|
122
|
+
|
|
123
|
+
def visit_pass_expr(self, node: nodes.PassExprNode) -> Any:
|
|
124
|
+
return self.generic_visit(node)
|
|
125
|
+
|
|
126
|
+
# =========================================================================
|
|
127
|
+
# Pattern nodes
|
|
128
|
+
# =========================================================================
|
|
129
|
+
|
|
130
|
+
def visit_pattern(self, node: nodes.PatternNode) -> Any:
|
|
131
|
+
return self.generic_visit(node)
|
|
132
|
+
|
|
133
|
+
def visit_wildcard_pattern(self, node: nodes.WildcardPattern) -> Any:
|
|
134
|
+
return self.generic_visit(node)
|
|
135
|
+
|
|
136
|
+
def visit_literal_pattern(self, node: nodes.LiteralPattern) -> Any:
|
|
137
|
+
return self.generic_visit(node)
|
|
138
|
+
|
|
139
|
+
def visit_binding_pattern(self, node: nodes.BindingPattern) -> Any:
|
|
140
|
+
return self.generic_visit(node)
|
|
141
|
+
|
|
142
|
+
def visit_field_pattern(self, node: nodes.FieldPattern) -> Any:
|
|
143
|
+
return self.generic_visit(node)
|
|
144
|
+
|
|
145
|
+
def visit_struct_pattern(self, node: nodes.StructPattern) -> Any:
|
|
146
|
+
return self.generic_visit(node)
|
|
147
|
+
|
|
148
|
+
# =========================================================================
|
|
149
|
+
# Match expression
|
|
150
|
+
# =========================================================================
|
|
151
|
+
|
|
152
|
+
def visit_match_arm(self, node: nodes.MatchArm) -> Any:
|
|
153
|
+
return self.generic_visit(node)
|
|
154
|
+
|
|
155
|
+
def visit_match_expr(self, node: nodes.MatchExprNode) -> Any:
|
|
156
|
+
return self.generic_visit(node)
|
|
157
|
+
|
|
158
|
+
# =========================================================================
|
|
159
|
+
# Struct definition and literal
|
|
160
|
+
# =========================================================================
|
|
161
|
+
|
|
162
|
+
def visit_field_def(self, node: nodes.FieldDef) -> Any:
|
|
163
|
+
return self.generic_visit(node)
|
|
164
|
+
|
|
165
|
+
def visit_struct_def(self, node: nodes.StructDefNode) -> Any:
|
|
166
|
+
return self.generic_visit(node)
|
|
167
|
+
|
|
168
|
+
def visit_field_assignment(self, node: nodes.FieldAssignment) -> Any:
|
|
169
|
+
return self.generic_visit(node)
|
|
170
|
+
|
|
171
|
+
def visit_struct_literal(self, node: nodes.StructLiteralNode) -> Any:
|
|
172
|
+
return self.generic_visit(node)
|
|
173
|
+
|
|
174
|
+
# =========================================================================
|
|
175
|
+
# Function definition
|
|
176
|
+
# =========================================================================
|
|
177
|
+
|
|
178
|
+
def visit_param_def(self, node: nodes.ParamDef) -> Any:
|
|
179
|
+
return self.generic_visit(node)
|
|
180
|
+
|
|
181
|
+
def visit_block(self, node: nodes.Block) -> Any:
|
|
182
|
+
return self.generic_visit(node)
|
|
183
|
+
|
|
184
|
+
def visit_function_def(self, node: nodes.FunctionDefNode) -> Any:
|
|
185
|
+
return self.generic_visit(node)
|
|
186
|
+
|
|
187
|
+
# =========================================================================
|
|
188
|
+
# Statements
|
|
189
|
+
# =========================================================================
|
|
190
|
+
|
|
191
|
+
def visit_variable_decl(self, node: nodes.VariableDecl) -> Any:
|
|
192
|
+
return self.generic_visit(node)
|
|
193
|
+
|
|
194
|
+
def visit_assignment_stmt(self, node: nodes.AssignmentStmt) -> Any:
|
|
195
|
+
return self.generic_visit(node)
|
|
196
|
+
|
|
197
|
+
def visit_return_stmt(self, node: nodes.ReturnStmt) -> Any:
|
|
198
|
+
return self.generic_visit(node)
|
|
199
|
+
|
|
200
|
+
def visit_pass_stmt(self, node: nodes.PassStmt) -> Any:
|
|
201
|
+
return self.generic_visit(node)
|
|
202
|
+
|
|
203
|
+
def visit_expression_stmt(self, node: nodes.ExpressionStmt) -> Any:
|
|
204
|
+
return self.generic_visit(node)
|
|
205
|
+
|
|
206
|
+
def visit_assert_stmt(self, node: nodes.AssertStmt) -> Any:
|
|
207
|
+
return self.generic_visit(node)
|
|
208
|
+
|
|
209
|
+
def visit_referencing_stmt(self, node: nodes.ReferencingStmt) -> Any:
|
|
210
|
+
return self.generic_visit(node)
|
|
211
|
+
|
|
212
|
+
# =========================================================================
|
|
213
|
+
# Statute-specific nodes
|
|
214
|
+
# =========================================================================
|
|
215
|
+
|
|
216
|
+
def visit_definition_entry(self, node: nodes.DefinitionEntry) -> Any:
|
|
217
|
+
return self.generic_visit(node)
|
|
218
|
+
|
|
219
|
+
def visit_element(self, node: nodes.ElementNode) -> Any:
|
|
220
|
+
return self.generic_visit(node)
|
|
221
|
+
|
|
222
|
+
def visit_penalty(self, node: nodes.PenaltyNode) -> Any:
|
|
223
|
+
return self.generic_visit(node)
|
|
224
|
+
|
|
225
|
+
def visit_illustration(self, node: nodes.IllustrationNode) -> Any:
|
|
226
|
+
return self.generic_visit(node)
|
|
227
|
+
|
|
228
|
+
def visit_statute(self, node: nodes.StatuteNode) -> Any:
|
|
229
|
+
return self.generic_visit(node)
|
|
230
|
+
|
|
231
|
+
# =========================================================================
|
|
232
|
+
# Import and module
|
|
233
|
+
# =========================================================================
|
|
234
|
+
|
|
235
|
+
def visit_import(self, node: nodes.ImportNode) -> Any:
|
|
236
|
+
return self.generic_visit(node)
|
|
237
|
+
|
|
238
|
+
def visit_module(self, node: nodes.ModuleNode) -> Any:
|
|
239
|
+
return self.generic_visit(node)
|
yuho/cli/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Yuho CLI module - command-line interface for the Yuho language.
|
|
3
|
+
|
|
4
|
+
Provides commands for:
|
|
5
|
+
- check: Parse and validate .yh files
|
|
6
|
+
- transpile: Convert to JSON, English, Mermaid, etc.
|
|
7
|
+
- explain: LLM-powered explanations
|
|
8
|
+
- serve: Start MCP server
|
|
9
|
+
- contribute: Package statutes for sharing
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from yuho.cli.main import cli
|
|
13
|
+
|
|
14
|
+
__all__ = ["cli"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command implementations."""
|