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,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Block-notation transpiler for Yuho.
|
|
3
|
+
|
|
4
|
+
Outputs a structured text representation of statute blocks,
|
|
5
|
+
providing a hierarchical view of the statute structure.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Any, Optional
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from yuho.transpile.base import TranspilerBase, TranspileTarget
|
|
12
|
+
from yuho.ast.nodes import (
|
|
13
|
+
ModuleNode,
|
|
14
|
+
StatuteNode,
|
|
15
|
+
DefinitionNode,
|
|
16
|
+
ElementNode,
|
|
17
|
+
PenaltyNode,
|
|
18
|
+
IllustrationNode,
|
|
19
|
+
)
|
|
20
|
+
from yuho.ast.visitor import ASTVisitor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Block:
|
|
25
|
+
"""Represents a block in block-notation."""
|
|
26
|
+
block_type: str
|
|
27
|
+
name: str
|
|
28
|
+
content: List[str]
|
|
29
|
+
children: List["Block"]
|
|
30
|
+
level: int = 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BlockBuilder(ASTVisitor):
|
|
34
|
+
"""Builds block notation from AST."""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.blocks: List[Block] = []
|
|
38
|
+
self.current_statute: Optional[Block] = None
|
|
39
|
+
|
|
40
|
+
def visit_module(self, node: ModuleNode) -> None:
|
|
41
|
+
"""Visit module node."""
|
|
42
|
+
for child in getattr(node, "children", []) or []:
|
|
43
|
+
self.visit(child)
|
|
44
|
+
|
|
45
|
+
for statute in getattr(node, "statutes", []) or []:
|
|
46
|
+
self.visit(statute)
|
|
47
|
+
|
|
48
|
+
def visit_statute(self, node: StatuteNode) -> None:
|
|
49
|
+
"""Visit statute node."""
|
|
50
|
+
section = getattr(node, "section_number", "") or getattr(node, "section", "") or "?"
|
|
51
|
+
title = getattr(node, "title", "") or ""
|
|
52
|
+
|
|
53
|
+
statute_block = Block(
|
|
54
|
+
block_type="STATUTE",
|
|
55
|
+
name=f"S{section}",
|
|
56
|
+
content=[f"title: {title}"],
|
|
57
|
+
children=[],
|
|
58
|
+
level=0,
|
|
59
|
+
)
|
|
60
|
+
self.current_statute = statute_block
|
|
61
|
+
|
|
62
|
+
# Visit children
|
|
63
|
+
if hasattr(node, "definitions") and node.definitions:
|
|
64
|
+
self._build_definitions_block(node.definitions)
|
|
65
|
+
|
|
66
|
+
if hasattr(node, "elements") and node.elements:
|
|
67
|
+
self._build_elements_block(node.elements)
|
|
68
|
+
|
|
69
|
+
if hasattr(node, "penalty") and node.penalty:
|
|
70
|
+
self._build_penalty_block(node.penalty)
|
|
71
|
+
|
|
72
|
+
if hasattr(node, "illustrations") and node.illustrations:
|
|
73
|
+
self._build_illustrations_block(node.illustrations)
|
|
74
|
+
|
|
75
|
+
self.blocks.append(statute_block)
|
|
76
|
+
self.current_statute = None
|
|
77
|
+
|
|
78
|
+
def _build_definitions_block(self, definitions: List[Any]) -> None:
|
|
79
|
+
"""Build definitions block."""
|
|
80
|
+
if not self.current_statute:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
content = []
|
|
84
|
+
for defn in definitions:
|
|
85
|
+
term = getattr(defn, "term", "") or getattr(defn, "name", "") or "?"
|
|
86
|
+
definition = getattr(defn, "definition", "") or getattr(defn, "value", "") or ""
|
|
87
|
+
# Truncate long definitions
|
|
88
|
+
if len(definition) > 60:
|
|
89
|
+
definition = definition[:57] + "..."
|
|
90
|
+
content.append(f"{term} := {definition}")
|
|
91
|
+
|
|
92
|
+
block = Block(
|
|
93
|
+
block_type="DEFINITIONS",
|
|
94
|
+
name="defs",
|
|
95
|
+
content=content,
|
|
96
|
+
children=[],
|
|
97
|
+
level=1,
|
|
98
|
+
)
|
|
99
|
+
self.current_statute.children.append(block)
|
|
100
|
+
|
|
101
|
+
def _build_elements_block(self, elements: List[Any]) -> None:
|
|
102
|
+
"""Build elements block."""
|
|
103
|
+
if not self.current_statute:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
children = []
|
|
107
|
+
|
|
108
|
+
# Group by type
|
|
109
|
+
actus_reus = []
|
|
110
|
+
mens_rea = []
|
|
111
|
+
other = []
|
|
112
|
+
|
|
113
|
+
for elem in elements:
|
|
114
|
+
elem_type = getattr(elem, "element_type", "") or getattr(elem, "type", "") or "element"
|
|
115
|
+
name = getattr(elem, "name", "") or getattr(elem, "identifier", "") or "?"
|
|
116
|
+
desc = getattr(elem, "description", "") or getattr(elem, "value", "") or ""
|
|
117
|
+
|
|
118
|
+
if len(desc) > 50:
|
|
119
|
+
desc = desc[:47] + "..."
|
|
120
|
+
|
|
121
|
+
entry = f"{name}: {desc}"
|
|
122
|
+
|
|
123
|
+
if "actus" in elem_type.lower():
|
|
124
|
+
actus_reus.append(entry)
|
|
125
|
+
elif "mens" in elem_type.lower():
|
|
126
|
+
mens_rea.append(entry)
|
|
127
|
+
else:
|
|
128
|
+
other.append(entry)
|
|
129
|
+
|
|
130
|
+
if actus_reus:
|
|
131
|
+
children.append(Block(
|
|
132
|
+
block_type="ACTUS_REUS",
|
|
133
|
+
name="act",
|
|
134
|
+
content=actus_reus,
|
|
135
|
+
children=[],
|
|
136
|
+
level=2,
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
if mens_rea:
|
|
140
|
+
children.append(Block(
|
|
141
|
+
block_type="MENS_REA",
|
|
142
|
+
name="intent",
|
|
143
|
+
content=mens_rea,
|
|
144
|
+
children=[],
|
|
145
|
+
level=2,
|
|
146
|
+
))
|
|
147
|
+
|
|
148
|
+
if other:
|
|
149
|
+
children.append(Block(
|
|
150
|
+
block_type="CIRCUMSTANCE",
|
|
151
|
+
name="context",
|
|
152
|
+
content=other,
|
|
153
|
+
children=[],
|
|
154
|
+
level=2,
|
|
155
|
+
))
|
|
156
|
+
|
|
157
|
+
block = Block(
|
|
158
|
+
block_type="ELEMENTS",
|
|
159
|
+
name="elements",
|
|
160
|
+
content=[],
|
|
161
|
+
children=children,
|
|
162
|
+
level=1,
|
|
163
|
+
)
|
|
164
|
+
self.current_statute.children.append(block)
|
|
165
|
+
|
|
166
|
+
def _build_penalty_block(self, penalty: Any) -> None:
|
|
167
|
+
"""Build penalty block."""
|
|
168
|
+
if not self.current_statute:
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
content = []
|
|
172
|
+
|
|
173
|
+
# Handle different penalty structures
|
|
174
|
+
if hasattr(penalty, "imprisonment"):
|
|
175
|
+
imp = penalty.imprisonment
|
|
176
|
+
if imp:
|
|
177
|
+
content.append(f"imprisonment: {imp}")
|
|
178
|
+
|
|
179
|
+
if hasattr(penalty, "fine"):
|
|
180
|
+
fine = penalty.fine
|
|
181
|
+
if fine:
|
|
182
|
+
content.append(f"fine: {fine}")
|
|
183
|
+
|
|
184
|
+
if hasattr(penalty, "supplementary"):
|
|
185
|
+
supp = penalty.supplementary
|
|
186
|
+
if supp:
|
|
187
|
+
content.append(f"supplementary: {supp}")
|
|
188
|
+
|
|
189
|
+
# If penalty has clauses
|
|
190
|
+
if hasattr(penalty, "clauses"):
|
|
191
|
+
for clause in penalty.clauses or []:
|
|
192
|
+
clause_type = getattr(clause, "type", "") or getattr(clause, "penalty_type", "") or "?"
|
|
193
|
+
value = getattr(clause, "value", "") or ""
|
|
194
|
+
content.append(f"{clause_type}: {value}")
|
|
195
|
+
|
|
196
|
+
if content:
|
|
197
|
+
block = Block(
|
|
198
|
+
block_type="PENALTY",
|
|
199
|
+
name="penalty",
|
|
200
|
+
content=content,
|
|
201
|
+
children=[],
|
|
202
|
+
level=1,
|
|
203
|
+
)
|
|
204
|
+
self.current_statute.children.append(block)
|
|
205
|
+
|
|
206
|
+
def _build_illustrations_block(self, illustrations: List[Any]) -> None:
|
|
207
|
+
"""Build illustrations block."""
|
|
208
|
+
if not self.current_statute:
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
content = []
|
|
212
|
+
for illus in illustrations:
|
|
213
|
+
label = getattr(illus, "label", "") or "?"
|
|
214
|
+
text = getattr(illus, "text", "") or getattr(illus, "description", "") or ""
|
|
215
|
+
if len(text) > 60:
|
|
216
|
+
text = text[:57] + "..."
|
|
217
|
+
content.append(f"{label} {text}")
|
|
218
|
+
|
|
219
|
+
if content:
|
|
220
|
+
block = Block(
|
|
221
|
+
block_type="ILLUSTRATIONS",
|
|
222
|
+
name="examples",
|
|
223
|
+
content=content,
|
|
224
|
+
children=[],
|
|
225
|
+
level=1,
|
|
226
|
+
)
|
|
227
|
+
self.current_statute.children.append(block)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def format_blocks(blocks: List[Block], style: str = "box") -> str:
|
|
231
|
+
"""
|
|
232
|
+
Format blocks into text output.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
blocks: List of Block objects
|
|
236
|
+
style: "box" for box drawing, "indent" for simple indentation
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Formatted string
|
|
240
|
+
"""
|
|
241
|
+
lines: List[str] = []
|
|
242
|
+
|
|
243
|
+
def render_block_box(block: Block, indent: int = 0) -> None:
|
|
244
|
+
"""Render a block with box drawing characters."""
|
|
245
|
+
prefix = " " * indent
|
|
246
|
+
width = 60 - (indent * 2)
|
|
247
|
+
|
|
248
|
+
# Top border
|
|
249
|
+
lines.append(f"{prefix}┌{'─' * (width - 2)}┐")
|
|
250
|
+
|
|
251
|
+
# Header
|
|
252
|
+
header = f" [{block.block_type}] {block.name} "
|
|
253
|
+
padding = width - 4 - len(header)
|
|
254
|
+
lines.append(f"{prefix}│{header}{'─' * max(0, padding)}│")
|
|
255
|
+
|
|
256
|
+
# Separator
|
|
257
|
+
lines.append(f"{prefix}├{'─' * (width - 2)}┤")
|
|
258
|
+
|
|
259
|
+
# Content
|
|
260
|
+
for line in block.content:
|
|
261
|
+
# Wrap long lines
|
|
262
|
+
if len(line) > width - 4:
|
|
263
|
+
line = line[:width - 7] + "..."
|
|
264
|
+
padding = width - 4 - len(line)
|
|
265
|
+
lines.append(f"{prefix}│ {line}{' ' * max(0, padding)} │")
|
|
266
|
+
|
|
267
|
+
if not block.content and not block.children:
|
|
268
|
+
lines.append(f"{prefix}│{' ' * (width - 2)}│")
|
|
269
|
+
|
|
270
|
+
# Children
|
|
271
|
+
if block.children:
|
|
272
|
+
if block.content:
|
|
273
|
+
lines.append(f"{prefix}├{'─' * (width - 2)}┤")
|
|
274
|
+
for child in block.children:
|
|
275
|
+
render_block_box(child, indent + 1)
|
|
276
|
+
|
|
277
|
+
# Bottom border
|
|
278
|
+
lines.append(f"{prefix}└{'─' * (width - 2)}┘")
|
|
279
|
+
|
|
280
|
+
def render_block_indent(block: Block, indent: int = 0) -> None:
|
|
281
|
+
"""Render a block with simple indentation."""
|
|
282
|
+
prefix = " " * indent
|
|
283
|
+
|
|
284
|
+
lines.append(f"{prefix}[{block.block_type}] {block.name}")
|
|
285
|
+
|
|
286
|
+
for line in block.content:
|
|
287
|
+
lines.append(f"{prefix} | {line}")
|
|
288
|
+
|
|
289
|
+
for child in block.children:
|
|
290
|
+
render_block_indent(child, indent + 1)
|
|
291
|
+
|
|
292
|
+
# Choose renderer
|
|
293
|
+
renderer = render_block_box if style == "box" else render_block_indent
|
|
294
|
+
|
|
295
|
+
for block in blocks:
|
|
296
|
+
renderer(block)
|
|
297
|
+
lines.append("")
|
|
298
|
+
|
|
299
|
+
return "\n".join(lines)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class BlocksTranspiler(TranspilerBase):
|
|
303
|
+
"""Transpiler that outputs block-notation format."""
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def target(self) -> TranspileTarget:
|
|
307
|
+
return TranspileTarget.BLOCKS
|
|
308
|
+
|
|
309
|
+
def transpile(self, ast: ModuleNode) -> str:
|
|
310
|
+
"""
|
|
311
|
+
Transpile AST to block-notation format.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
ast: The root ModuleNode
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Block-notation string
|
|
318
|
+
"""
|
|
319
|
+
builder = BlockBuilder()
|
|
320
|
+
builder.visit(ast)
|
|
321
|
+
|
|
322
|
+
# Header
|
|
323
|
+
lines = [
|
|
324
|
+
"╔════════════════════════════════════════════════════════════╗",
|
|
325
|
+
"║ BLOCK NOTATION VIEW ║",
|
|
326
|
+
"╚════════════════════════════════════════════════════════════╝",
|
|
327
|
+
"",
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
# Render blocks
|
|
331
|
+
output = format_blocks(builder.blocks, style="box")
|
|
332
|
+
lines.append(output)
|
|
333
|
+
|
|
334
|
+
# Footer
|
|
335
|
+
lines.append("")
|
|
336
|
+
lines.append(f"Total statutes: {len(builder.blocks)}")
|
|
337
|
+
|
|
338
|
+
return "\n".join(lines)
|