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,357 @@
1
+ """
2
+ Mermaid transpiler - decision tree flowchart generation.
3
+
4
+ Converts match expressions to Mermaid flowchart diagrams.
5
+ """
6
+
7
+ from typing import List, Optional, Dict, Set
8
+
9
+ from yuho.ast import nodes
10
+ from yuho.ast.visitor import Visitor
11
+ from yuho.transpile.base import TranspileTarget, TranspilerBase
12
+
13
+
14
+ class MermaidTranspiler(TranspilerBase, Visitor):
15
+ """
16
+ Transpile Yuho AST to Mermaid flowchart diagrams.
17
+
18
+ Generates decision tree flowcharts from match expressions,
19
+ with diamonds for conditions and rectangles for outcomes.
20
+ """
21
+
22
+ def __init__(self, direction: str = "TD", use_subgraphs: bool = True):
23
+ """
24
+ Initialize the Mermaid transpiler.
25
+
26
+ Args:
27
+ direction: Flowchart direction (TD=top-down, LR=left-right)
28
+ use_subgraphs: Whether to wrap nested match expressions in subgraphs
29
+ """
30
+ self.direction = direction
31
+ self.use_subgraphs = use_subgraphs
32
+ self._output: List[str] = []
33
+ self._node_counter = 0
34
+ self._subgraph_counter = 0
35
+ self._node_ids: Dict[int, str] = {}
36
+ self._nesting_depth = 0 # Track nesting level for subgraph indentation
37
+
38
+ @property
39
+ def target(self) -> TranspileTarget:
40
+ return TranspileTarget.MERMAID
41
+
42
+ def transpile(self, ast: nodes.ModuleNode) -> str:
43
+ """Transpile AST to Mermaid diagram."""
44
+ self._output = []
45
+ self._node_counter = 0
46
+ self._subgraph_counter = 0
47
+ self._nesting_depth = 0
48
+
49
+ # Header
50
+ self._emit(f"flowchart {self.direction}")
51
+
52
+ # Process each statute
53
+ for statute in ast.statutes:
54
+ self._transpile_statute(statute)
55
+
56
+ # Process standalone match expressions in functions
57
+ for func in ast.function_defs:
58
+ self._transpile_function(func)
59
+
60
+ return "\n".join(self._output)
61
+
62
+ def _emit(self, line: str) -> None:
63
+ """Add a line to output."""
64
+ self._output.append(line)
65
+
66
+ def _new_node_id(self, prefix: str = "N") -> str:
67
+ """Generate a new unique node ID."""
68
+ self._node_counter += 1
69
+ return f"{prefix}{self._node_counter}"
70
+
71
+ def _new_subgraph_id(self, name: str = "sub") -> str:
72
+ """Generate a new unique subgraph ID."""
73
+ self._subgraph_counter += 1
74
+ return f"{name}_{self._subgraph_counter}"
75
+
76
+ def _indent(self) -> str:
77
+ """Get current indentation string based on nesting depth."""
78
+ return " " * (self._nesting_depth + 1)
79
+
80
+ def _escape_text(self, text: str) -> str:
81
+ """Escape text for Mermaid labels."""
82
+ # Escape quotes and special chars
83
+ text = text.replace('"', "'")
84
+ text = text.replace("<", "&lt;")
85
+ text = text.replace(">", "&gt;")
86
+ # Truncate long text
87
+ if len(text) > 50:
88
+ text = text[:47] + "..."
89
+ return text
90
+
91
+ # =========================================================================
92
+ # Statute processing
93
+ # =========================================================================
94
+
95
+ def _transpile_statute(self, statute: nodes.StatuteNode) -> None:
96
+ """Generate flowchart for a statute."""
97
+ title = statute.title.value if statute.title else statute.section_number
98
+ self._emit(f" %% Statute: {self._escape_text(title)}")
99
+
100
+ # Start node
101
+ start_id = self._new_node_id("START")
102
+ self._emit(f" {start_id}([Section {statute.section_number}])")
103
+
104
+ # Process elements that contain match expressions
105
+ prev_id = start_id
106
+ for elem in statute.elements:
107
+ if isinstance(elem.description, nodes.MatchExprNode):
108
+ elem_start = self._new_node_id("ELEM")
109
+ self._emit(f" {elem_start}[/{elem.name}/]")
110
+ self._emit(f" {prev_id} --> {elem_start}")
111
+
112
+ end_id = self._transpile_match_expr(elem.description, elem_start)
113
+ prev_id = end_id
114
+ else:
115
+ elem_id = self._new_node_id("ELEM")
116
+ label = self._escape_text(self._expr_to_label(elem.description))
117
+ self._emit(f" {elem_id}[{label}]")
118
+ self._emit(f" {prev_id} --> {elem_id}")
119
+ prev_id = elem_id
120
+
121
+ # Penalty outcome
122
+ if statute.penalty:
123
+ penalty_id = self._new_node_id("PENALTY")
124
+ penalty_text = self._penalty_to_label(statute.penalty)
125
+ self._emit(f" {penalty_id}[[\"{self._escape_text(penalty_text)}\"]]")
126
+ self._emit(f" {prev_id} --> {penalty_id}")
127
+
128
+ self._emit("")
129
+
130
+ # =========================================================================
131
+ # Function processing
132
+ # =========================================================================
133
+
134
+ def _transpile_function(self, func: nodes.FunctionDefNode) -> None:
135
+ """Generate flowchart for function containing match expressions."""
136
+ # Find match expressions in function body
137
+ match_exprs = self._find_match_exprs(func.body)
138
+ if not match_exprs:
139
+ return
140
+
141
+ self._emit(f" %% Function: {func.name}")
142
+
143
+ start_id = self._new_node_id("FN")
144
+ params = ", ".join(p.name for p in func.params)
145
+ self._emit(f" {start_id}([{func.name}({params})])")
146
+
147
+ prev_id = start_id
148
+ for match_expr in match_exprs:
149
+ end_id = self._transpile_match_expr(match_expr, prev_id)
150
+ prev_id = end_id
151
+
152
+ self._emit("")
153
+
154
+ def _find_match_exprs(self, node: nodes.ASTNode) -> List[nodes.MatchExprNode]:
155
+ """Find all match expressions in a node tree."""
156
+ matches: List[nodes.MatchExprNode] = []
157
+
158
+ if isinstance(node, nodes.MatchExprNode):
159
+ matches.append(node)
160
+
161
+ for child in node.children():
162
+ matches.extend(self._find_match_exprs(child))
163
+
164
+ return matches
165
+
166
+ # =========================================================================
167
+ # Match expression to flowchart
168
+ # =========================================================================
169
+
170
+ def _transpile_match_expr(
171
+ self,
172
+ match: nodes.MatchExprNode,
173
+ start_id: str,
174
+ subgraph_name: Optional[str] = None,
175
+ ) -> str:
176
+ """
177
+ Generate flowchart nodes for a match expression.
178
+
179
+ Returns the ID of the end/merge node.
180
+ """
181
+ # Check if this contains nested match expressions
182
+ has_nested = any(
183
+ isinstance(arm.body, nodes.MatchExprNode)
184
+ for arm in match.arms
185
+ )
186
+
187
+ # Use subgraph for nested match at depth > 0 or if explicitly named
188
+ use_subgraph = (
189
+ self.use_subgraphs and
190
+ (self._nesting_depth > 0 or subgraph_name is not None)
191
+ )
192
+
193
+ subgraph_id = None
194
+ if use_subgraph:
195
+ subgraph_id = self._new_subgraph_id("match")
196
+ label = subgraph_name or "nested decision"
197
+ self._emit(f"{self._indent()}subgraph {subgraph_id}[\"{self._escape_text(label)}\"]")
198
+ self._nesting_depth += 1
199
+
200
+ # Create decision node for scrutinee
201
+ if match.scrutinee:
202
+ scrutinee_label = self._expr_to_label(match.scrutinee)
203
+ decision_id = self._new_node_id("D")
204
+ self._emit(f"{self._indent()}{decision_id}{{{{{self._escape_text(scrutinee_label)}}}}}")
205
+ self._emit(f"{self._indent()}{start_id} --> {decision_id}")
206
+ else:
207
+ decision_id = start_id
208
+
209
+ # End node for merging
210
+ end_id = self._new_node_id("END")
211
+
212
+ # Process each arm
213
+ for i, arm in enumerate(match.arms):
214
+ arm_outcome_id = self._transpile_match_arm(arm, decision_id, i)
215
+ self._emit(f"{self._indent()}{arm_outcome_id} --> {end_id}")
216
+
217
+ # Create end node (circle for merge point)
218
+ self._emit(f"{self._indent()}{end_id}((*))")
219
+
220
+ # Close subgraph if opened
221
+ if use_subgraph:
222
+ self._nesting_depth -= 1
223
+ self._emit(f"{self._indent()}end")
224
+
225
+ return end_id
226
+
227
+ def _transpile_match_arm(self, arm: nodes.MatchArm, from_id: str, index: int) -> str:
228
+ """
229
+ Generate nodes for a match arm.
230
+
231
+ Returns the ID of the outcome node.
232
+ """
233
+ # Edge label from pattern
234
+ pattern_label = self._pattern_to_label(arm.pattern)
235
+
236
+ # If there's a guard, create intermediate decision node
237
+ if arm.guard:
238
+ guard_id = self._new_node_id("G")
239
+ guard_label = self._expr_to_label(arm.guard)
240
+ self._emit(f"{self._indent()}{guard_id}{{{{{self._escape_text(guard_label)}}}}}")
241
+ self._emit(f"{self._indent()}{from_id} -->|\"{self._escape_text(pattern_label)}\"| {guard_id}")
242
+
243
+ # Check if body is a nested match expression
244
+ if isinstance(arm.body, nodes.MatchExprNode):
245
+ # Generate subgraph for nested match
246
+ nested_label = f"when {pattern_label} (guarded)"
247
+ end_id = self._transpile_match_expr(arm.body, guard_id, nested_label)
248
+ return end_id
249
+ else:
250
+ # True path goes to outcome
251
+ outcome_id = self._new_node_id("O")
252
+ body_label = self._expr_to_label(arm.body)
253
+ self._emit(f"{self._indent()}{outcome_id}[\"{self._escape_text(body_label)}\"]")
254
+ self._emit(f"{self._indent()}{guard_id} -->|\"Yes\"| {outcome_id}")
255
+ return outcome_id
256
+ else:
257
+ # Check if body is a nested match expression
258
+ if isinstance(arm.body, nodes.MatchExprNode):
259
+ # Create a connector node for the nested match
260
+ connector_id = self._new_node_id("C")
261
+ self._emit(f"{self._indent()}{connector_id}((...))")
262
+ self._emit(f"{self._indent()}{from_id} -->|\"{self._escape_text(pattern_label)}\"| {connector_id}")
263
+
264
+ # Generate subgraph for nested match
265
+ nested_label = f"when {pattern_label}"
266
+ end_id = self._transpile_match_expr(arm.body, connector_id, nested_label)
267
+ return end_id
268
+ else:
269
+ # Direct path to outcome
270
+ outcome_id = self._new_node_id("O")
271
+ body_label = self._expr_to_label(arm.body)
272
+ self._emit(f"{self._indent()}{outcome_id}[\"{self._escape_text(body_label)}\"]")
273
+ self._emit(f"{self._indent()}{from_id} -->|\"{self._escape_text(pattern_label)}\"| {outcome_id}")
274
+ return outcome_id
275
+
276
+ # =========================================================================
277
+ # Label generation helpers
278
+ # =========================================================================
279
+
280
+ def _expr_to_label(self, node: nodes.ASTNode) -> str:
281
+ """Convert expression to label text."""
282
+ if isinstance(node, nodes.IntLit):
283
+ return str(node.value)
284
+ elif isinstance(node, nodes.FloatLit):
285
+ return str(node.value)
286
+ elif isinstance(node, nodes.BoolLit):
287
+ return "TRUE" if node.value else "FALSE"
288
+ elif isinstance(node, nodes.StringLit):
289
+ return node.value
290
+ elif isinstance(node, nodes.MoneyNode):
291
+ return f"${node.amount}"
292
+ elif isinstance(node, nodes.PercentNode):
293
+ return f"{node.value}%"
294
+ elif isinstance(node, nodes.DateNode):
295
+ return node.value.isoformat()
296
+ elif isinstance(node, nodes.DurationNode):
297
+ return str(node)
298
+ elif isinstance(node, nodes.IdentifierNode):
299
+ return node.name
300
+ elif isinstance(node, nodes.FieldAccessNode):
301
+ base = self._expr_to_label(node.base)
302
+ return f"{base}.{node.field_name}"
303
+ elif isinstance(node, nodes.IndexAccessNode):
304
+ base = self._expr_to_label(node.base)
305
+ index = self._expr_to_label(node.index)
306
+ return f"{base}[{index}]"
307
+ elif isinstance(node, nodes.FunctionCallNode):
308
+ callee = self._expr_to_label(node.callee)
309
+ args = ", ".join(self._expr_to_label(a) for a in node.args)
310
+ return f"{callee}({args})"
311
+ elif isinstance(node, nodes.BinaryExprNode):
312
+ left = self._expr_to_label(node.left)
313
+ right = self._expr_to_label(node.right)
314
+ return f"{left} {node.operator} {right}"
315
+ elif isinstance(node, nodes.UnaryExprNode):
316
+ operand = self._expr_to_label(node.operand)
317
+ return f"{node.operator}{operand}"
318
+ elif isinstance(node, nodes.PassExprNode):
319
+ return "pass"
320
+ elif isinstance(node, nodes.MatchExprNode):
321
+ return "[nested decision]"
322
+ elif isinstance(node, nodes.StructLiteralNode):
323
+ if node.struct_name:
324
+ return f"new {node.struct_name}"
325
+ return "{...}"
326
+ else:
327
+ return "?"
328
+
329
+ def _pattern_to_label(self, pattern: nodes.PatternNode) -> str:
330
+ """Convert pattern to edge label."""
331
+ if isinstance(pattern, nodes.WildcardPattern):
332
+ return "otherwise"
333
+ elif isinstance(pattern, nodes.LiteralPattern):
334
+ return self._expr_to_label(pattern.literal)
335
+ elif isinstance(pattern, nodes.BindingPattern):
336
+ return f"-> {pattern.name}"
337
+ elif isinstance(pattern, nodes.StructPattern):
338
+ fields = ", ".join(fp.name for fp in pattern.fields)
339
+ return f"{pattern.type_name}{{{fields}}}"
340
+ else:
341
+ return "?"
342
+
343
+ def _penalty_to_label(self, penalty: nodes.PenaltyNode) -> str:
344
+ """Convert penalty to label text."""
345
+ parts: List[str] = []
346
+
347
+ if penalty.imprisonment_max:
348
+ duration = str(penalty.imprisonment_max)
349
+ parts.append(f"Imprisonment up to {duration}")
350
+
351
+ if penalty.fine_max:
352
+ parts.append(f"Fine up to ${penalty.fine_max.amount}")
353
+
354
+ if penalty.supplementary:
355
+ parts.append(penalty.supplementary.value)
356
+
357
+ return "; ".join(parts) if parts else "Penalty TBD"
@@ -0,0 +1,275 @@
1
+ """
2
+ TranspilerRegistry singleton for managing transpiler instances.
3
+
4
+ Provides a centralized registry mapping TranspileTarget to Transpiler
5
+ instances, supporting both built-in and user-registered transpilers.
6
+ """
7
+
8
+ from typing import Dict, Optional, Type, Callable
9
+ import threading
10
+
11
+ from yuho.transpile.base import TranspileTarget, TranspilerBase
12
+
13
+
14
+ class TranspilerRegistry:
15
+ """
16
+ Singleton registry mapping TranspileTarget to Transpiler instances.
17
+
18
+ Provides lazy instantiation of transpilers and supports registration
19
+ of custom transpilers.
20
+
21
+ Usage:
22
+ registry = TranspilerRegistry.instance()
23
+ transpiler = registry.get(TranspileTarget.JSON)
24
+ output = transpiler.transpile(ast)
25
+
26
+ # Register custom transpiler
27
+ registry.register(MyCustomTarget, MyCustomTranspiler)
28
+
29
+ Thread Safety:
30
+ The registry is thread-safe for both reading and registration.
31
+ """
32
+
33
+ _instance: Optional["TranspilerRegistry"] = None
34
+ _lock = threading.Lock()
35
+
36
+ def __new__(cls) -> "TranspilerRegistry":
37
+ """Ensure only one instance exists (singleton pattern)."""
38
+ if cls._instance is None:
39
+ with cls._lock:
40
+ # Double-check locking
41
+ if cls._instance is None:
42
+ cls._instance = super().__new__(cls)
43
+ cls._instance._initialized = False
44
+ return cls._instance
45
+
46
+ def __init__(self):
47
+ """Initialize the registry with built-in transpilers."""
48
+ if self._initialized:
49
+ return
50
+
51
+ with self._lock:
52
+ if self._initialized:
53
+ return
54
+
55
+ # Registry maps target -> transpiler class
56
+ self._registry: Dict[TranspileTarget, Type[TranspilerBase]] = {}
57
+ # Cache of instantiated transpilers
58
+ self._instances: Dict[TranspileTarget, TranspilerBase] = {}
59
+ # Factory functions for custom creation
60
+ self._factories: Dict[TranspileTarget, Callable[[], TranspilerBase]] = {}
61
+
62
+ # Register built-in transpilers lazily
63
+ self._register_builtins()
64
+ self._initialized = True
65
+
66
+ def _register_builtins(self) -> None:
67
+ """Register all built-in transpilers."""
68
+ # Import lazily to avoid circular imports
69
+ from yuho.transpile.json_transpiler import JSONTranspiler
70
+ from yuho.transpile.jsonld_transpiler import JSONLDTranspiler
71
+ from yuho.transpile.english_transpiler import EnglishTranspiler
72
+ from yuho.transpile.latex_transpiler import LaTeXTranspiler
73
+ from yuho.transpile.mermaid_transpiler import MermaidTranspiler
74
+ from yuho.transpile.alloy_transpiler import AlloyTranspiler
75
+ from yuho.transpile.graphql_transpiler import GraphQLTranspiler
76
+ from yuho.transpile.blocks_transpiler import BlocksTranspiler
77
+
78
+ self._registry[TranspileTarget.JSON] = JSONTranspiler
79
+ self._registry[TranspileTarget.JSON_LD] = JSONLDTranspiler
80
+ self._registry[TranspileTarget.ENGLISH] = EnglishTranspiler
81
+ self._registry[TranspileTarget.LATEX] = LaTeXTranspiler
82
+ self._registry[TranspileTarget.MERMAID] = MermaidTranspiler
83
+ self._registry[TranspileTarget.ALLOY] = AlloyTranspiler
84
+ self._registry[TranspileTarget.GRAPHQL] = GraphQLTranspiler
85
+ self._registry[TranspileTarget.BLOCKS] = BlocksTranspiler
86
+
87
+ @classmethod
88
+ def instance(cls) -> "TranspilerRegistry":
89
+ """
90
+ Get the singleton instance of the registry.
91
+
92
+ Returns:
93
+ The global TranspilerRegistry instance.
94
+ """
95
+ return cls()
96
+
97
+ @classmethod
98
+ def reset(cls) -> None:
99
+ """
100
+ Reset the singleton instance (primarily for testing).
101
+
102
+ Clears all registered transpilers and cached instances.
103
+ """
104
+ with cls._lock:
105
+ if cls._instance is not None:
106
+ cls._instance._registry.clear()
107
+ cls._instance._instances.clear()
108
+ cls._instance._factories.clear()
109
+ cls._instance._initialized = False
110
+ cls._instance = None
111
+
112
+ def get(self, target: TranspileTarget) -> TranspilerBase:
113
+ """
114
+ Get a transpiler instance for the given target.
115
+
116
+ Instances are cached for reuse. If no transpiler is registered
117
+ for the target, raises KeyError.
118
+
119
+ Args:
120
+ target: The transpilation target.
121
+
122
+ Returns:
123
+ A transpiler instance for the target.
124
+
125
+ Raises:
126
+ KeyError: If no transpiler is registered for the target.
127
+ """
128
+ # Check cache first
129
+ if target in self._instances:
130
+ return self._instances[target]
131
+
132
+ with self._lock:
133
+ # Double-check after acquiring lock
134
+ if target in self._instances:
135
+ return self._instances[target]
136
+
137
+ # Check for factory function
138
+ if target in self._factories:
139
+ instance = self._factories[target]()
140
+ self._instances[target] = instance
141
+ return instance
142
+
143
+ # Check for registered class
144
+ if target in self._registry:
145
+ instance = self._registry[target]()
146
+ self._instances[target] = instance
147
+ return instance
148
+
149
+ raise KeyError(f"No transpiler registered for target: {target}")
150
+
151
+ def get_or_none(self, target: TranspileTarget) -> Optional[TranspilerBase]:
152
+ """
153
+ Get a transpiler instance, returning None if not registered.
154
+
155
+ Args:
156
+ target: The transpilation target.
157
+
158
+ Returns:
159
+ A transpiler instance, or None if not registered.
160
+ """
161
+ try:
162
+ return self.get(target)
163
+ except KeyError:
164
+ return None
165
+
166
+ def register(
167
+ self,
168
+ target: TranspileTarget,
169
+ transpiler_class: Type[TranspilerBase],
170
+ ) -> None:
171
+ """
172
+ Register a transpiler class for a target.
173
+
174
+ Args:
175
+ target: The transpilation target to register.
176
+ transpiler_class: The transpiler class to instantiate.
177
+ """
178
+ with self._lock:
179
+ self._registry[target] = transpiler_class
180
+ # Clear cached instance to force re-creation
181
+ self._instances.pop(target, None)
182
+
183
+ def register_factory(
184
+ self,
185
+ target: TranspileTarget,
186
+ factory: Callable[[], TranspilerBase],
187
+ ) -> None:
188
+ """
189
+ Register a factory function for creating a transpiler.
190
+
191
+ Useful when transpiler creation requires custom initialization.
192
+
193
+ Args:
194
+ target: The transpilation target to register.
195
+ factory: A callable that returns a TranspilerBase instance.
196
+ """
197
+ with self._lock:
198
+ self._factories[target] = factory
199
+ # Clear cached instance to force re-creation
200
+ self._instances.pop(target, None)
201
+
202
+ def register_instance(
203
+ self,
204
+ target: TranspileTarget,
205
+ instance: TranspilerBase,
206
+ ) -> None:
207
+ """
208
+ Register a pre-created transpiler instance.
209
+
210
+ Args:
211
+ target: The transpilation target to register.
212
+ instance: The transpiler instance to use.
213
+ """
214
+ with self._lock:
215
+ self._instances[target] = instance
216
+
217
+ def unregister(self, target: TranspileTarget) -> bool:
218
+ """
219
+ Remove a transpiler registration.
220
+
221
+ Args:
222
+ target: The target to unregister.
223
+
224
+ Returns:
225
+ True if a registration was removed, False otherwise.
226
+ """
227
+ with self._lock:
228
+ removed = False
229
+ if target in self._registry:
230
+ del self._registry[target]
231
+ removed = True
232
+ if target in self._factories:
233
+ del self._factories[target]
234
+ removed = True
235
+ if target in self._instances:
236
+ del self._instances[target]
237
+ removed = True
238
+ return removed
239
+
240
+ def is_registered(self, target: TranspileTarget) -> bool:
241
+ """
242
+ Check if a transpiler is registered for the target.
243
+
244
+ Args:
245
+ target: The target to check.
246
+
247
+ Returns:
248
+ True if a transpiler is registered.
249
+ """
250
+ return (
251
+ target in self._registry
252
+ or target in self._factories
253
+ or target in self._instances
254
+ )
255
+
256
+ def registered_targets(self) -> list[TranspileTarget]:
257
+ """
258
+ Get all registered transpilation targets.
259
+
260
+ Returns:
261
+ List of registered TranspileTarget values.
262
+ """
263
+ targets = set(self._registry.keys())
264
+ targets.update(self._factories.keys())
265
+ targets.update(self._instances.keys())
266
+ return list(targets)
267
+
268
+ def clear_cache(self) -> None:
269
+ """
270
+ Clear all cached transpiler instances.
271
+
272
+ Registered classes and factories are preserved.
273
+ """
274
+ with self._lock:
275
+ self._instances.clear()
@@ -0,0 +1,43 @@
1
+ """
2
+ Yuho verify module - formal verification with Alloy and Z3.
3
+
4
+ Provides:
5
+ - Alloy model generation from statute AST
6
+ - Alloy analyzer integration for bounded model checking
7
+ - Z3 constraint generation and satisfiability checking
8
+ - Z3 constraint generation from AST (parallel to Alloy)
9
+ - Combined Alloy+Z3 verification with cross-validation
10
+ - Counterexample parsing and diagnostic generation
11
+ """
12
+
13
+ from yuho.verify.alloy import (
14
+ AlloyGenerator,
15
+ AlloyAnalyzer,
16
+ AlloyCounterexample,
17
+ )
18
+ from yuho.verify.z3_solver import (
19
+ Z3Solver,
20
+ Z3Generator,
21
+ Z3Diagnostic,
22
+ Z3CounterexampleExtractor,
23
+ ConstraintGenerator,
24
+ SatisfiabilityResult,
25
+ )
26
+ from yuho.verify.combined import (
27
+ CombinedVerifier,
28
+ CombinedVerificationResult,
29
+ )
30
+
31
+ __all__ = [
32
+ "AlloyGenerator",
33
+ "AlloyAnalyzer",
34
+ "AlloyCounterexample",
35
+ "Z3Solver",
36
+ "Z3Generator",
37
+ "Z3Diagnostic",
38
+ "Z3CounterexampleExtractor",
39
+ "ConstraintGenerator",
40
+ "SatisfiabilityResult",
41
+ "CombinedVerifier",
42
+ "CombinedVerificationResult",
43
+ ]