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,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REPL command - interactive statute experimentation.
|
|
3
|
+
|
|
4
|
+
Provides an interactive Read-Eval-Print Loop for:
|
|
5
|
+
- Parsing and validating Yuho code snippets
|
|
6
|
+
- Transpiling to various targets
|
|
7
|
+
- Exploring statute definitions
|
|
8
|
+
- Testing legal logic
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import readline
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional, List, Dict, Any
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from yuho.parser import Parser
|
|
20
|
+
from yuho.ast import ASTBuilder
|
|
21
|
+
from yuho.ast.nodes import ModuleNode
|
|
22
|
+
from yuho.transpile.base import TranspileTarget
|
|
23
|
+
from yuho.transpile.registry import TranspilerRegistry
|
|
24
|
+
from yuho.cli.error_formatter import format_errors, Colors, colorize
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class YuhoREPL:
|
|
28
|
+
"""
|
|
29
|
+
Interactive REPL for Yuho statute language.
|
|
30
|
+
|
|
31
|
+
Supports:
|
|
32
|
+
- Multi-line input with continuation
|
|
33
|
+
- Command history (via readline)
|
|
34
|
+
- Transpilation to any target
|
|
35
|
+
- AST inspection
|
|
36
|
+
- Statute exploration
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
PROMPT = "yuho> "
|
|
40
|
+
CONTINUATION_PROMPT = " ... "
|
|
41
|
+
|
|
42
|
+
COMMANDS = {
|
|
43
|
+
"help": "Show this help message",
|
|
44
|
+
"exit": "Exit the REPL (also: quit, Ctrl+D)",
|
|
45
|
+
"clear": "Clear the screen",
|
|
46
|
+
"history": "Show command history",
|
|
47
|
+
"load <file>": "Load and parse a .yh file",
|
|
48
|
+
"transpile <target>": "Transpile last valid input (json, english, mermaid, latex, graphql)",
|
|
49
|
+
"ast": "Show AST of last valid input",
|
|
50
|
+
"targets": "List available transpile targets",
|
|
51
|
+
"reset": "Clear session state",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, color: bool = True, verbose: bool = False):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the REPL.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
color: Whether to use colored output
|
|
60
|
+
verbose: Enable verbose mode
|
|
61
|
+
"""
|
|
62
|
+
self.color = color
|
|
63
|
+
self.verbose = verbose
|
|
64
|
+
self.parser = Parser()
|
|
65
|
+
self.registry = TranspilerRegistry.instance()
|
|
66
|
+
|
|
67
|
+
# Session state
|
|
68
|
+
self.history: List[str] = []
|
|
69
|
+
self.last_ast: Optional[ModuleNode] = None
|
|
70
|
+
self.last_source: str = ""
|
|
71
|
+
self.session_statutes: Dict[str, Any] = {}
|
|
72
|
+
|
|
73
|
+
# Setup readline history
|
|
74
|
+
self._setup_readline()
|
|
75
|
+
|
|
76
|
+
def _setup_readline(self) -> None:
|
|
77
|
+
"""Configure readline for history and completion."""
|
|
78
|
+
# History file
|
|
79
|
+
history_file = Path.home() / ".yuho_history"
|
|
80
|
+
try:
|
|
81
|
+
readline.read_history_file(history_file)
|
|
82
|
+
except FileNotFoundError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
# Save history on exit
|
|
86
|
+
import atexit
|
|
87
|
+
atexit.register(lambda: readline.write_history_file(history_file))
|
|
88
|
+
|
|
89
|
+
# Set history length
|
|
90
|
+
readline.set_history_length(1000)
|
|
91
|
+
|
|
92
|
+
def _colorize(self, text: str, color: str) -> str:
|
|
93
|
+
"""Apply color if enabled."""
|
|
94
|
+
if not self.color:
|
|
95
|
+
return text
|
|
96
|
+
return colorize(text, color)
|
|
97
|
+
|
|
98
|
+
def _print_banner(self) -> None:
|
|
99
|
+
"""Print welcome banner."""
|
|
100
|
+
banner = f"""
|
|
101
|
+
{self._colorize("Yuho REPL", Colors.CYAN)} - Interactive statute experimentation
|
|
102
|
+
Type {self._colorize("help", Colors.YELLOW)} for commands, {self._colorize("exit", Colors.YELLOW)} to quit
|
|
103
|
+
"""
|
|
104
|
+
print(banner.strip())
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
def _print_help(self) -> None:
|
|
108
|
+
"""Print help message."""
|
|
109
|
+
print(f"\n{self._colorize('Commands:', Colors.CYAN)}")
|
|
110
|
+
for cmd, desc in self.COMMANDS.items():
|
|
111
|
+
print(f" {self._colorize(cmd, Colors.YELLOW):20s} {desc}")
|
|
112
|
+
|
|
113
|
+
print(f"\n{self._colorize('Yuho Syntax Examples:', Colors.CYAN)}")
|
|
114
|
+
examples = [
|
|
115
|
+
'statute "299" "Culpable Homicide" { ... }',
|
|
116
|
+
'struct Person { name: string, age: int }',
|
|
117
|
+
'fn is_adult(age: int) -> bool { return age >= 18; }',
|
|
118
|
+
]
|
|
119
|
+
for ex in examples:
|
|
120
|
+
print(f" {ex}")
|
|
121
|
+
|
|
122
|
+
print(f"\n{self._colorize('Multi-line Input:', Colors.CYAN)}")
|
|
123
|
+
print(" End a line with \\ to continue on the next line")
|
|
124
|
+
print(" Or use matching braces { } for blocks")
|
|
125
|
+
print()
|
|
126
|
+
|
|
127
|
+
def _print_targets(self) -> None:
|
|
128
|
+
"""Print available transpile targets."""
|
|
129
|
+
print(f"\n{self._colorize('Available Targets:', Colors.CYAN)}")
|
|
130
|
+
for target in TranspileTarget:
|
|
131
|
+
name = target.name.lower()
|
|
132
|
+
ext = target.file_extension
|
|
133
|
+
print(f" {self._colorize(name, Colors.YELLOW):12s} -> {ext}")
|
|
134
|
+
print()
|
|
135
|
+
|
|
136
|
+
def _read_input(self) -> Optional[str]:
|
|
137
|
+
"""
|
|
138
|
+
Read input, handling multi-line continuation.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Complete input string, or None on EOF
|
|
142
|
+
"""
|
|
143
|
+
lines: List[str] = []
|
|
144
|
+
prompt = self.PROMPT
|
|
145
|
+
brace_depth = 0
|
|
146
|
+
|
|
147
|
+
while True:
|
|
148
|
+
try:
|
|
149
|
+
line = input(self._colorize(prompt, Colors.GREEN))
|
|
150
|
+
except EOFError:
|
|
151
|
+
if lines:
|
|
152
|
+
print() # Newline after incomplete input
|
|
153
|
+
return None
|
|
154
|
+
except KeyboardInterrupt:
|
|
155
|
+
print(f"\n{self._colorize('(Use exit to quit)', Colors.YELLOW)}")
|
|
156
|
+
return ""
|
|
157
|
+
|
|
158
|
+
# Track brace depth for multi-line blocks
|
|
159
|
+
brace_depth += line.count("{") - line.count("}")
|
|
160
|
+
|
|
161
|
+
# Check for line continuation
|
|
162
|
+
if line.endswith("\\"):
|
|
163
|
+
lines.append(line[:-1])
|
|
164
|
+
prompt = self.CONTINUATION_PROMPT
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
lines.append(line)
|
|
168
|
+
|
|
169
|
+
# Continue if braces are unbalanced
|
|
170
|
+
if brace_depth > 0:
|
|
171
|
+
prompt = self.CONTINUATION_PROMPT
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
return "\n".join(lines)
|
|
177
|
+
|
|
178
|
+
def _handle_command(self, cmd: str) -> bool:
|
|
179
|
+
"""
|
|
180
|
+
Handle a REPL command.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
cmd: The command string (without leading colon)
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if command was handled, False otherwise
|
|
187
|
+
"""
|
|
188
|
+
parts = cmd.strip().split(maxsplit=1)
|
|
189
|
+
command = parts[0].lower()
|
|
190
|
+
arg = parts[1] if len(parts) > 1 else ""
|
|
191
|
+
|
|
192
|
+
if command in ("exit", "quit"):
|
|
193
|
+
print("Goodbye!")
|
|
194
|
+
return True # Signal exit
|
|
195
|
+
|
|
196
|
+
if command == "help":
|
|
197
|
+
self._print_help()
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
if command == "clear":
|
|
201
|
+
os.system("clear" if os.name == "posix" else "cls")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
if command == "history":
|
|
205
|
+
if not self.history:
|
|
206
|
+
print(self._colorize("No history yet", Colors.YELLOW))
|
|
207
|
+
else:
|
|
208
|
+
print(f"\n{self._colorize('History:', Colors.CYAN)}")
|
|
209
|
+
for i, h in enumerate(self.history[-20:], 1):
|
|
210
|
+
preview = h[:60] + "..." if len(h) > 60 else h
|
|
211
|
+
print(f" {i:2d}: {preview}")
|
|
212
|
+
print()
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
if command == "targets":
|
|
216
|
+
self._print_targets()
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
if command == "reset":
|
|
220
|
+
self.last_ast = None
|
|
221
|
+
self.last_source = ""
|
|
222
|
+
self.session_statutes.clear()
|
|
223
|
+
print(self._colorize("Session state cleared", Colors.GREEN))
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
if command == "ast":
|
|
227
|
+
if not self.last_ast:
|
|
228
|
+
print(self._colorize("No valid AST available. Parse some code first.", Colors.YELLOW))
|
|
229
|
+
else:
|
|
230
|
+
self._print_ast(self.last_ast)
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
if command == "load":
|
|
234
|
+
if not arg:
|
|
235
|
+
print(self._colorize("Usage: load <file.yh>", Colors.RED))
|
|
236
|
+
else:
|
|
237
|
+
self._load_file(arg)
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
if command == "transpile":
|
|
241
|
+
if not arg:
|
|
242
|
+
print(self._colorize("Usage: transpile <target>", Colors.RED))
|
|
243
|
+
self._print_targets()
|
|
244
|
+
else:
|
|
245
|
+
self._transpile(arg)
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
# Unknown command - try parsing as code
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
def _load_file(self, filepath: str) -> None:
|
|
252
|
+
"""Load and parse a Yuho file."""
|
|
253
|
+
path = Path(filepath).expanduser()
|
|
254
|
+
|
|
255
|
+
if not path.exists():
|
|
256
|
+
print(self._colorize(f"File not found: {filepath}", Colors.RED))
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
source = path.read_text(encoding="utf-8")
|
|
261
|
+
self._parse_and_validate(source, str(path))
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(self._colorize(f"Error loading file: {e}", Colors.RED))
|
|
264
|
+
|
|
265
|
+
def _parse_and_validate(self, source: str, filename: str = "<repl>") -> bool:
|
|
266
|
+
"""
|
|
267
|
+
Parse and validate Yuho source code.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
source: The source code
|
|
271
|
+
filename: Filename for error messages
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if parsing succeeded
|
|
275
|
+
"""
|
|
276
|
+
result = self.parser.parse_string(source, filename)
|
|
277
|
+
|
|
278
|
+
if result.errors:
|
|
279
|
+
print(self._colorize("Parse errors:", Colors.RED))
|
|
280
|
+
for err in result.errors[:5]: # Limit to 5 errors
|
|
281
|
+
loc = f"{err.location.line}:{err.location.col}" if err.location else "?"
|
|
282
|
+
print(f" [{loc}] {err.message}")
|
|
283
|
+
if len(result.errors) > 5:
|
|
284
|
+
print(f" ... and {len(result.errors) - 5} more errors")
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
# Build AST
|
|
288
|
+
try:
|
|
289
|
+
builder = ASTBuilder()
|
|
290
|
+
ast = builder.build(result.tree)
|
|
291
|
+
|
|
292
|
+
self.last_ast = ast
|
|
293
|
+
self.last_source = source
|
|
294
|
+
|
|
295
|
+
# Track statutes in session
|
|
296
|
+
for statute in ast.statutes:
|
|
297
|
+
self.session_statutes[statute.section_number] = statute
|
|
298
|
+
|
|
299
|
+
# Success message
|
|
300
|
+
stats = []
|
|
301
|
+
if ast.statutes:
|
|
302
|
+
stats.append(f"{len(ast.statutes)} statute(s)")
|
|
303
|
+
if ast.type_defs:
|
|
304
|
+
stats.append(f"{len(ast.type_defs)} type(s)")
|
|
305
|
+
if ast.function_defs:
|
|
306
|
+
stats.append(f"{len(ast.function_defs)} function(s)")
|
|
307
|
+
|
|
308
|
+
if stats:
|
|
309
|
+
print(self._colorize(f"✓ Parsed: {', '.join(stats)}", Colors.GREEN))
|
|
310
|
+
else:
|
|
311
|
+
print(self._colorize("✓ Valid (empty module)", Colors.GREEN))
|
|
312
|
+
|
|
313
|
+
return True
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
print(self._colorize(f"AST build error: {e}", Colors.RED))
|
|
317
|
+
if self.verbose:
|
|
318
|
+
import traceback
|
|
319
|
+
traceback.print_exc()
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
def _transpile(self, target_name: str) -> None:
|
|
323
|
+
"""Transpile last valid AST to a target format."""
|
|
324
|
+
if not self.last_ast:
|
|
325
|
+
print(self._colorize("No valid AST available. Parse some code first.", Colors.YELLOW))
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
target = TranspileTarget.from_string(target_name)
|
|
330
|
+
except ValueError:
|
|
331
|
+
print(self._colorize(f"Unknown target: {target_name}", Colors.RED))
|
|
332
|
+
self._print_targets()
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
transpiler = self.registry.get(target)
|
|
337
|
+
output = transpiler.transpile(self.last_ast)
|
|
338
|
+
|
|
339
|
+
print(f"\n{self._colorize(f'=== {target.name} Output ===', Colors.CYAN)}")
|
|
340
|
+
print(output)
|
|
341
|
+
print()
|
|
342
|
+
|
|
343
|
+
except Exception as e:
|
|
344
|
+
print(self._colorize(f"Transpilation error: {e}", Colors.RED))
|
|
345
|
+
if self.verbose:
|
|
346
|
+
import traceback
|
|
347
|
+
traceback.print_exc()
|
|
348
|
+
|
|
349
|
+
def _print_ast(self, ast: ModuleNode) -> None:
|
|
350
|
+
"""Print a simple representation of the AST."""
|
|
351
|
+
print(f"\n{self._colorize('=== AST ===', Colors.CYAN)}")
|
|
352
|
+
|
|
353
|
+
if ast.imports:
|
|
354
|
+
print(f"Imports: {len(ast.imports)}")
|
|
355
|
+
for imp in ast.imports:
|
|
356
|
+
print(f" - {imp.path}")
|
|
357
|
+
|
|
358
|
+
if ast.type_defs:
|
|
359
|
+
print(f"Types: {len(ast.type_defs)}")
|
|
360
|
+
for td in ast.type_defs:
|
|
361
|
+
fields = ", ".join(f.name for f in td.fields)
|
|
362
|
+
print(f" - struct {td.name} {{ {fields} }}")
|
|
363
|
+
|
|
364
|
+
if ast.function_defs:
|
|
365
|
+
print(f"Functions: {len(ast.function_defs)}")
|
|
366
|
+
for fn in ast.function_defs:
|
|
367
|
+
params = ", ".join(p.name for p in fn.params)
|
|
368
|
+
print(f" - fn {fn.name}({params})")
|
|
369
|
+
|
|
370
|
+
if ast.statutes:
|
|
371
|
+
print(f"Statutes: {len(ast.statutes)}")
|
|
372
|
+
for st in ast.statutes:
|
|
373
|
+
title = st.title.value if st.title else "(untitled)"
|
|
374
|
+
print(f" - Section {st.section_number}: {title}")
|
|
375
|
+
if st.elements:
|
|
376
|
+
print(f" Elements: {len(st.elements)}")
|
|
377
|
+
if st.penalty:
|
|
378
|
+
print(f" Has penalty")
|
|
379
|
+
if st.illustrations:
|
|
380
|
+
print(f" Illustrations: {len(st.illustrations)}")
|
|
381
|
+
|
|
382
|
+
print()
|
|
383
|
+
|
|
384
|
+
def run(self) -> int:
|
|
385
|
+
"""
|
|
386
|
+
Run the REPL loop.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Exit code (0 for success)
|
|
390
|
+
"""
|
|
391
|
+
self._print_banner()
|
|
392
|
+
|
|
393
|
+
while True:
|
|
394
|
+
try:
|
|
395
|
+
source = self._read_input()
|
|
396
|
+
|
|
397
|
+
if source is None:
|
|
398
|
+
# EOF
|
|
399
|
+
print("\nGoodbye!")
|
|
400
|
+
break
|
|
401
|
+
|
|
402
|
+
if not source.strip():
|
|
403
|
+
continue
|
|
404
|
+
|
|
405
|
+
# Add to history
|
|
406
|
+
self.history.append(source)
|
|
407
|
+
|
|
408
|
+
# Check if it's a command
|
|
409
|
+
if source.strip() in self.COMMANDS or source.split()[0] in [
|
|
410
|
+
"help", "exit", "quit", "clear", "history", "load",
|
|
411
|
+
"transpile", "ast", "targets", "reset"
|
|
412
|
+
]:
|
|
413
|
+
if self._handle_command(source):
|
|
414
|
+
break # Exit command
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
# Try to parse as Yuho code
|
|
418
|
+
self._parse_and_validate(source)
|
|
419
|
+
|
|
420
|
+
except KeyboardInterrupt:
|
|
421
|
+
print(f"\n{self._colorize('(Use exit to quit)', Colors.YELLOW)}")
|
|
422
|
+
continue
|
|
423
|
+
except Exception as e:
|
|
424
|
+
print(self._colorize(f"Error: {e}", Colors.RED))
|
|
425
|
+
if self.verbose:
|
|
426
|
+
import traceback
|
|
427
|
+
traceback.print_exc()
|
|
428
|
+
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def run_repl(color: bool = True, verbose: bool = False) -> int:
|
|
433
|
+
"""
|
|
434
|
+
Entry point for the REPL command.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
color: Whether to use colored output
|
|
438
|
+
verbose: Enable verbose mode
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
Exit code
|
|
442
|
+
"""
|
|
443
|
+
repl = YuhoREPL(color=color, verbose=verbose)
|
|
444
|
+
return repl.run()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serve command - start MCP server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from yuho.cli.error_formatter import Colors, colorize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_serve(port: int = 8080, host: str = "127.0.0.1", stdio: bool = False, verbose: bool = False) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Start the MCP (Model Context Protocol) server.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
port: Port to listen on
|
|
19
|
+
host: Host to bind to
|
|
20
|
+
stdio: Use stdio transport
|
|
21
|
+
verbose: Enable verbose output
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
from yuho.mcp import create_server
|
|
25
|
+
except ImportError:
|
|
26
|
+
click.echo(colorize("error: MCP module not available. Install with: pip install yuho[mcp]", Colors.RED), err=True)
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
|
|
29
|
+
server = create_server()
|
|
30
|
+
|
|
31
|
+
if stdio:
|
|
32
|
+
if verbose:
|
|
33
|
+
click.echo("Starting MCP server on stdio...")
|
|
34
|
+
server.run_stdio()
|
|
35
|
+
else:
|
|
36
|
+
if verbose:
|
|
37
|
+
click.echo(f"Starting MCP server on {host}:{port}...")
|
|
38
|
+
click.echo(colorize(f"Yuho MCP server listening on http://{host}:{port}", Colors.CYAN + Colors.BOLD))
|
|
39
|
+
click.echo(colorize("Press Ctrl+C to stop", Colors.DIM))
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
server.run_http(host=host, port=port)
|
|
43
|
+
except KeyboardInterrupt:
|
|
44
|
+
click.echo("\nShutting down...")
|