compiler.py
Rama main · réplica de lectura del repositorio original.
"""
Madaros compiler bridge.
Auto-detects environment:
- If MADAROS_BIN exists locally → runs madaros directly (remote workspace)
- Otherwise → tunnels via SSH to sounio-workspace-control (local Mac)
"""
import asyncio
import os
import subprocess
import threading
import time
from dataclasses import dataclass, field
_DEFAULT_BIN = "/workspace/sounio/bin/madaros"
MADAROS_BIN = (os.environ.get("SOUNIO_MADAROS_BIN") or
os.environ.get("MADAROS_RAW_BIN") or
_DEFAULT_BIN)
SSH_HOST = "sounio-workspace-control"
SSH_SOCKET = os.path.expanduser("~/.ssh/sockets/sounio-workspace-control.sock")
# True when running on a host that has the madaros binary locally
_LOCAL = os.path.isfile(MADAROS_BIN)
@dataclass
class CheckResult:
ok: bool
time_ms: float
raw: str
errors: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
def _parse_output(raw: str, exit_code: int) -> tuple[bool, list[str], list[str]]:
errors, warnings = [], []
for line in raw.splitlines():
l = line.strip()
if not l:
continue
low = l.lower()
if "error" in low or "failed" in low or "unexpected" in low:
errors.append(l)
elif "warning" in low or "warn" in low:
warnings.append(l)
ok = exit_code == 0 and "check: OK" in raw
return ok, errors, warnings
def _tmp_path() -> str:
tid = threading.get_ident() % 99991
return f"/tmp/mc_{os.getpid()}_{tid}.sio"
class MadarosCompiler:
def __init__(self, madaros_bin: str = MADAROS_BIN,
ssh_host: str = SSH_HOST, timeout: float = 15.0):
self.madaros_bin = madaros_bin
self.ssh_host = ssh_host
self.timeout = timeout
def check(self, code: str) -> CheckResult:
t0 = time.monotonic()
if _LOCAL:
return self._check_local(code, t0)
return self._check_ssh(code, t0)
def _check_local(self, code: str, t0: float) -> CheckResult:
path = _tmp_path()
try:
with open(path, "w") as f:
f.write(code)
proc = subprocess.run(
[self.madaros_bin, "check", path],
capture_output=True, timeout=self.timeout,
)
elapsed = (time.monotonic() - t0) * 1000
except subprocess.TimeoutExpired:
return CheckResult(ok=False, time_ms=self.timeout * 1000,
raw="timeout", errors=["Timeout"])
except Exception as e:
return CheckResult(ok=False, time_ms=0, raw=str(e), errors=[str(e)])
finally:
try:
os.unlink(path)
except OSError:
pass
raw = proc.stdout.decode() + proc.stderr.decode()
ok, errors, warnings = _parse_output(raw, proc.returncode)
return CheckResult(ok=ok, time_ms=elapsed, raw=raw,
errors=errors, warnings=warnings)
def _check_ssh(self, code: str, t0: float) -> CheckResult:
import shlex
path = _tmp_path()
inline = shlex.quote(code)
cmd_str = (f"printf %s {inline} > {path} && "
f"{self.madaros_bin} check {path}; rm -f {path}")
ssh_cmd = ["ssh", "-S", SSH_SOCKET, self.ssh_host, cmd_str]
try:
proc = subprocess.run(ssh_cmd, capture_output=True, timeout=self.timeout)
elapsed = (time.monotonic() - t0) * 1000
except subprocess.TimeoutExpired:
return CheckResult(ok=False, time_ms=self.timeout * 1000,
raw="timeout", errors=["Timeout"])
except Exception as e:
return CheckResult(ok=False, time_ms=0, raw=str(e), errors=[str(e)])
raw = proc.stdout.decode() + proc.stderr.decode()
ok, errors, warnings = _parse_output(raw, proc.returncode)
return CheckResult(ok=ok, time_ms=elapsed, raw=raw,
errors=errors, warnings=warnings)
class AsyncMadarosCompiler:
def __init__(self, madaros_bin: str = MADAROS_BIN,
ssh_host: str = SSH_HOST,
max_concurrent: int = 20, timeout: float = 15.0):
# Local: 20 concurrent fine (no SSH overhead)
# Remote via SSH: cap at 6 to avoid ControlMaster overload
concurrency = max_concurrent if _LOCAL else min(max_concurrent, 6)
self.semaphore = asyncio.Semaphore(concurrency)
self.madaros_bin = madaros_bin
self.ssh_host = ssh_host
self.timeout = timeout
async def check(self, code: str) -> CheckResult:
async with self.semaphore:
loop = asyncio.get_event_loop()
c = MadarosCompiler(self.madaros_bin, self.ssh_host, self.timeout)
return await loop.run_in_executor(None, c.check, code)
async def check_batch(self, codes: list[str]) -> list[CheckResult]:
return await asyncio.gather(*[self.check(c) for c in codes])
def smoke_test():
mode = "LOCAL" if _LOCAL else f"SSH→{SSH_HOST}"
print(f"mode: {mode}")
c = MadarosCompiler()
for name, code in [
("valid", "fn main() -> i32 { return 42 }"),
("invalid", "fn broken { ??? }"),
]:
r = c.check(code)
print(f"[{name}] ok={r.ok} time={r.time_ms:.0f}ms")
for e in r.errors:
print(f" ERR: {e}")
if __name__ == "__main__":
smoke_test()