diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index 80e36dc9..7615ae4d 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -160,6 +160,68 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { } impl NyashRunner { + /// Execute a MIR module quietly and return an OS exit code derived from the + /// program's return value (Integer/Bool/Float). Strings and other values map to 0. + /// - Integer: value as i64, truncated to 0..=255 (two's complement wrap) + /// - Bool: true=1, false=0 + /// - Float: floor toward zero (as i64), truncated to 0..=255 + pub(crate) fn execute_mir_module_quiet_exit(&self, module: &crate::mir::MirModule) -> i32 { + use crate::backend::MirInterpreter; + use crate::box_trait::{BoolBox, IntegerBox, StringBox}; + use crate::boxes::FloatBox; + use crate::mir::MirType; + + fn to_rc(val: i64) -> i32 { + let m = ((val % 256) + 256) % 256; // normalize into 0..=255 + m as i32 + } + + let mut interp = MirInterpreter::new(); + match interp.execute_module(module) { + Ok(result) => { + if let Some(func) = module.functions.get("main") { + match &func.signature.return_type { + MirType::Integer => { + if let Some(ib) = result.as_any().downcast_ref::() { + return to_rc(ib.value); + } + } + MirType::Bool => { + if let Some(bb) = result.as_any().downcast_ref::() { + return if bb.value { 1 } else { 0 }; + } + } + MirType::Float => { + if let Some(fb) = result.as_any().downcast_ref::() { + return to_rc(fb.value as i64); + } + if let Some(ib) = result.as_any().downcast_ref::() { + return to_rc(ib.value); + } + } + _ => {} + } + // Fallbacks by inspecting boxed value kinds (robust to imprecise return types) + if let Some(ib) = result.as_any().downcast_ref::() { + return to_rc(ib.value); + } + if let Some(bb) = result.as_any().downcast_ref::() { + return if bb.value { 1 } else { 0 }; + } + if let Some(fb) = result.as_any().downcast_ref::() { + return to_rc(fb.value as i64); + } + if let Some(_sb) = result.as_any().downcast_ref::() { + return 0; // strings do not define rc semantics yet + } + 0 + } else { + 0 + } + } + Err(_) => 1, + } + } pub(crate) fn execute_mir_module(&self, module: &crate::mir::MirModule) { // If CLI requested MIR JSON emit, write to file and exit immediately. let groups = self.config.as_groups(); diff --git a/src/runner/pipe_io.rs b/src/runner/pipe_io.rs index e24c5160..63300eea 100644 --- a/src/runner/pipe_io.rs +++ b/src/runner/pipe_io.rs @@ -48,12 +48,12 @@ impl NyashRunner { super::json_v0_bridge::maybe_dump_mir(&module); // Gate‑C(Core) strict OOB fail‑fast: reset observe flag before run if crate::config::env::oob_strict_fail() { crate::runtime::observe::reset(); } - self.execute_mir_module(&module); + let rc = self.execute_mir_module_quiet_exit(&module); if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { eprintln!("[gate-c][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } - return true; + std::process::exit(rc); } Ok(None) => {} Err(e) => { @@ -119,14 +119,14 @@ impl NyashRunner { std::process::exit(1); } } - // Default: Execute via MIR interpreter + // Default: Execute via MIR interpreter (quiet) and exit with rc mirrored from return value if crate::config::env::oob_strict_fail() { crate::runtime::observe::reset(); } - self.execute_mir_module(&module); + let rc = self.execute_mir_module_quiet_exit(&module); if crate::config::env::oob_strict_fail() && crate::runtime::observe::oob_seen() { eprintln!("[gate-c][oob-strict] Out-of-bounds observed → exit(1)"); std::process::exit(1); } - true + std::process::exit(rc); } Err(e) => { eprintln!("❌ JSON v0 bridge error: {}", e); diff --git a/tools/smokes/v2/profiles/quick/core/gate_c_parity_file_vm.sh b/tools/smokes/v2/profiles/quick/core/gate_c_parity_file_vm.sh new file mode 100644 index 00000000..16b8c097 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/gate_c_parity_file_vm.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# gate_c_parity_file_vm.sh — Gate‑C (file) exit-code mirrors return (quick) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)" +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +json_path="/tmp/ny_gatec_parity_$$.json" +cat >"$json_path" <<'JSON' +{"version":0,"kind":"Program","body":[{"type":"Return","expr":{"type":"Int","value":42}}]} +JSON + +set +e +"$ROOT/target/release/nyash" --json-file "$json_path" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$json_path" + +if [ $rc -eq 42 ]; then + echo "[PASS] gate_c_parity_file_vm" + exit 0 +else + echo "[FAIL] gate_c_parity_file_vm: expected rc=42 got $rc" >&2 + exit 1 +fi + diff --git a/tools/smokes/v2/profiles/quick/core/gate_c_parity_pipe_vm.sh b/tools/smokes/v2/profiles/quick/core/gate_c_parity_pipe_vm.sh new file mode 100644 index 00000000..232d2711 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/gate_c_parity_pipe_vm.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# gate_c_parity_pipe_vm.sh — Gate‑C (stdin pipe) exit-code mirrors return (quick) + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)" +source "$ROOT/tools/smokes/v2/lib/test_runner.sh" +require_env || exit 2 + +payload='{"version":0,"kind":"Program","body":[{"type":"Return","expr":{"type":"Int","value":9}}]}' +set +e +echo "$payload" | "$ROOT/target/release/nyash" --ny-parser-pipe >/dev/null 2>&1 +rc=$? +set -e + +if [ $rc -eq 9 ]; then + echo "[PASS] gate_c_parity_pipe_vm" + exit 0 +else + echo "[FAIL] gate_c_parity_pipe_vm: expected rc=9 got $rc" >&2 + exit 1 +fi +