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,490 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scope analysis visitor for Yuho AST.
|
|
3
|
+
|
|
4
|
+
Builds symbol tables and resolves references:
|
|
5
|
+
- Variable declarations and their scopes
|
|
6
|
+
- Function definitions and their signatures
|
|
7
|
+
- Struct definitions and their members
|
|
8
|
+
- Undeclared identifier detection
|
|
9
|
+
- Duplicate declaration detection
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from enum import Enum, auto
|
|
15
|
+
|
|
16
|
+
from yuho.ast import nodes
|
|
17
|
+
from yuho.ast.visitor import Visitor
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SymbolKind(Enum):
|
|
21
|
+
"""Type of symbol in the symbol table."""
|
|
22
|
+
|
|
23
|
+
VARIABLE = auto()
|
|
24
|
+
FUNCTION = auto()
|
|
25
|
+
STRUCT = auto()
|
|
26
|
+
PARAMETER = auto()
|
|
27
|
+
FIELD = auto()
|
|
28
|
+
ENUM_VARIANT = auto()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Symbol:
|
|
33
|
+
"""Represents a symbol in the symbol table."""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
kind: SymbolKind
|
|
37
|
+
declaration_node: Optional[nodes.ASTNode] = None
|
|
38
|
+
type_annotation: Optional[str] = None
|
|
39
|
+
scope_level: int = 0
|
|
40
|
+
line: int = 0
|
|
41
|
+
column: int = 0
|
|
42
|
+
|
|
43
|
+
# For structs: field names
|
|
44
|
+
members: Dict[str, "Symbol"] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class Scope:
|
|
49
|
+
"""Represents a lexical scope with its own symbol table."""
|
|
50
|
+
|
|
51
|
+
name: str
|
|
52
|
+
parent: Optional["Scope"] = None
|
|
53
|
+
symbols: Dict[str, Symbol] = field(default_factory=dict)
|
|
54
|
+
children: List["Scope"] = field(default_factory=list)
|
|
55
|
+
level: int = 0
|
|
56
|
+
|
|
57
|
+
def define(self, symbol: Symbol) -> Optional[str]:
|
|
58
|
+
"""
|
|
59
|
+
Define a symbol in this scope.
|
|
60
|
+
Returns error message if duplicate, None if success.
|
|
61
|
+
"""
|
|
62
|
+
if symbol.name in self.symbols:
|
|
63
|
+
existing = self.symbols[symbol.name]
|
|
64
|
+
return f"Symbol '{symbol.name}' already declared at line {existing.line}"
|
|
65
|
+
|
|
66
|
+
symbol.scope_level = self.level
|
|
67
|
+
self.symbols[symbol.name] = symbol
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def lookup(self, name: str, recursive: bool = True) -> Optional[Symbol]:
|
|
71
|
+
"""
|
|
72
|
+
Look up a symbol, optionally searching parent scopes.
|
|
73
|
+
"""
|
|
74
|
+
if name in self.symbols:
|
|
75
|
+
return self.symbols[name]
|
|
76
|
+
if recursive and self.parent:
|
|
77
|
+
return self.parent.lookup(name, recursive=True)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def lookup_local(self, name: str) -> Optional[Symbol]:
|
|
81
|
+
"""Look up a symbol only in this scope."""
|
|
82
|
+
return self.symbols.get(name)
|
|
83
|
+
|
|
84
|
+
def all_symbols(self) -> Dict[str, Symbol]:
|
|
85
|
+
"""Get all symbols visible from this scope."""
|
|
86
|
+
result: Dict[str, Symbol] = {}
|
|
87
|
+
if self.parent:
|
|
88
|
+
result.update(self.parent.all_symbols())
|
|
89
|
+
result.update(self.symbols)
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class ScopeError:
|
|
95
|
+
"""Represents a scope-related error."""
|
|
96
|
+
|
|
97
|
+
message: str
|
|
98
|
+
line: int = 0
|
|
99
|
+
column: int = 0
|
|
100
|
+
severity: str = "error"
|
|
101
|
+
|
|
102
|
+
def __str__(self) -> str:
|
|
103
|
+
loc = f"{self.line}:{self.column}" if self.line else ""
|
|
104
|
+
return f"[{self.severity}] {loc} {self.message}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class ScopeAnalysisResult:
|
|
109
|
+
"""Result of scope analysis."""
|
|
110
|
+
|
|
111
|
+
# Root scope (module level)
|
|
112
|
+
root_scope: Scope = field(default_factory=lambda: Scope(name="module", level=0))
|
|
113
|
+
|
|
114
|
+
# All errors found
|
|
115
|
+
errors: List[ScopeError] = field(default_factory=list)
|
|
116
|
+
|
|
117
|
+
# All warnings found
|
|
118
|
+
warnings: List[ScopeError] = field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
# Map from node id to its resolved symbol
|
|
121
|
+
references: Dict[int, Symbol] = field(default_factory=dict)
|
|
122
|
+
|
|
123
|
+
# All scopes created
|
|
124
|
+
all_scopes: List[Scope] = field(default_factory=list)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def has_errors(self) -> bool:
|
|
128
|
+
return len(self.errors) > 0
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def is_valid(self) -> bool:
|
|
132
|
+
return not self.has_errors
|
|
133
|
+
|
|
134
|
+
def add_error(
|
|
135
|
+
self,
|
|
136
|
+
message: str,
|
|
137
|
+
node: Optional[nodes.ASTNode] = None,
|
|
138
|
+
severity: str = "error",
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Add a scope error."""
|
|
141
|
+
line = 0
|
|
142
|
+
column = 0
|
|
143
|
+
|
|
144
|
+
if node and node.source_location:
|
|
145
|
+
line = node.source_location.start_line
|
|
146
|
+
column = node.source_location.start_column
|
|
147
|
+
|
|
148
|
+
error = ScopeError(
|
|
149
|
+
message=message,
|
|
150
|
+
line=line,
|
|
151
|
+
column=column,
|
|
152
|
+
severity=severity,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if severity == "error":
|
|
156
|
+
self.errors.append(error)
|
|
157
|
+
else:
|
|
158
|
+
self.warnings.append(error)
|
|
159
|
+
|
|
160
|
+
def get_symbol(self, node: nodes.ASTNode) -> Optional[Symbol]:
|
|
161
|
+
"""Get the symbol that a node references."""
|
|
162
|
+
return self.references.get(id(node))
|
|
163
|
+
|
|
164
|
+
def set_reference(self, node: nodes.ASTNode, symbol: Symbol) -> None:
|
|
165
|
+
"""Set the symbol that a node references."""
|
|
166
|
+
self.references[id(node)] = symbol
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class ScopeAnalysisVisitor(Visitor):
|
|
170
|
+
"""
|
|
171
|
+
Visitor that builds symbol tables and resolves references.
|
|
172
|
+
|
|
173
|
+
Usage:
|
|
174
|
+
visitor = ScopeAnalysisVisitor()
|
|
175
|
+
module.accept(visitor)
|
|
176
|
+
result = visitor.result
|
|
177
|
+
|
|
178
|
+
if result.has_errors:
|
|
179
|
+
for error in result.errors:
|
|
180
|
+
print(error)
|
|
181
|
+
|
|
182
|
+
# Get symbol for any identifier
|
|
183
|
+
symbol = result.get_symbol(some_identifier_node)
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self) -> None:
|
|
187
|
+
self.result = ScopeAnalysisResult()
|
|
188
|
+
self._current_scope = self.result.root_scope
|
|
189
|
+
self.result.all_scopes.append(self._current_scope)
|
|
190
|
+
|
|
191
|
+
def _push_scope(self, name: str) -> Scope:
|
|
192
|
+
"""Create a new child scope and enter it."""
|
|
193
|
+
new_scope = Scope(
|
|
194
|
+
name=name,
|
|
195
|
+
parent=self._current_scope,
|
|
196
|
+
level=self._current_scope.level + 1,
|
|
197
|
+
)
|
|
198
|
+
self._current_scope.children.append(new_scope)
|
|
199
|
+
self._current_scope = new_scope
|
|
200
|
+
self.result.all_scopes.append(new_scope)
|
|
201
|
+
return new_scope
|
|
202
|
+
|
|
203
|
+
def _pop_scope(self) -> None:
|
|
204
|
+
"""Exit the current scope."""
|
|
205
|
+
if self._current_scope.parent:
|
|
206
|
+
self._current_scope = self._current_scope.parent
|
|
207
|
+
|
|
208
|
+
def _define_symbol(
|
|
209
|
+
self,
|
|
210
|
+
name: str,
|
|
211
|
+
kind: SymbolKind,
|
|
212
|
+
node: Optional[nodes.ASTNode] = None,
|
|
213
|
+
type_annotation: Optional[str] = None,
|
|
214
|
+
) -> Symbol:
|
|
215
|
+
"""Define a new symbol in the current scope."""
|
|
216
|
+
line = 0
|
|
217
|
+
column = 0
|
|
218
|
+
if node and node.source_location:
|
|
219
|
+
line = node.source_location.start_line
|
|
220
|
+
column = node.source_location.start_column
|
|
221
|
+
|
|
222
|
+
symbol = Symbol(
|
|
223
|
+
name=name,
|
|
224
|
+
kind=kind,
|
|
225
|
+
declaration_node=node,
|
|
226
|
+
type_annotation=type_annotation,
|
|
227
|
+
line=line,
|
|
228
|
+
column=column,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
error = self._current_scope.define(symbol)
|
|
232
|
+
if error:
|
|
233
|
+
self.result.add_error(error, node)
|
|
234
|
+
|
|
235
|
+
return symbol
|
|
236
|
+
|
|
237
|
+
def _resolve_identifier(self, name: str, node: nodes.ASTNode) -> Optional[Symbol]:
|
|
238
|
+
"""Resolve an identifier to its symbol."""
|
|
239
|
+
symbol = self._current_scope.lookup(name)
|
|
240
|
+
if symbol:
|
|
241
|
+
self.result.set_reference(node, symbol)
|
|
242
|
+
return symbol
|
|
243
|
+
|
|
244
|
+
# =========================================================================
|
|
245
|
+
# Struct definitions
|
|
246
|
+
# =========================================================================
|
|
247
|
+
|
|
248
|
+
def visit_struct_def(self, node: nodes.StructDefNode) -> Any:
|
|
249
|
+
"""Define struct and its fields."""
|
|
250
|
+
# Define struct in current scope
|
|
251
|
+
struct_symbol = self._define_symbol(
|
|
252
|
+
name=node.name,
|
|
253
|
+
kind=SymbolKind.STRUCT,
|
|
254
|
+
node=node,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Create scope for struct members
|
|
258
|
+
self._push_scope(f"struct_{node.name}")
|
|
259
|
+
|
|
260
|
+
# Define fields
|
|
261
|
+
for field_def in node.fields:
|
|
262
|
+
field_type = None
|
|
263
|
+
if field_def.type_annotation:
|
|
264
|
+
field_type = self._type_to_string(field_def.type_annotation)
|
|
265
|
+
|
|
266
|
+
field_symbol = self._define_symbol(
|
|
267
|
+
name=field_def.name,
|
|
268
|
+
kind=SymbolKind.FIELD if field_type else SymbolKind.ENUM_VARIANT,
|
|
269
|
+
node=field_def,
|
|
270
|
+
type_annotation=field_type,
|
|
271
|
+
)
|
|
272
|
+
struct_symbol.members[field_def.name] = field_symbol
|
|
273
|
+
|
|
274
|
+
self._pop_scope()
|
|
275
|
+
return self.generic_visit(node)
|
|
276
|
+
|
|
277
|
+
def _type_to_string(self, type_node: nodes.TypeNode) -> str:
|
|
278
|
+
"""Convert type node to string representation."""
|
|
279
|
+
if isinstance(type_node, nodes.BuiltinType):
|
|
280
|
+
return type_node.name
|
|
281
|
+
elif isinstance(type_node, nodes.NamedType):
|
|
282
|
+
return type_node.name
|
|
283
|
+
elif isinstance(type_node, nodes.OptionalType):
|
|
284
|
+
return f"{self._type_to_string(type_node.inner)}?"
|
|
285
|
+
elif isinstance(type_node, nodes.ArrayType):
|
|
286
|
+
return f"[{self._type_to_string(type_node.element_type)}]"
|
|
287
|
+
elif isinstance(type_node, nodes.GenericType):
|
|
288
|
+
return type_node.base
|
|
289
|
+
return "unknown"
|
|
290
|
+
|
|
291
|
+
# =========================================================================
|
|
292
|
+
# Function definitions
|
|
293
|
+
# =========================================================================
|
|
294
|
+
|
|
295
|
+
def visit_function_def(self, node: nodes.FunctionDefNode) -> Any:
|
|
296
|
+
"""Define function and its parameters."""
|
|
297
|
+
return_type = None
|
|
298
|
+
if node.return_type:
|
|
299
|
+
return_type = self._type_to_string(node.return_type)
|
|
300
|
+
|
|
301
|
+
# Define function in current scope
|
|
302
|
+
self._define_symbol(
|
|
303
|
+
name=node.name,
|
|
304
|
+
kind=SymbolKind.FUNCTION,
|
|
305
|
+
node=node,
|
|
306
|
+
type_annotation=return_type,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Create scope for function body
|
|
310
|
+
self._push_scope(f"function_{node.name}")
|
|
311
|
+
|
|
312
|
+
# Define parameters
|
|
313
|
+
for param in node.parameters:
|
|
314
|
+
param_type = None
|
|
315
|
+
if param.type_annotation:
|
|
316
|
+
param_type = self._type_to_string(param.type_annotation)
|
|
317
|
+
|
|
318
|
+
self._define_symbol(
|
|
319
|
+
name=param.name,
|
|
320
|
+
kind=SymbolKind.PARAMETER,
|
|
321
|
+
node=param,
|
|
322
|
+
type_annotation=param_type,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Visit body
|
|
326
|
+
if node.body:
|
|
327
|
+
self.visit(node.body)
|
|
328
|
+
|
|
329
|
+
self._pop_scope()
|
|
330
|
+
return None # Don't call generic_visit to avoid re-visiting
|
|
331
|
+
|
|
332
|
+
# =========================================================================
|
|
333
|
+
# Variable declarations
|
|
334
|
+
# =========================================================================
|
|
335
|
+
|
|
336
|
+
def visit_variable_decl(self, node: nodes.VariableDecl) -> Any:
|
|
337
|
+
"""Define variable in current scope."""
|
|
338
|
+
var_type = None
|
|
339
|
+
if node.type_annotation:
|
|
340
|
+
var_type = self._type_to_string(node.type_annotation)
|
|
341
|
+
|
|
342
|
+
self._define_symbol(
|
|
343
|
+
name=node.name,
|
|
344
|
+
kind=SymbolKind.VARIABLE,
|
|
345
|
+
node=node,
|
|
346
|
+
type_annotation=var_type,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Visit initializer
|
|
350
|
+
if node.value:
|
|
351
|
+
self.visit(node.value)
|
|
352
|
+
|
|
353
|
+
return self.generic_visit(node)
|
|
354
|
+
|
|
355
|
+
# =========================================================================
|
|
356
|
+
# Identifier references
|
|
357
|
+
# =========================================================================
|
|
358
|
+
|
|
359
|
+
def visit_identifier(self, node: nodes.IdentifierNode) -> Any:
|
|
360
|
+
"""Resolve identifier to its declaration."""
|
|
361
|
+
symbol = self._resolve_identifier(node.name, node)
|
|
362
|
+
if not symbol:
|
|
363
|
+
# Check if it's a builtin or known constant
|
|
364
|
+
if node.name not in ("TRUE", "FALSE", "pass"):
|
|
365
|
+
self.result.add_error(
|
|
366
|
+
f"Undeclared identifier '{node.name}'",
|
|
367
|
+
node,
|
|
368
|
+
)
|
|
369
|
+
return self.generic_visit(node)
|
|
370
|
+
|
|
371
|
+
def visit_field_access(self, node: nodes.FieldAccessNode) -> Any:
|
|
372
|
+
"""Resolve field access, handling enum variant access."""
|
|
373
|
+
# Visit base expression
|
|
374
|
+
self.visit(node.base)
|
|
375
|
+
|
|
376
|
+
# Try to resolve as enum type.variant
|
|
377
|
+
if isinstance(node.base, nodes.IdentifierNode):
|
|
378
|
+
base_symbol = self.result.get_symbol(node.base)
|
|
379
|
+
if base_symbol and base_symbol.kind == SymbolKind.STRUCT:
|
|
380
|
+
# Check if field exists as member
|
|
381
|
+
if node.field_name in base_symbol.members:
|
|
382
|
+
field_symbol = base_symbol.members[node.field_name]
|
|
383
|
+
self.result.set_reference(node, field_symbol)
|
|
384
|
+
# It's an enum variant access - we trust it exists
|
|
385
|
+
|
|
386
|
+
return self.generic_visit(node)
|
|
387
|
+
|
|
388
|
+
# =========================================================================
|
|
389
|
+
# Match expression
|
|
390
|
+
# =========================================================================
|
|
391
|
+
|
|
392
|
+
def visit_match_expr(self, node: nodes.MatchExprNode) -> Any:
|
|
393
|
+
"""Visit match expression, creating scope for each arm."""
|
|
394
|
+
if node.scrutinee:
|
|
395
|
+
self.visit(node.scrutinee)
|
|
396
|
+
|
|
397
|
+
for arm in node.arms:
|
|
398
|
+
self.visit(arm)
|
|
399
|
+
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
def visit_match_arm(self, node: nodes.MatchArm) -> Any:
|
|
403
|
+
"""Create scope for match arm bindings."""
|
|
404
|
+
# Create scope for arm (for binding patterns)
|
|
405
|
+
self._push_scope("match_arm")
|
|
406
|
+
|
|
407
|
+
# Visit pattern (may introduce bindings)
|
|
408
|
+
self.visit(node.pattern)
|
|
409
|
+
|
|
410
|
+
# Visit guard
|
|
411
|
+
if node.guard:
|
|
412
|
+
self.visit(node.guard)
|
|
413
|
+
|
|
414
|
+
# Visit body
|
|
415
|
+
self.visit(node.body)
|
|
416
|
+
|
|
417
|
+
self._pop_scope()
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
def visit_binding_pattern(self, node: nodes.BindingPattern) -> Any:
|
|
421
|
+
"""Define binding pattern as variable in current scope."""
|
|
422
|
+
# Binding patterns introduce variables
|
|
423
|
+
self._define_symbol(
|
|
424
|
+
name=node.name,
|
|
425
|
+
kind=SymbolKind.VARIABLE,
|
|
426
|
+
node=node,
|
|
427
|
+
)
|
|
428
|
+
return self.generic_visit(node)
|
|
429
|
+
|
|
430
|
+
# =========================================================================
|
|
431
|
+
# Block statements
|
|
432
|
+
# =========================================================================
|
|
433
|
+
|
|
434
|
+
def visit_block(self, node: nodes.Block) -> Any:
|
|
435
|
+
"""Create new scope for block."""
|
|
436
|
+
self._push_scope("block")
|
|
437
|
+
|
|
438
|
+
for stmt in node.statements:
|
|
439
|
+
self.visit(stmt)
|
|
440
|
+
|
|
441
|
+
self._pop_scope()
|
|
442
|
+
return None
|
|
443
|
+
|
|
444
|
+
# =========================================================================
|
|
445
|
+
# Statute blocks
|
|
446
|
+
# =========================================================================
|
|
447
|
+
|
|
448
|
+
def visit_statute(self, node: nodes.StatuteNode) -> Any:
|
|
449
|
+
"""Create scope for statute definitions."""
|
|
450
|
+
scope_name = f"statute_{node.section_number}"
|
|
451
|
+
self._push_scope(scope_name)
|
|
452
|
+
|
|
453
|
+
# Visit all members
|
|
454
|
+
for member in node.definitions:
|
|
455
|
+
self.visit(member)
|
|
456
|
+
for member in node.elements:
|
|
457
|
+
self.visit(member)
|
|
458
|
+
|
|
459
|
+
self._pop_scope()
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
# =========================================================================
|
|
463
|
+
# Module entry point
|
|
464
|
+
# =========================================================================
|
|
465
|
+
|
|
466
|
+
def visit_module(self, node: nodes.ModuleNode) -> Any:
|
|
467
|
+
"""Entry point: analyze all declarations."""
|
|
468
|
+
# First pass: collect struct and function definitions
|
|
469
|
+
for decl in node.declarations:
|
|
470
|
+
if isinstance(decl, nodes.StructDefNode):
|
|
471
|
+
self.visit_struct_def(decl)
|
|
472
|
+
elif isinstance(decl, nodes.FunctionDefNode):
|
|
473
|
+
# Just define the function signature
|
|
474
|
+
return_type = None
|
|
475
|
+
if decl.return_type:
|
|
476
|
+
return_type = self._type_to_string(decl.return_type)
|
|
477
|
+
self._define_symbol(
|
|
478
|
+
name=decl.name,
|
|
479
|
+
kind=SymbolKind.FUNCTION,
|
|
480
|
+
node=decl,
|
|
481
|
+
type_annotation=return_type,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Second pass: full traversal
|
|
485
|
+
for decl in node.declarations:
|
|
486
|
+
if isinstance(decl, nodes.StructDefNode):
|
|
487
|
+
continue # Already processed
|
|
488
|
+
self.visit(decl)
|
|
489
|
+
|
|
490
|
+
return self.result
|