yuho 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. yuho/__init__.py +16 -0
  2. yuho/ast/__init__.py +196 -0
  3. yuho/ast/builder.py +926 -0
  4. yuho/ast/constant_folder.py +280 -0
  5. yuho/ast/dead_code.py +199 -0
  6. yuho/ast/exhaustiveness.py +503 -0
  7. yuho/ast/nodes.py +907 -0
  8. yuho/ast/overlap.py +291 -0
  9. yuho/ast/reachability.py +293 -0
  10. yuho/ast/scope_analysis.py +490 -0
  11. yuho/ast/transformer.py +490 -0
  12. yuho/ast/type_check.py +471 -0
  13. yuho/ast/type_inference.py +425 -0
  14. yuho/ast/visitor.py +239 -0
  15. yuho/cli/__init__.py +14 -0
  16. yuho/cli/commands/__init__.py +1 -0
  17. yuho/cli/commands/api.py +431 -0
  18. yuho/cli/commands/ast_viz.py +334 -0
  19. yuho/cli/commands/check.py +218 -0
  20. yuho/cli/commands/config.py +311 -0
  21. yuho/cli/commands/contribute.py +122 -0
  22. yuho/cli/commands/diff.py +487 -0
  23. yuho/cli/commands/explain.py +240 -0
  24. yuho/cli/commands/fmt.py +253 -0
  25. yuho/cli/commands/generate.py +316 -0
  26. yuho/cli/commands/graph.py +410 -0
  27. yuho/cli/commands/init.py +120 -0
  28. yuho/cli/commands/library.py +656 -0
  29. yuho/cli/commands/lint.py +503 -0
  30. yuho/cli/commands/lsp.py +36 -0
  31. yuho/cli/commands/preview.py +377 -0
  32. yuho/cli/commands/repl.py +444 -0
  33. yuho/cli/commands/serve.py +44 -0
  34. yuho/cli/commands/test.py +528 -0
  35. yuho/cli/commands/transpile.py +121 -0
  36. yuho/cli/commands/wizard.py +370 -0
  37. yuho/cli/completions.py +182 -0
  38. yuho/cli/error_formatter.py +193 -0
  39. yuho/cli/main.py +1064 -0
  40. yuho/config/__init__.py +46 -0
  41. yuho/config/loader.py +235 -0
  42. yuho/config/mask.py +194 -0
  43. yuho/config/schema.py +147 -0
  44. yuho/library/__init__.py +84 -0
  45. yuho/library/index.py +328 -0
  46. yuho/library/install.py +699 -0
  47. yuho/library/lockfile.py +330 -0
  48. yuho/library/package.py +421 -0
  49. yuho/library/resolver.py +791 -0
  50. yuho/library/signature.py +335 -0
  51. yuho/llm/__init__.py +45 -0
  52. yuho/llm/config.py +75 -0
  53. yuho/llm/factory.py +123 -0
  54. yuho/llm/prompts.py +146 -0
  55. yuho/llm/providers.py +383 -0
  56. yuho/llm/utils.py +470 -0
  57. yuho/lsp/__init__.py +14 -0
  58. yuho/lsp/code_action_handler.py +518 -0
  59. yuho/lsp/completion_handler.py +85 -0
  60. yuho/lsp/diagnostics.py +100 -0
  61. yuho/lsp/hover_handler.py +130 -0
  62. yuho/lsp/server.py +1425 -0
  63. yuho/mcp/__init__.py +10 -0
  64. yuho/mcp/server.py +1452 -0
  65. yuho/parser/__init__.py +8 -0
  66. yuho/parser/source_location.py +108 -0
  67. yuho/parser/wrapper.py +311 -0
  68. yuho/testing/__init__.py +48 -0
  69. yuho/testing/coverage.py +274 -0
  70. yuho/testing/fixtures.py +263 -0
  71. yuho/transpile/__init__.py +52 -0
  72. yuho/transpile/alloy_transpiler.py +546 -0
  73. yuho/transpile/base.py +100 -0
  74. yuho/transpile/blocks_transpiler.py +338 -0
  75. yuho/transpile/english_transpiler.py +470 -0
  76. yuho/transpile/graphql_transpiler.py +404 -0
  77. yuho/transpile/json_transpiler.py +217 -0
  78. yuho/transpile/jsonld_transpiler.py +250 -0
  79. yuho/transpile/latex_preamble.py +161 -0
  80. yuho/transpile/latex_transpiler.py +406 -0
  81. yuho/transpile/latex_utils.py +206 -0
  82. yuho/transpile/mermaid_transpiler.py +357 -0
  83. yuho/transpile/registry.py +275 -0
  84. yuho/verify/__init__.py +43 -0
  85. yuho/verify/alloy.py +352 -0
  86. yuho/verify/combined.py +218 -0
  87. yuho/verify/z3_solver.py +1155 -0
  88. yuho-5.0.0.dist-info/METADATA +186 -0
  89. yuho-5.0.0.dist-info/RECORD +91 -0
  90. yuho-5.0.0.dist-info/WHEEL +4 -0
  91. yuho-5.0.0.dist-info/entry_points.txt +2 -0
@@ -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