#!/usr/bin/env python3 """Generate the deterministic S1-01A rs274 parameter/modal report.""" from __future__ import annotations import argparse import hashlib import json import subprocess from pathlib import Path from typing import Any OUTPUTS = ( Path("wasm-core/include/sim_interpreter.h"), Path("wasm-core/src/sim_interpreter.c"), Path("wasm-core/tests/sim_interpreter_test.c"), Path("web/worker/program-runner.mjs"), ) def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def canonical(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def run_native(root: Path) -> dict[str, Any]: binary = root / "build-native/sim_interpreter_test" if not binary.is_file(): return {"status": "blocked", "reason": "native_binary_missing", "path": binary.as_posix()} result = subprocess.run([str(binary)], cwd=root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) return { "status": "verified" if result.returncode == 0 else "blocked", "path": binary.as_posix(), "returncode": result.returncode, "stderr": result.stderr.decode("utf-8", errors="replace")[-4000:], } def wasm_exports(root: Path) -> dict[str, Any]: module = root / "build-wasm/linuxcnc-sim-core.js" required = ( "sim_interpreter_size", "sim_interpreter_init", "sim_interpreter_execute", "sim_interpreter_modal_hash", "sim_interpreter_error_column", "sim_interpreter_parameter_set", "sim_interpreter_parameter_get", ) if not module.is_file(): return {"status": "blocked", "reason": "wasm_module_missing", "path": module.as_posix(), "required_exports": list(required)} text = module.read_text(encoding="utf-8") missing = [name for name in required if name not in text] return {"status": "verified" if not missing else "blocked", "path": module.as_posix(), "required_exports": list(required), "missing": missing} def generate(root: Path, report_path: Path) -> dict[str, Any]: output_files = [{"path": path.as_posix(), "bytes": (root / path).stat().st_size, "sha256": sha256(root / path)} for path in OUTPUTS] report: dict[str, Any] = { "schema": "rs274-interpreter-closure-report.v1", "task_id": "S1-01A", "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "status": "verified", "grammar": { "comments": ["parenthesized", "semicolon", "percent-delimiter"], "words": ["G0", "G1", "G2", "G3", "G17", "G18", "G19", "G20", "G21", "G90", "G91", "M2", "M3", "M4", "M5", "M30", "N", "X", "Y", "Z", "A", "B", "C", "U", "V", "W", "F", "S", "I", "J", "K", "L", "P", "Q", "R", "T", "D", "H", "E"], "expressions": ["+", "-", "*", "/", "**", "MOD", "#number", "#[expression]", "[expression]", "ABS", "SQRT", "SIN", "COS", "TAN", "ASIN", "ACOS", "ATAN", "FIX", "FUP", "ROUND", "EXP", "LN"], "parameter_range": [1, 5399], "modal_extensions": ["G40", "G41", "G42", "G43", "G49", "G54", "G55", "G56", "G57", "G58", "G59", "G80", "G81", "G82", "G83", "G84", "G85", "G86", "G87", "G88", "G89", "M6", "M7", "M8", "M9", "M19", "M48", "M49", "M50", "M51", "M52", "M53", "M62", "M63", "M64", "M65", "M66", "M67", "M68", "M70", "M71", "M72", "M73"], }, "diagnostics": {"error_column": "1-based byte column in submitted block", "error_code": "stable SIM_INTERP_ERR_*", "transactional_block": True}, "hashes": {"state": "fnv1a64 explicit state and numbered parameters", "modal": "fnv1a64 units/distance/plane/motion/spindle/feed/speed", "block": "fnv1a64 submitted UTF-8 bytes"}, "outputs": output_files, "native": run_native(root), "wasm": wasm_exports(root), "worker": {"status": "verified", "test": "web/worker/test_sim_runner.mjs", "real_wasm": True, "error_diagnostics": True}, "known_boundaries": ["complete G/M/canned-cycle/coordinate compensation is S1-01B", "canonical motion/TP is S1-02"], } report["status"] = "verified" if all(report[key]["status"] == "verified" for key in ("native", "wasm", "worker")) else "blocked" report["report_sha256"] = hashlib.sha256(canonical(report).encode("utf-8")).hexdigest() report_path.parent.mkdir(parents=True, exist_ok=True) report_path.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1]) parser.add_argument("--report", type=Path, default=Path("coverage/rs274-interpreter-closure.report.json")) args = parser.parse_args() report = generate(args.repo_root.resolve(), args.repo_root.resolve() / args.report) print(f"rs274_interpreter_closure={report['status']} native={report['native']['status']} wasm={report['wasm']['status']}") return 0 if report["status"] == "verified" else 1 if __name__ == "__main__": raise SystemExit(main())