Files
hakorune/src/runner/modes/common_util/selfhost_exe.rs
nyash-codex c479e5f527 fix(dx): Quick Win 1-3 for better error messages and API simplification
Quick Win 1: Show available boxes on "Unknown Box type" error
- vm.rs, vm_fallback.rs: Display sorted list of available user-defined boxes
- Before: "Unknown Box type: Foo"
- After:  "Unknown Box type: Foo. Available: Bar, Baz, Main"

Quick Win 2: Show stderr on child process timeout
- child.rs, selfhost_exe.rs: Capture and display stderr (up to 500 chars)
- Helps diagnose what went wrong in selfhost compiler child process

Quick Win 3: Simplify Stage-B compiler API (SSOT)
- compiler_stageb.hako: Add StageBDriverBox.compile() as single entry point
- compiler_stageb.hako: Remove StageBMain compatibility wrapper
- compiler.hako: Change from `using ... as StageBMain` to direct import
- compiler.hako: Call StageBDriverBox.compile() directly

Also includes child_env.rs NYASH_MODULES env var for module mapping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 08:44:31 +09:00

140 lines
4.4 KiB
Rust

use std::io::Read;
use std::process::Stdio;
use std::thread::sleep;
use std::time::{Duration, Instant};
/// Try external selfhost compiler EXE to parse Ny -> JSON v0 and return MIR module.
/// Returns Some(module) on success, None on failure (timeout/invalid output/missing exe)
pub fn exe_try_parse_json_v0(filename: &str, timeout_ms: u64) -> Option<crate::mir::MirModule> {
// Resolve parser EXE path
let exe_path = if let Ok(p) = std::env::var("NYASH_NY_COMPILER_EXE_PATH") {
std::path::PathBuf::from(p)
} else {
let mut p = std::path::PathBuf::from("dist/nyash_compiler");
#[cfg(windows)]
{
p.push("nyash_compiler.exe");
}
#[cfg(not(windows))]
{
p.push("nyash_compiler");
}
if !p.exists() {
if let Ok(w) = which::which("nyash_compiler") {
w
} else {
p
}
} else {
p
}
};
if !exe_path.exists() {
crate::cli_v!("[ny-compiler] exe not found at {}", exe_path.display());
return None;
}
// Build command
let mut cmd = std::process::Command::new(&exe_path);
cmd.arg(filename);
if crate::config::env::ny_compiler_min_json() {
cmd.arg("--min-json");
}
if crate::config::env::selfhost_read_tmp() {
cmd.arg("--read-tmp");
}
if let Some(raw) = crate::config::env::ny_compiler_child_args() {
for tok in raw.split_whitespace() {
cmd.arg(tok);
}
}
let cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
eprintln!("[ny-compiler] exe spawn failed: {}", e);
return None;
}
};
let ch_stdout = child.stdout.take();
let ch_stderr = child.stderr.take();
let start = Instant::now();
let mut timed_out = false;
loop {
match child.try_wait() {
Ok(Some(_)) => break,
Ok(None) => {
if start.elapsed() >= Duration::from_millis(timeout_ms) {
let _ = child.kill();
let _ = child.wait();
timed_out = true;
break;
}
sleep(Duration::from_millis(10));
}
Err(e) => {
eprintln!("[ny-compiler] exe wait error: {}", e);
return None;
}
}
}
let mut out_buf = Vec::new();
let mut err_buf = Vec::new();
if let Some(mut s) = ch_stdout {
let _ = s.read_to_end(&mut out_buf);
}
if let Some(mut s) = ch_stderr {
let _ = s.read_to_end(&mut err_buf);
}
if timed_out {
let head = String::from_utf8_lossy(&out_buf)
.chars()
.take(200)
.collect::<String>();
// Quick Win 2: Show stderr for easier debugging
let err_head = String::from_utf8_lossy(&err_buf)
.chars()
.take(500)
.collect::<String>();
eprintln!(
"[ny-compiler] exe timeout after {} ms; stdout(head)='{}'",
timeout_ms,
head.replace('\n', "\\n")
);
if !err_head.is_empty() {
eprintln!(
"[ny-compiler] stderr(head)='{}'",
err_head.replace('\n', "\\n")
);
}
return None;
}
let stdout = match String::from_utf8(out_buf) {
Ok(s) => s,
Err(_) => String::new(),
};
let json_line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout)
.unwrap_or_default();
if json_line.is_empty() {
if crate::config::env::cli_verbose() {
let head: String = stdout.chars().take(200).collect();
let errh: String = String::from_utf8_lossy(&err_buf)
.chars()
.take(200)
.collect();
crate::cli_v!(
"[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'",
head.replace('\n', "\\n"),
errh.replace('\n', "\\n")
);
}
return None;
}
match crate::runner::json_v0_bridge::parse_json_v0_to_module(&json_line) {
Ok(module) => Some(module),
Err(e) => {
eprintln!("[ny-compiler] JSON parse failed: {}", e);
None
}
}
}