diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs new file mode 100644 index 00000000..a63de075 --- /dev/null +++ b/src/runner/dispatch.rs @@ -0,0 +1,172 @@ +/*! + * Runner dispatch helpers β€” execute MIR module and report result + */ + +use super::*; +use crate::runner::json_v0_bridge; +use nyash_rust::parser::NyashParser; +use std::{fs, process}; + +impl NyashRunner { + /// Thin file dispatcher: select backend and delegate to mode executors + pub(crate) fn run_file(&self, filename: &str) { + // Selfhost pipeline (Ny -> JSON v0) behind env gate + if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") { + if self.try_run_selfhost_pipeline(filename) { + return; + } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)"); + } + } + + // Direct v0 bridge when requested via CLI/env + let use_ny_parser = self.config.parser_ny || std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1"); + if use_ny_parser { + let code = match fs::read_to_string(filename) { + Ok(content) => content, + Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); } + }; + match json_v0_bridge::parse_source_v0_to_module(&code) { + Ok(module) => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("πŸš€ Nyash MIR Interpreter - (parser=ny) Executing file: {} πŸš€", filename); + } + self.execute_mir_module(&module); + return; + } + Err(e) => { eprintln!("❌ Direct bridge parse error: {}", e); process::exit(1); } + } + } + + // AST dump mode + if self.config.dump_ast { + println!("🧠 Nyash AST Dump - Processing file: {}", filename); + let code = match fs::read_to_string(filename) { + Ok(content) => content, + Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); } + }; + let ast = match NyashParser::parse_from_string(&code) { + Ok(ast) => ast, + Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); } + }; + println!("{:#?}", ast); + return; + } + + // MIR dump/verify + if self.config.dump_mir || self.config.verify_mir { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("πŸš€ Nyash MIR Compiler - Processing file: {} πŸš€", filename); + } + self.execute_mir_mode(filename); + return; + } + + // WASM / AOT (feature-gated) + if self.config.compile_wasm { + #[cfg(feature = "wasm-backend")] + { self.execute_wasm_mode(filename); return; } + #[cfg(not(feature = "wasm-backend"))] + { eprintln!("❌ WASM backend not available. Please rebuild with: cargo build --features wasm-backend"); process::exit(1); } + } + if self.config.compile_native { + #[cfg(feature = "cranelift-jit")] + { self.execute_aot_mode(filename); return; } + #[cfg(not(feature = "cranelift-jit"))] + { eprintln!("❌ Native AOT compilation requires Cranelift. Please rebuild: cargo build --features cranelift-jit"); process::exit(1); } + } + + // Backend selection + match self.config.backend.as_str() { + "mir" => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("πŸš€ Nyash MIR Interpreter - Executing file: {} πŸš€", filename); + } + self.execute_mir_mode(filename); + } + #[cfg(feature = "cranelift-jit")] + "jit-direct" => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("⚑ Nyash JIT-Direct Backend - Executing file: {} ⚑", filename); + } + #[cfg(feature = "cranelift-jit")] + { + crate::runner::modes::cranelift::execute_jit_direct_mode(self, filename); + } + #[cfg(not(feature = "cranelift-jit"))] + { + eprintln!("❌ Cranelift backend not available. Please rebuild with: cargo build --features cranelift-jit"); + process::exit(1); + } + } + "llvm" => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("⚑ Nyash LLVM Backend - Executing file: {} ⚑", filename); + } + self.execute_llvm_mode(filename); + } + _ => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + println!("πŸ¦€ Nyash Rust Implementation - Executing file: {} πŸ¦€", filename); + if let Some(fuel) = self.config.debug_fuel { + println!("πŸ”₯ Debug fuel limit: {} iterations", fuel); + } else { + println!("πŸ”₯ Debug fuel limit: unlimited"); + } + println!("===================================================="); + } + self.execute_nyash_file(filename); + } + } + } + pub(crate) fn execute_mir_module(&self, module: &crate::mir::MirModule) { + use crate::backend::MirInterpreter; + use crate::box_trait::{IntegerBox, BoolBox, StringBox}; + use crate::boxes::FloatBox; + use crate::mir::MirType; + + let mut interp = MirInterpreter::new(); + match interp.execute_module(module) { + Ok(result) => { + println!("βœ… MIR interpreter execution completed!"); + if let Some(func) = module.functions.get("main") { + let (ety, sval) = match &func.signature.return_type { + MirType::Float => { + if let Some(fb) = result.as_any().downcast_ref::() { + ("Float", format!("{}", fb.value)) + } else if let Some(ib) = result.as_any().downcast_ref::() { + ("Float", format!("{}", ib.value as f64)) + } else { ("Float", result.to_string_box().value) } + } + MirType::Integer => { + if let Some(ib) = result.as_any().downcast_ref::() { + ("Integer", ib.value.to_string()) + } else { ("Integer", result.to_string_box().value) } + } + MirType::Bool => { + if let Some(bb) = result.as_any().downcast_ref::() { + ("Bool", bb.value.to_string()) + } else if let Some(ib) = result.as_any().downcast_ref::() { + ("Bool", (ib.value != 0).to_string()) + } else { ("Bool", result.to_string_box().value) } + } + MirType::String => { + if let Some(sb) = result.as_any().downcast_ref::() { + ("String", sb.value.clone()) + } else { ("String", result.to_string_box().value) } + } + _ => { (result.type_name(), result.to_string_box().value) } + }; + println!("ResultType(MIR): {}", ety); + println!("Result: {}", sval); + } else { + println!("Result: {:?}", result); + } + } + Err(e) => { + eprintln!("❌ MIR interpreter error: {}", e); + std::process::exit(1); + } + } + } +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index e84a286e..d92a351d 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -31,6 +31,10 @@ mod modes; mod demos; mod json_v0_bridge; mod mir_json_emit; +mod pipe_io; +mod pipeline; +mod dispatch; +mod selfhost; // v2 plugin system imports use nyash_rust::runtime; @@ -116,50 +120,10 @@ impl NyashRunner { } return; } - // Using/module overrides via env only (MVP) - // Prepare shared accumulators for script/env processing - let mut using_paths: Vec = Vec::new(); - let mut pending_modules: Vec<(String, String)> = Vec::new(); + // Using/module overrides pre-processing + let mut using_ctx = self.init_using_context(); let mut pending_using: Vec<(String, Option)> = Vec::new(); - // Using-paths from env, with defaults - if let Ok(p) = std::env::var("NYASH_USING_PATH") { - for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } - } - if using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); } - // nyash.toml: load [modules] and [using.paths] - if std::path::Path::new("nyash.toml").exists() { - if let Ok(text) = fs::read_to_string("nyash.toml") { - if let Ok(doc) = toml::from_str::(&text) { - if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) { - for (k, v) in mods.iter() { - if let Some(path) = v.as_str() { - pending_modules.push((k.to_string(), path.to_string())); - } - } - } - if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) { - if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) { - for p in paths_arr { - if let Some(s) = p.as_str() { - let s = s.trim(); - if !s.is_empty() { using_paths.push(s.to_string()); } - } - } - } - } - } - } - } - // Modules mapping from env (e.g., FOO=path) - if let Ok(ms) = std::env::var("NYASH_MODULES") { - for ent in ms.split(',') { - if let Some((k,v)) = ent.split_once('=') { - let k=k.trim(); let v=v.trim(); - if !k.is_empty() && !v.is_empty() { pending_modules.push((k.to_string(), v.to_string())); } - } - } - } - for (ns, path) in pending_modules.iter() { + for (ns, path) in using_ctx.pending_modules.iter() { let sb = crate::box_trait::StringBox::new(path.clone()); crate::runtime::modules_registry::set(ns.clone(), Box::new(sb)); } @@ -182,81 +146,7 @@ impl NyashRunner { } // Phase-15: JSON IR v0 bridge (stdin/file) - if self.config.ny_parser_pipe || self.config.json_file.is_some() { - let json = if let Some(path) = &self.config.json_file { - match std::fs::read_to_string(path) { - Ok(s) => s, - Err(e) => { eprintln!("❌ json-file read error: {}", e); std::process::exit(1); } - } - } else { - use std::io::Read; - let mut buf = String::new(); - if let Err(e) = std::io::stdin().read_to_string(&mut buf) { - eprintln!("❌ stdin read error: {}", e); std::process::exit(1); - } - buf - }; - match json_v0_bridge::parse_json_v0_to_module(&json) { - Ok(module) => { - // Optional dump via env verbose - json_v0_bridge::maybe_dump_mir(&module); - // Optional: delegate to PyVM when NYASH_PIPE_USE_PYVM=1 - if std::env::var("NYASH_PIPE_USE_PYVM").ok().as_deref() == Some("1") { - let py = which::which("python3").ok(); - if let Some(py3) = py { - let runner = std::path::Path::new("tools/pyvm_runner.py"); - if runner.exists() { - // Emit MIR(JSON) for PyVM - let tmp_dir = std::path::Path::new("tmp"); - let _ = std::fs::create_dir_all(tmp_dir); - let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); - if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { - eprintln!("❌ PyVM MIR JSON emit error: {}", e); - std::process::exit(1); - } - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[Bridge] using PyVM (pipe) β†’ {}", mir_json_path.display()); - } - // Determine entry function hint (prefer Main.main if present) - let entry = if module.functions.contains_key("Main.main") { "Main.main" } - else if module.functions.contains_key("main") { "main" } else { "Main.main" }; - let status = std::process::Command::new(py3) - .args([ - runner.to_string_lossy().as_ref(), - "--in", - &mir_json_path.display().to_string(), - "--entry", - entry, - ]) - .status() - .map_err(|e| format!("spawn pyvm: {}", e)) - .unwrap(); - let code = status.code().unwrap_or(1); - if !status.success() { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("❌ PyVM (pipe) failed (status={})", code); - } - } - std::process::exit(code); - } else { - eprintln!("❌ PyVM runner not found: {}", runner.display()); - std::process::exit(1); - } - } else { - eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM with --ny-parser-pipe."); - std::process::exit(1); - } - } - // Default: Execute via MIR interpreter - self.execute_mir_module(&module); - return; - } - Err(e) => { - eprintln!("❌ JSON v0 bridge error: {}", e); - std::process::exit(1); - } - } - } + if self.try_run_json_v0_pipe() { return; } // Run named task from nyash.toml (MVP) if let Some(task) = self.config.run_task.clone() { if let Err(e) = run_named_task(&task) { @@ -307,20 +197,21 @@ impl NyashRunner { } // Env overrides for using rules + // Merge late env overrides (if any) if let Ok(paths) = std::env::var("NYASH_USING_PATH") { - for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_paths.push(p.to_string()); } } + for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_ctx.using_paths.push(p.to_string()); } } } if let Ok(mods) = std::env::var("NYASH_MODULES") { for ent in mods.split(',') { if let Some((k,v)) = ent.split_once('=') { let k = k.trim(); let v = v.trim(); - if !k.is_empty() && !v.is_empty() { pending_modules.push((k.to_string(), v.to_string())); } + if !k.is_empty() && !v.is_empty() { using_ctx.pending_modules.push((k.to_string(), v.to_string())); } } } } // Apply pending modules to registry as StringBox (path or ns token) - for (ns, path) in pending_modules.iter() { + for (ns, path) in using_ctx.pending_modules.iter() { let sb = nyash_rust::box_trait::StringBox::new(path.clone()); nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb)); } @@ -329,7 +220,7 @@ impl NyashRunner { let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1"); let ctx = std::path::Path::new(filename).parent(); for (ns, alias) in pending_using.iter() { - let value = match resolve_using_target(ns, false, &pending_modules, &using_paths, ctx, strict, verbose) { + let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, ctx, strict, verbose) { Ok(v) => v, Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); } }; @@ -945,57 +836,7 @@ impl NyashRunner { if speedup > 1.0 { "faster" } else { "slower" }); } - /// Execute a prepared MIR module via the interpreter (Phase-15 path) - fn execute_mir_module(&self, module: &crate::mir::MirModule) { - use crate::backend::MirInterpreter; - use crate::mir::MirType; - use crate::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox}; - use crate::boxes::FloatBox; - - let mut interp = MirInterpreter::new(); - match interp.execute_module(module) { - Ok(result) => { - println!("βœ… MIR interpreter execution completed!"); - if let Some(func) = module.functions.get("main") { - let (ety, sval) = match &func.signature.return_type { - MirType::Float => { - if let Some(fb) = result.as_any().downcast_ref::() { - ("Float", format!("{}", fb.value)) - } else if let Some(ib) = result.as_any().downcast_ref::() { - ("Float", format!("{}", ib.value as f64)) - } else { ("Float", result.to_string_box().value) } - } - MirType::Integer => { - if let Some(ib) = result.as_any().downcast_ref::() { - ("Integer", ib.value.to_string()) - } else { ("Integer", result.to_string_box().value) } - } - MirType::Bool => { - if let Some(bb) = result.as_any().downcast_ref::() { - ("Bool", bb.value.to_string()) - } else if let Some(ib) = result.as_any().downcast_ref::() { - ("Bool", (ib.value != 0).to_string()) - } else { ("Bool", result.to_string_box().value) } - } - MirType::String => { - if let Some(sb) = result.as_any().downcast_ref::() { - ("String", sb.value.clone()) - } else { ("String", result.to_string_box().value) } - } - _ => { (result.type_name(), result.to_string_box().value) } - }; - println!("ResultType(MIR): {}", ety); - println!("Result: {}", sval); - } else { - println!("Result: {:?}", result); - } - } - Err(e) => { - eprintln!("❌ MIR interpreter error: {}", e); - std::process::exit(1); - } - } - } + // execute_mir_module moved to runner/dispatch.rs /// Minimal AOT build pipeline driven by nyash.toml (MVP, single-platform, best-effort) fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> { diff --git a/src/runner/modes/common.rs b/src/runner/modes/common.rs index 96a42732..bcb0d58b 100644 --- a/src/runner/modes/common.rs +++ b/src/runner/modes/common.rs @@ -8,41 +8,17 @@ use std::io::Read; use std::process::Stdio; use std::time::{Duration, Instant}; use std::thread::sleep; +use crate::runner::pipeline::suggest_in_base; -// limited directory walk: add matching files ending with .nyash and given leaf name -fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec) { - use std::fs; - fn walk(dir: &std::path::Path, leaf: &str, out: &mut Vec, depth: usize) { - if depth == 0 || out.len() >= 5 { return; } - if let Ok(entries) = fs::read_dir(dir) { - for e in entries.flatten() { - let path = e.path(); - if path.is_dir() { - walk(&path, leaf, out, depth - 1); - if out.len() >= 5 { return; } - } else if let Some(ext) = path.extension().and_then(|s| s.to_str()) { - if ext == "nyash" { - if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { - if stem == leaf { - out.push(path.to_string_lossy().to_string()); - if out.len() >= 5 { return; } - } - } - } - } - } - } - } - let p = std::path::Path::new(base); - walk(p, leaf, out, 4); -} +// (moved) suggest_in_base is now in runner/pipeline.rs impl NyashRunner { /// File-mode dispatcher (thin wrapper around backend/mode selection) - pub(crate) fn run_file(&self, filename: &str) { + #[allow(dead_code)] + pub(crate) fn run_file_legacy(&self, filename: &str) { // Phase-15.3: Ny compiler MVP (Ny -> JSON v0) behind env gate if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") { - if self.try_run_ny_compiler_pipeline(filename) { + if self.try_run_selfhost_pipeline(filename) { return; } else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)"); @@ -154,7 +130,7 @@ impl NyashRunner { } /// Phase-15.3: Attempt Ny compiler pipeline (Ny -> JSON v0 via Ny program), then execute MIR - fn try_run_ny_compiler_pipeline(&self, filename: &str) -> bool { + pub(crate) fn try_run_ny_compiler_pipeline(&self, filename: &str) -> bool { use std::io::Write; // Read input source let code = match fs::read_to_string(filename) { diff --git a/src/runner/pipe_io.rs b/src/runner/pipe_io.rs new file mode 100644 index 00000000..b67462d7 --- /dev/null +++ b/src/runner/pipe_io.rs @@ -0,0 +1,96 @@ +/*! + * Runner Pipe I/O helpers β€” JSON v0 handling + * + * Extracted from runner/mod.rs to keep the main runner slimmer. + * Handles: + * - Reading JSON v0 from stdin or file + * - Optional MIR dump + * - Optional PyVM delegation via tools/pyvm_runner.py + * - Fallback to MIR interpreter execution + */ + +use super::*; + +impl NyashRunner { + /// Try to handle `--ny-parser-pipe` / `--json-file` flow. + /// Returns true if the request was handled (program should return early). + pub(super) fn try_run_json_v0_pipe(&self) -> bool { + if !(self.config.ny_parser_pipe || self.config.json_file.is_some()) { + return false; + } + let json = if let Some(path) = &self.config.json_file { + match std::fs::read_to_string(path) { + Ok(s) => s, + Err(e) => { eprintln!("❌ json-file read error: {}", e); std::process::exit(1); } + } + } else { + use std::io::Read; + let mut buf = String::new(); + if let Err(e) = std::io::stdin().read_to_string(&mut buf) { + eprintln!("❌ stdin read error: {}", e); std::process::exit(1); + } + buf + }; + match super::json_v0_bridge::parse_json_v0_to_module(&json) { + Ok(module) => { + // Optional dump via env verbose + super::json_v0_bridge::maybe_dump_mir(&module); + // Optional: delegate to PyVM when NYASH_PIPE_USE_PYVM=1 + if std::env::var("NYASH_PIPE_USE_PYVM").ok().as_deref() == Some("1") { + let py = which::which("python3").ok(); + if let Some(py3) = py { + let runner = std::path::Path::new("tools/pyvm_runner.py"); + if runner.exists() { + // Emit MIR(JSON) for PyVM + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); + if let Err(e) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + eprintln!("❌ PyVM MIR JSON emit error: {}", e); + std::process::exit(1); + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[Bridge] using PyVM (pipe) β†’ {}", mir_json_path.display()); + } + // Determine entry function hint (prefer Main.main if present) + let entry = if module.functions.contains_key("Main.main") { "Main.main" } + else if module.functions.contains_key("main") { "main" } else { "Main.main" }; + let status = std::process::Command::new(py3) + .args([ + runner.to_string_lossy().as_ref(), + "--in", + &mir_json_path.display().to_string(), + "--entry", + entry, + ]) + .status() + .map_err(|e| format!("spawn pyvm: {}", e)) + .unwrap(); + let code = status.code().unwrap_or(1); + if !status.success() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("❌ PyVM (pipe) failed (status={})", code); + } + } + std::process::exit(code); + } else { + eprintln!("❌ PyVM runner not found: {}", runner.display()); + std::process::exit(1); + } + } else { + eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM with --ny-parser-pipe."); + std::process::exit(1); + } + } + // Default: Execute via MIR interpreter + self.execute_mir_module(&module); + true + } + Err(e) => { + eprintln!("❌ JSON v0 bridge error: {}", e); + std::process::exit(1); + } + } + } +} + diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs new file mode 100644 index 00000000..60418821 --- /dev/null +++ b/src/runner/pipeline.rs @@ -0,0 +1,101 @@ +/*! + * Runner pipeline helpers β€” using/modules/env pre-processing + * + * Extracts the early-phase setup from runner/mod.rs: + * - load nyash.toml [modules] and [using.paths] + * - merge with defaults and env overrides + * - expose context (using_paths, pending_modules) for downstream resolution + */ + +use super::*; + +/// Using/module resolution context accumulated from config/env/nyash.toml +pub(super) struct UsingContext { + pub using_paths: Vec, + pub pending_modules: Vec<(String, String)>, +} + +impl NyashRunner { + /// Initialize using/module context from defaults, nyash.toml and env + pub(super) fn init_using_context(&self) -> UsingContext { + let mut using_paths: Vec = Vec::new(); + let mut pending_modules: Vec<(String, String)> = Vec::new(); + + // Defaults + using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string())); + + // nyash.toml: [modules] and [using.paths] + if std::path::Path::new("nyash.toml").exists() { + if let Ok(text) = std::fs::read_to_string("nyash.toml") { + if let Ok(doc) = toml::from_str::(&text) { + if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) { + for (k, v) in mods.iter() { + if let Some(path) = v.as_str() { + pending_modules.push((k.to_string(), path.to_string())); + } + } + } + if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) { + if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) { + for p in paths_arr { + if let Some(s) = p.as_str() { + let s = s.trim(); + if !s.is_empty() { using_paths.push(s.to_string()); } + } + } + } + } + } + } + } + + // Env overrides: modules and using paths + if let Ok(ms) = std::env::var("NYASH_MODULES") { + for ent in ms.split(',') { + if let Some((k, v)) = ent.split_once('=') { + let k = k.trim(); + let v = v.trim(); + if !k.is_empty() && !v.is_empty() { + pending_modules.push((k.to_string(), v.to_string())); + } + } + } + } + if let Ok(p) = std::env::var("NYASH_USING_PATH") { + for s in p.split(':') { + let s = s.trim(); + if !s.is_empty() { using_paths.push(s.to_string()); } + } + } + + UsingContext { using_paths, pending_modules } + } +} + +/// Suggest candidate files by leaf name within limited bases (apps/lib/.) +pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec) { + use std::fs; + fn walk(dir: &std::path::Path, leaf: &str, out: &mut Vec, depth: usize) { + if depth == 0 || out.len() >= 5 { return; } + if let Ok(entries) = fs::read_dir(dir) { + for e in entries.flatten() { + let path = e.path(); + if path.is_dir() { + walk(&path, leaf, out, depth - 1); + if out.len() >= 5 { return; } + } else if let Some(ext) = path.extension().and_then(|s| s.to_str()) { + if ext == "nyash" { + if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { + if stem == leaf { + out.push(path.to_string_lossy().to_string()); + if out.len() >= 5 { return; } + } + } + } + } + } + } + } + let p = std::path::Path::new(base); + walk(p, leaf, out, 4); +} diff --git a/src/runner/selfhost.rs b/src/runner/selfhost.rs new file mode 100644 index 00000000..e374355b --- /dev/null +++ b/src/runner/selfhost.rs @@ -0,0 +1,312 @@ +/*! + * Runner selfhost helpers β€” Ny compiler pipeline (Ny -> JSON v0) + * + * Transitional shim: provides a stable entrypoint from callers, while the + * heavy implementation currently lives in modes/common.rs. Next step will + * migrate the full implementation here. + */ + +use super::*; + +use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter}; +use std::{fs, process}; +use std::io::Read; +use std::process::Stdio; +use std::time::{Duration, Instant}; +use std::thread::sleep; + +impl NyashRunner { + /// Selfhost (Ny -> JSON v0) pipeline: EXE/VM/Python フォールバック含む + pub(crate) fn try_run_selfhost_pipeline(&self, filename: &str) -> bool { + use std::io::Write; + // Read input source + let code = match fs::read_to_string(filename) { + Ok(c) => c, + Err(e) => { eprintln!("[ny-compiler] read error: {}", e); return false; } + }; + // Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file) + let enable_using = std::env::var("NYASH_ENABLE_USING").ok().as_deref() == Some("1"); + let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code); + if enable_using { + let mut out = String::with_capacity(code.len()); + let mut used_names: Vec<(String, Option)> = Vec::new(); + for line in code.lines() { + let t = line.trim_start(); + if t.starts_with("using ") { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[using] stripped(lineβ†’selfhost): {}", line); + } + let rest0 = t.strip_prefix("using ").unwrap().trim(); + let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim(); + let (target, alias) = if let Some(pos) = rest0.find(" as ") { + (rest0[..pos].trim().to_string(), Some(rest0[pos+4..].trim().to_string())) + } else { (rest0.to_string(), None) }; + let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash"); + if is_path { + let path = target.trim_matches('"').to_string(); + let name = alias.clone().unwrap_or_else(|| { + std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string() + }); + used_names.push((name, Some(path))); + } else { + used_names.push((target, alias)); + } + continue; + } + out.push_str(line); + out.push('\n'); + } + // Register modules into minimal registry with best-effort path resolution + for (ns_or_alias, alias_or_path) in used_names { + if let Some(path) = alias_or_path { + let sb = crate::box_trait::StringBox::new(path); + crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb)); + } else { + let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/")); + let exists = std::path::Path::new(&rel).exists(); + let path_or_ns = if exists { rel } else { ns_or_alias.clone() }; + let sb = crate::box_trait::StringBox::new(path_or_ns); + crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb)); + } + } + code_ref = std::borrow::Cow::Owned(out); + } + + // Write to tmp/ny_parser_input.ny (as expected by Ny parser v0), unless forced to reuse existing tmp + let use_tmp_only = std::env::var("NYASH_NY_COMPILER_USE_TMP_ONLY").ok().as_deref() == Some("1"); + let tmp_dir = std::path::Path::new("tmp"); + if let Err(e) = std::fs::create_dir_all(tmp_dir) { + eprintln!("[ny-compiler] mkdir tmp failed: {}", e); + return false; + } + let tmp_path = tmp_dir.join("ny_parser_input.ny"); + if !use_tmp_only { + match std::fs::File::create(&tmp_path) { + Ok(mut f) => { + if let Err(e) = f.write_all(code_ref.as_bytes()) { + eprintln!("[ny-compiler] write tmp failed: {}", e); + return false; + } + } + Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; } + } + } + // EXE-first: if requested, try external parser EXE (nyash_compiler) + if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") { + // 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() { + // Try PATH + if let Ok(w) = which::which("nyash_compiler") { w } else { p } + } else { p } + }; + if exe_path.exists() { + let mut cmd = std::process::Command::new(&exe_path); + // Prefer passing the original filename directly (parser EXE accepts positional path) + cmd.arg(filename); + // Gates + if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { cmd.arg("--min-json"); } + if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { cmd.arg("--read-tmp"); } + if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") { for tok in raw.split_whitespace() { cmd.arg(tok); } } + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000); + let mut 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 false; } }; + let mut ch_stdout = child.stdout.take(); + let mut ch_stderr = child.stderr.take(); + let start = Instant::now(); + let mut timed_out = false; + loop { + match child.try_wait() { + Ok(Some(_status)) => { 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 false; } + } + } + 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::(); + eprintln!("[ny-compiler] exe timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n")); + return false; + } + let stdout = match String::from_utf8(out_buf) { Ok(s) => s, Err(_) => String::new() }; + let mut json_line = String::new(); + for line in stdout.lines() { let t = line.trim(); if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } } + if json_line.is_empty() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + let head: String = stdout.chars().take(200).collect(); + let errh: String = String::from_utf8_lossy(&err_buf).chars().take(200).collect(); + eprintln!("[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'", head.replace('\n', "\\n"), errh.replace('\n', "\\n")); + } + return false; + } + // Parse JSON v0 β†’ MIR module + match super::json_v0_bridge::parse_json_v0_to_module(&json_line) { + Ok(module) => { + println!("πŸš€ Ny compiler EXE path (nyβ†’json_v0) ON"); + super::json_v0_bridge::maybe_dump_mir(&module); + let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1"; + if emit_only { + return false; + } else { + // Prefer PyVM when requested AND the module contains BoxCalls (Stage-2 semantics) + let needs_pyvm = module.functions.values().any(|f| { + f.blocks.values().any(|bb| bb.instructions.iter().any(|inst| matches!(inst, crate::mir::MirInstruction::BoxCall { .. }))) + }); + if needs_pyvm && std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") { + if let Ok(py3) = which::which("python3") { + let runner = std::path::Path::new("tools/pyvm_runner.py"); + if runner.exists() { + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + eprintln!("❌ PyVM MIR JSON emit error: {}", e); + process::exit(1); + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[Bridge] using PyVM (selfhost) β†’ {}", mir_json_path.display()); + } + let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" }; + let status = std::process::Command::new(py3) + .args(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry]) + .status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap(); + let code = status.code().unwrap_or(1); + if !status.success() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("❌ PyVM (selfhost) failed (status={})", code); + } + } + // Harmonize with interpreter path for smokes: print Result then exit code + println!("Result: {}", code); + std::process::exit(code); + } + } + } + self.execute_mir_module(&module); + return true; + } + } + Err(e) => { eprintln!("[ny-compiler] json parse error: {}", e); return false; } + } + } + } + + // Fallback: run compiler.nyash via VM(PyVM) and pick the JSON line + // Guard against recursion: ensure child does NOT enable selfhost pipeline. + let mut raw = String::new(); + { + // Locate current nyash executable + let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash")); + let mut cmd = std::process::Command::new(exe); + cmd.arg("--backend").arg("vm").arg("apps/selfhost-compiler/compiler.nyash"); + // Pass script args to child when gated + if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { cmd.arg("--").arg("--min-json"); } + if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { cmd.arg("--").arg("--read-tmp"); } + // Recursion guard and minimal, quiet env for child + cmd.env_remove("NYASH_USE_NY_COMPILER"); + cmd.env_remove("NYASH_CLI_VERBOSE"); + cmd.env("NYASH_JSON_ONLY", "1"); + if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); } + if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); } + + // Timeout guard (default 2000ms) + let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000); + let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + let mut child = match cmd.spawn() { + Ok(c) => c, + Err(e) => { eprintln!("[ny-compiler] spawn nyash vm failed: {}", e); return false; } + }; + let mut ch_stdout = child.stdout.take(); + let mut ch_stderr = child.stderr.take(); + let start = Instant::now(); + let mut timed_out = false; + loop { + match child.try_wait() { + Ok(Some(_status)) => { 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] child wait error: {}", e); break; } + } + } + 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::(); + eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n")); + } + raw = String::from_utf8_lossy(&out_buf).to_string(); + } + let mut json_line = String::new(); + for line in raw.lines() { + let t = line.trim(); + if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } + } + if json_line.is_empty() { return false; } + match super::json_v0_bridge::parse_json_v0_to_module(&json_line) { + Ok(module) => { + super::json_v0_bridge::maybe_dump_mir(&module); + let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1"; + if emit_only { return false; } + // Prefer PyVM when requested AND the module contains BoxCalls (Stage-2 semantics) + let needs_pyvm = module.functions.values().any(|f| { + f.blocks.values().any(|bb| bb.instructions.iter().any(|inst| matches!(inst, crate::mir::MirInstruction::BoxCall { .. }))) + }); + if needs_pyvm && std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") { + if let Ok(py3) = which::which("python3") { + let runner = std::path::Path::new("tools/pyvm_runner.py"); + if runner.exists() { + let tmp_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(tmp_dir); + let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json"); + if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) { + eprintln!("❌ PyVM MIR JSON emit error: {}", e); + process::exit(1); + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[Bridge] using PyVM (selfhost-fallback) β†’ {}", mir_json_path.display()); + } + let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" }; + let status = std::process::Command::new(py3) + .args(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry]) + .status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap(); + let code = status.code().unwrap_or(1); + if !status.success() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("❌ PyVM (selfhost-fallback) failed (status={})", code); + } + } + // Harmonize with interpreter path for smokes + println!("Result: {}", code); + std::process::exit(code); + } + } + } + self.execute_mir_module(&module); + true + } + Err(e) => { eprintln!("❌ JSON v0 bridge error: {}", e); false } + } + } +} diff --git a/tools/selfhost_stage2_smoke.sh b/tools/selfhost_stage2_smoke.sh index 746f4816..810fd192 100644 --- a/tools/selfhost_stage2_smoke.sh +++ b/tools/selfhost_stage2_smoke.sh @@ -26,6 +26,23 @@ run_case_expect() { "$BIN" --backend vm "$file" 2>&1) RC=$? set -e + # If PyVM is used, prefer exit code parity to stdout matching + if [[ "${NYASH_VM_USE_PY:-0}" == "1" ]]; then + local expect_code="" + if [[ "$regex" =~ true ]]; then expect_code=1; fi + if [[ "$regex" =~ false ]]; then expect_code=0; fi + if [[ -z "$expect_code" ]]; then + # Try to extract the last integer from the regex (e.g., '^Result: 7') + local num + num=$(printf '%s' "$regex" | sed -E 's/.*([^0-9]|^)([0-9]+)([^0-9]|$).*/\2/;t;d' || true) + if [[ -n "$num" ]]; then expect_code="$num"; fi + fi + if [[ -n "$expect_code" ]]; then + if [[ "$RC" == "$expect_code" ]]; then pass "$name"; else fail "$name" "$OUT"; fi + return + fi + fi + # Fallback to stdout regex matching if echo "$OUT" | rg -q "$regex"; then pass "$name"; else fail "$name" "$OUT"; fi } @@ -107,4 +124,3 @@ echo "$OUT" | rg -q '^Result:\s*3\b' && pass "String.length()" || fail "String.l echo "All selfhost Stage-2 smokes PASS" >&2 exit 0 -