feat(phase32): L-2.1 Stage-1 UsingResolver JoinIR integration + cleanup
Phase 32 L-2.1 complete implementation: 1. Stage-1 UsingResolver main line JoinIR connection - CFG-based LoopForm construction for resolve_for_source/5 - LoopToJoinLowerer integration with handwritten fallback - JSON snapshot tests 6/6 PASS 2. JoinIR/VM Bridge improvements - Simplified join_ir_vm_bridge.rs dispatch logic - Enhanced json.rs serialization - PHI core boxes cleanup (local_scope_inspector, loop_exit_liveness, loop_var_classifier) 3. Stage-1 CLI enhancements - Extended args.rs, groups.rs, mod.rs for new options - Improved stage1_bridge module (args, env, mod) - Updated stage1_cli.hako 4. MIR builder cleanup - Simplified if_form.rs control flow - Removed dead code from loop_builder.rs - Enhanced phi_merge.rs 5. Runner module updates - json_v0_bridge/lowering.rs improvements - dispatch.rs, selfhost.rs, modes/vm.rs cleanup 6. Documentation updates - CURRENT_TASK.md, AGENTS.md - Various docs/ updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -208,9 +208,8 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
"vm" => {
|
||||
crate::cli_v!("🚀 Hakorune VM Backend - Executing file: {} 🚀", filename);
|
||||
// Route to primary VM path by default. Fallback is a last resort and must be explicitly enabled.
|
||||
let force_fallback =
|
||||
std::env::var("NYASH_VM_USE_FALLBACK").ok().as_deref() == Some("1");
|
||||
let route_trace = std::env::var("NYASH_VM_ROUTE_TRACE").ok().as_deref() == Some("1");
|
||||
let force_fallback = crate::config::env::vm_use_fallback();
|
||||
let route_trace = crate::config::env::vm_route_trace();
|
||||
if force_fallback {
|
||||
if route_trace {
|
||||
eprintln!("[vm-route] choose=fallback reason=env:NYASH_VM_USE_FALLBACK=1");
|
||||
|
||||
@ -579,8 +579,70 @@ pub(super) fn lower_program(
|
||||
}
|
||||
|
||||
pub(super) fn maybe_dump_mir(module: &MirModule) {
|
||||
// New: file dump path for offline analysis (Stage‑1/Stage‑B selfhost, ParserBox 等)
|
||||
// Use env RUST_MIR_DUMP_PATH to write the MIR printer output to a file.
|
||||
if let Some(path) = crate::config::env::rust_mir_dump_path() {
|
||||
if let Ok(mut f) = std::fs::File::create(&path) {
|
||||
let p = MirPrinter::new();
|
||||
let _ = std::io::Write::write_all(&mut f, p.print_module(module).as_bytes());
|
||||
}
|
||||
}
|
||||
// Existing: verbose flag dumps to stdout
|
||||
if crate::config::env::cli_verbose() {
|
||||
let p = MirPrinter::new();
|
||||
println!("{}", p.print_module(module));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn temp_path(name: &str) -> PathBuf {
|
||||
let mut p = std::env::temp_dir();
|
||||
p.push(format!("{}_{}", name, std::process::id()));
|
||||
p
|
||||
}
|
||||
|
||||
fn dummy_module() -> MirModule {
|
||||
MirModule::new("test-module".to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_file_when_dump_path_is_set() {
|
||||
let path = temp_path("mir_dump_path");
|
||||
let _ = fs::remove_file(&path);
|
||||
std::env::set_var("RUST_MIR_DUMP_PATH", path.to_string_lossy().to_string());
|
||||
|
||||
maybe_dump_mir(&dummy_module());
|
||||
|
||||
assert!(
|
||||
path.exists(),
|
||||
"maybe_dump_mir should write when RUST_MIR_DUMP_PATH is set"
|
||||
);
|
||||
let contents = fs::read_to_string(&path).unwrap_or_default();
|
||||
assert!(
|
||||
contents.contains("test-module"),
|
||||
"dump should contain module name"
|
||||
);
|
||||
|
||||
let _ = fs::remove_file(&path);
|
||||
std::env::remove_var("RUST_MIR_DUMP_PATH");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_write_when_dump_path_is_unset() {
|
||||
std::env::remove_var("RUST_MIR_DUMP_PATH");
|
||||
let path = temp_path("mir_dump_path_unset");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
maybe_dump_mir(&dummy_module());
|
||||
|
||||
assert!(
|
||||
!path.exists(),
|
||||
"maybe_dump_mir should not write when RUST_MIR_DUMP_PATH is unset"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,8 +92,11 @@ impl NyashRunner {
|
||||
return;
|
||||
}
|
||||
let groups = self.config.as_groups();
|
||||
if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) {
|
||||
std::process::exit(code);
|
||||
let skip_stage1_stub = groups.emit.hako_emit_program_json || groups.emit.hako_emit_mir_json;
|
||||
if !skip_stage1_stub {
|
||||
if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) {
|
||||
std::process::exit(code);
|
||||
}
|
||||
}
|
||||
// Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec.
|
||||
if let Some(path) = groups.parser.mir_json_file.as_ref() {
|
||||
@ -139,6 +142,50 @@ impl NyashRunner {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Emit Program(JSON v0) and exit
|
||||
if let Some(path) = groups.emit.emit_program_json.as_ref() {
|
||||
// Prefer Stage-1/.hako route when requested via hako-* flags or env
|
||||
let use_hako = groups.emit.hako_emit_program_json
|
||||
|| crate::config::env::stage1::emit_program_json()
|
||||
|| crate::config::env::stage1::enabled();
|
||||
if use_hako {
|
||||
if let Err(e) = self.emit_program_json_v0(&groups, path) {
|
||||
eprintln!("❌ emit-program-json error: {}", e);
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
println!("Program JSON written: {}", path);
|
||||
std::process::exit(0);
|
||||
}
|
||||
} else if let Some(file) = groups.input.file.as_ref() {
|
||||
match std::fs::read_to_string(file) {
|
||||
Ok(code) => match crate::parser::NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => {
|
||||
let prog = crate::r#macro::ast_json::ast_to_json(&ast);
|
||||
let out_path = std::path::Path::new(path);
|
||||
if let Err(e) = std::fs::write(out_path, prog.to_string()) {
|
||||
eprintln!("❌ emit-program-json write error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
println!("Program JSON written: {}", out_path.display());
|
||||
std::process::exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
file, &code, &e,
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", file, e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ --emit-program-json requires an input file");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
// Preprocess usings and directives (includes dep-tree log)
|
||||
self.preprocess_usings_and_directives(&groups);
|
||||
// JSON v0 bridge
|
||||
|
||||
@ -2,13 +2,9 @@ use super::super::NyashRunner;
|
||||
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
||||
use std::{fs, process};
|
||||
|
||||
// Phase 30.x: JoinIR VM Bridge integration (experimental)
|
||||
// Phase 30 F-4.4: JoinIR VM Bridge dispatch (experimental)
|
||||
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||
use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled};
|
||||
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, lower_skip_ws_to_joinir, JoinFuncId};
|
||||
use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
||||
use crate::mir::join_ir_vm_bridge_dispatch::try_run_joinir_vm_bridge;
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute VM mode with full plugin initialization and AST prelude merge
|
||||
@ -503,127 +499,10 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 30.x: JoinIR VM Bridge experimental path
|
||||
// Phase 30 F-4.4: JoinIR VM Bridge experimental path (consolidated dispatch)
|
||||
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||
// Currently only supports minimal_ssa_skip_ws.hako (Main.skip function)
|
||||
let joinir_path_attempted = if joinir_experiment_enabled() && joinir_vm_bridge_enabled() {
|
||||
// Check if this module contains Main.skip/1 (minimal_ssa_skip_ws target)
|
||||
// Note: function names include arity suffix like "Main.skip/1"
|
||||
let has_main_skip = module_vm.functions.contains_key("Main.skip/1");
|
||||
let has_trim = module_vm.functions.contains_key("FuncScannerBox.trim/1");
|
||||
// Phase 30.x: Stage-1 UsingResolver (deletable block - start)
|
||||
let has_stage1_usingresolver = module_vm.functions.contains_key("Stage1UsingResolverBox.resolve_for_source/5");
|
||||
// Phase 30.x: Stage-1 UsingResolver (deletable block - end)
|
||||
|
||||
if has_main_skip {
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Main.skip");
|
||||
match lower_skip_ws_to_joinir(&module_vm) {
|
||||
Some(join_module) => {
|
||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||
.unwrap_or_else(|_| " abc".to_string());
|
||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||
|
||||
match run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(input)],
|
||||
) {
|
||||
Ok(result) => {
|
||||
let exit_code = match &result {
|
||||
JoinValue::Int(v) => *v as i32,
|
||||
JoinValue::Bool(b) => if *b { 1 } else { 0 },
|
||||
_ => 0,
|
||||
};
|
||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result);
|
||||
if !quiet_pipe {
|
||||
println!("RC: {}", exit_code);
|
||||
}
|
||||
process::exit(exit_code);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", e);
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false // Continue to normal VM execution
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("[joinir/vm_bridge] lower_skip_ws_to_joinir returned None");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
} else if has_trim {
|
||||
// Phase 30.x: FuncScannerBox.trim/1 JoinIR path
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for FuncScannerBox.trim");
|
||||
match lower_funcscanner_trim_to_joinir(&module_vm) {
|
||||
Some(join_module) => {
|
||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
||||
.unwrap_or_else(|_| " abc ".to_string());
|
||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
||||
|
||||
match run_joinir_via_vm(
|
||||
&join_module,
|
||||
JoinFuncId::new(0),
|
||||
&[JoinValue::Str(input)],
|
||||
) {
|
||||
Ok(result) => {
|
||||
// trim returns a string, print it and exit with 0
|
||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result);
|
||||
if !quiet_pipe {
|
||||
match &result {
|
||||
JoinValue::Str(s) => println!("{}", s),
|
||||
_ => println!("{:?}", result),
|
||||
}
|
||||
}
|
||||
process::exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e);
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
eprintln!("[joinir/vm_bridge] lower_funcscanner_trim_to_joinir returned None");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
// Phase 30.x: Stage-1 UsingResolver JoinIR path (deletable block - start)
|
||||
} else if has_stage1_usingresolver {
|
||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Stage1UsingResolverBox.resolve_for_source");
|
||||
match lower_stage1_usingresolver_to_joinir(&module_vm) {
|
||||
Some(join_module) => {
|
||||
eprintln!("[joinir/vm_bridge] ✅ Stage-1 JoinIR module generated ({} functions)", join_module.functions.len());
|
||||
// Stage-1 requires ArrayBox/MapBox arguments which JoinValue doesn't support yet
|
||||
// For now, just verify lowering works and fall back to VM
|
||||
eprintln!("[joinir/vm_bridge] Note: ArrayBox/MapBox args not yet supported in JoinValue");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path for actual execution");
|
||||
false // Fall back to VM for now
|
||||
}
|
||||
None => {
|
||||
eprintln!("[joinir/vm_bridge] lower_stage1_usingresolver_to_joinir returned None");
|
||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
||||
false
|
||||
}
|
||||
}
|
||||
// Phase 30.x: Stage-1 UsingResolver JoinIR path (deletable block - end)
|
||||
} else {
|
||||
false // No supported JoinIR target function
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Normal VM execution path (fallback or default)
|
||||
if joinir_path_attempted {
|
||||
// This branch is never reached because successful JoinIR path calls process::exit()
|
||||
unreachable!("JoinIR path should have exited");
|
||||
}
|
||||
// Routing logic is centralized in join_ir_vm_bridge_dispatch module
|
||||
try_run_joinir_vm_bridge(&module_vm, quiet_pipe);
|
||||
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(ret) => {
|
||||
|
||||
@ -227,15 +227,13 @@ impl NyashRunner {
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--min-json".to_string());
|
||||
}
|
||||
// Phase 28.2 fix: Use Stage-B compiler for file-based compilation.
|
||||
// Stage-A (_compile_source_to_json_v0) expects source CONTENT, not path.
|
||||
// Stage-B (StageBMain._do_compile_stage_b) reads files via FileBox.
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--stage-b".to_string());
|
||||
// Pass source file path to compiler.hako
|
||||
// Phase 28.2b fix: Use Stage-A (not Stage-B) for compiler.hako.
|
||||
// Stage-A expects source CONTENT via `--source <code>`, not a file path.
|
||||
// Stage-B requires FileBox (plugins), but Rust VM can't execute user-defined
|
||||
// boxes like StageBMain inside compiler.hako. Stage-A works without plugins.
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--source".to_string());
|
||||
extra_owned.push("tmp/ny_parser_input.ny".to_string());
|
||||
extra_owned.push(code_ref.to_string());
|
||||
if crate::config::env::ny_compiler_stage3() {
|
||||
extra_owned.push("--".to_string());
|
||||
extra_owned.push("--stage3".to_string());
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
* Constructs stage1_args based on execution mode (emit_program / emit_mir / run).
|
||||
*/
|
||||
|
||||
use crate::config::env::stage1;
|
||||
use crate::cli::CliGroups;
|
||||
use serde_json;
|
||||
use std::process;
|
||||
@ -15,6 +16,7 @@ pub(super) struct Stage1Args {
|
||||
pub env_script_args: Option<String>,
|
||||
pub source_env: Option<String>,
|
||||
pub progjson_env: Option<String>,
|
||||
pub emit_mir: bool,
|
||||
}
|
||||
|
||||
/// Build stage1_args based on execution mode
|
||||
@ -25,23 +27,11 @@ pub(super) struct Stage1Args {
|
||||
/// - run: run --backend <backend> <source.hako>
|
||||
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
||||
let source = std::env::var("NYASH_STAGE1_INPUT")
|
||||
.ok()
|
||||
.or_else(|| groups.input.file.as_ref().cloned())
|
||||
.or_else(|| std::env::var("STAGE1_SOURCE").ok())
|
||||
.or_else(|| std::env::var("STAGE1_INPUT").ok());
|
||||
let source = stage1::input_path()
|
||||
.or_else(|| groups.input.file.as_ref().cloned());
|
||||
|
||||
let mode_env = std::env::var("NYASH_STAGE1_MODE")
|
||||
.ok()
|
||||
.map(|m| m.to_ascii_lowercase().replace('_', "-"));
|
||||
let emit_program = matches!(
|
||||
mode_env.as_deref(),
|
||||
Some("emit-program") | Some("emit-program-json")
|
||||
) || std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1");
|
||||
let emit_mir = matches!(
|
||||
mode_env.as_deref(),
|
||||
Some("emit-mir") | Some("emit-mir-json")
|
||||
) || std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
let emit_program = stage1::emit_program_json();
|
||||
let emit_mir = stage1::emit_mir_json();
|
||||
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
let mut source_env: Option<String> = None;
|
||||
@ -57,9 +47,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
args.push(src);
|
||||
source_env = args.last().cloned();
|
||||
} else if emit_mir {
|
||||
if let Ok(pjson) = std::env::var("NYASH_STAGE1_PROGRAM_JSON")
|
||||
.or_else(|_| std::env::var("STAGE1_PROGRAM_JSON"))
|
||||
{
|
||||
if let Some(pjson) = stage1::program_json_path() {
|
||||
args.push("emit".into());
|
||||
args.push("mir-json".into());
|
||||
args.push("--from-program-json".into());
|
||||
@ -81,9 +69,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
process::exit(97);
|
||||
});
|
||||
args.push("run".into());
|
||||
let backend = std::env::var("NYASH_STAGE1_BACKEND")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("STAGE1_BACKEND").ok())
|
||||
let backend = stage1::backend_hint()
|
||||
.unwrap_or_else(|| groups.backend.backend.clone());
|
||||
args.push("--backend".into());
|
||||
args.push(backend);
|
||||
@ -110,5 +96,6 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
env_script_args,
|
||||
source_env,
|
||||
progjson_env,
|
||||
emit_mir,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
* Sets default environment variables for Stage-1 CLI child process.
|
||||
*/
|
||||
|
||||
use crate::config::env;
|
||||
use crate::config::env::stage1;
|
||||
use std::process::Command;
|
||||
|
||||
/// Configure environment variables for Stage-1 CLI child process
|
||||
@ -23,12 +25,8 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Unified Stage-1 env (NYASH_STAGE1_*) — derive from legacy if unset to keep compatibility.
|
||||
if std::env::var("NYASH_STAGE1_MODE").is_err() {
|
||||
if std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-program");
|
||||
} else if std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-mir");
|
||||
} else if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "run");
|
||||
if let Some(m) = stage1::mode() {
|
||||
cmd.env("NYASH_STAGE1_MODE", m);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,17 +51,17 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Stage-1 unified input/backend (fallback to legacy)
|
||||
if std::env::var("NYASH_STAGE1_INPUT").is_err() {
|
||||
if let Ok(src) = std::env::var("STAGE1_SOURCE") {
|
||||
if let Some(src) = stage1::input_path() {
|
||||
cmd.env("NYASH_STAGE1_INPUT", src);
|
||||
}
|
||||
}
|
||||
if std::env::var("NYASH_STAGE1_BACKEND").is_err() {
|
||||
if let Ok(be) = std::env::var("STAGE1_BACKEND") {
|
||||
if let Some(be) = stage1::backend_hint().or_else(stage1::backend_alias_warned) {
|
||||
cmd.env("NYASH_STAGE1_BACKEND", be);
|
||||
}
|
||||
}
|
||||
if std::env::var("NYASH_STAGE1_PROGRAM_JSON").is_err() {
|
||||
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
|
||||
if let Some(pjson) = stage1::program_json_path() {
|
||||
cmd.env("NYASH_STAGE1_PROGRAM_JSON", pjson);
|
||||
}
|
||||
}
|
||||
@ -78,16 +76,16 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Parser toggles
|
||||
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
||||
cmd.env("NYASH_ENABLE_USING", "1");
|
||||
cmd.env("NYASH_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||
}
|
||||
if std::env::var("HAKO_ENABLE_USING").is_err() {
|
||||
cmd.env("HAKO_ENABLE_USING", "1");
|
||||
cmd.env("HAKO_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||
}
|
||||
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
|
||||
cmd.env("NYASH_PARSER_STAGE3", "1");
|
||||
cmd.env("NYASH_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||
}
|
||||
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
|
||||
cmd.env("HAKO_PARSER_STAGE3", "1");
|
||||
cmd.env("HAKO_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||
}
|
||||
|
||||
// Modules list
|
||||
@ -104,11 +102,11 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Backend hint
|
||||
if std::env::var("STAGE1_BACKEND").is_err() {
|
||||
if let Some(be) = stage1_args
|
||||
let be_cli = stage1_args
|
||||
.windows(2)
|
||||
.find(|w| w[0] == "--backend")
|
||||
.map(|w| w[1].clone())
|
||||
{
|
||||
.map(|w| w[1].clone());
|
||||
if let Some(be) = stage1::backend_hint().or(be_cli) {
|
||||
cmd.env("STAGE1_BACKEND", be);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,44 +20,117 @@ mod env;
|
||||
mod modules;
|
||||
|
||||
use super::NyashRunner;
|
||||
use crate::runner::stage1_bridge::args::Stage1Args;
|
||||
use crate::config;
|
||||
use crate::config::env::stage1;
|
||||
use crate::cli::CliGroups;
|
||||
use crate::mir::MirPrinter;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
impl NyashRunner {
|
||||
/// Emit Program(JSON v0) using Stage-1 stub and write to a file.
|
||||
pub(crate) fn emit_program_json_v0(&self, groups: &CliGroups, out_path: &str) -> Result<(), String> {
|
||||
// Resolve source path from CLI groups or env
|
||||
let source = stage1::input_path()
|
||||
.or_else(|| groups.input.file.as_ref().cloned())
|
||||
.ok_or_else(|| "emit-program-json requires an input file".to_string())?;
|
||||
|
||||
// Build minimal args to force emit program-json
|
||||
let args_result = Stage1Args {
|
||||
args: vec!["emit".into(), "program-json".into(), source.clone()],
|
||||
env_script_args: None,
|
||||
source_env: Some(source.clone()),
|
||||
progjson_env: None,
|
||||
emit_mir: false,
|
||||
};
|
||||
|
||||
// Collect modules list (same as bridge)
|
||||
let modules_list = modules::collect_modules_list();
|
||||
|
||||
// Prepare command
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| {
|
||||
std::path::PathBuf::from("target/release/nyash")
|
||||
});
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
let entry_fn =
|
||||
std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
|
||||
let entry = stage1::entry_override()
|
||||
.unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
|
||||
cmd.arg(&entry).arg("--");
|
||||
for a in &args_result.args {
|
||||
cmd.arg(a);
|
||||
}
|
||||
// Set environment variables for args
|
||||
if let Some(src) = args_result.source_env.as_ref() {
|
||||
cmd.env("STAGE1_SOURCE", src);
|
||||
cmd.env("NYASH_STAGE1_INPUT", src);
|
||||
}
|
||||
if let Ok(text) = std::fs::read_to_string(&source) {
|
||||
cmd.env("STAGE1_SOURCE_TEXT", text);
|
||||
}
|
||||
cmd.env("NYASH_USE_STAGE1_CLI", "1");
|
||||
// Configure environment (shared helper)
|
||||
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
|
||||
cmd.env("STAGE1_EMIT_PROGRAM_JSON", "1");
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|e| format!("stage1 emit program-json spawn failed: {}", e))?;
|
||||
if !output.stderr.is_empty() && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1") {
|
||||
let _ = std::io::stderr().write_all(&output.stderr);
|
||||
}
|
||||
if !output.status.success() {
|
||||
if !output.stdout.is_empty() {
|
||||
let _ = std::io::stdout().write_all(&output.stdout);
|
||||
}
|
||||
if !output.stderr.is_empty() {
|
||||
let _ = std::io::stderr().write_all(&output.stderr);
|
||||
}
|
||||
return Err(format!(
|
||||
"stage1 emit program-json exited with code {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout)
|
||||
.ok_or_else(|| "stage1 emit program-json did not produce Program(JSON v0)".to_string())?;
|
||||
std::fs::write(out_path, line)
|
||||
.map_err(|e| format!("write {} failed: {}", out_path, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If enabled, run the Stage-1 CLI stub as a child process and return its exit code.
|
||||
/// Returns None when the bridge is not engaged.
|
||||
pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option<i32> {
|
||||
// Temporary trace: confirm the bridge is evaluated
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
||||
if config::env::cli_verbose_level() == 2 {
|
||||
eprintln!("[stage1-bridge/trace] maybe_run_stage1_cli_stub invoked");
|
||||
}
|
||||
|
||||
// Guard: skip if child invocation
|
||||
if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
||||
if stage1::child_invocation() {
|
||||
if config::env::cli_verbose_level() == 2 {
|
||||
eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// Guard: skip if not enabled
|
||||
if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
||||
if !stage1::enabled() {
|
||||
if config::env::cli_verbose_level() == 2 {
|
||||
eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1");
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2")
|
||||
{
|
||||
if config::env::cli_verbose() || config::env::cli_verbose_level() == 2 {
|
||||
eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
|
||||
}
|
||||
|
||||
// Locate Stage-1 CLI entry
|
||||
let entry = std::env::var("STAGE1_CLI_ENTRY")
|
||||
.or_else(|_| std::env::var("HAKORUNE_STAGE1_ENTRY"))
|
||||
.unwrap_or_else(|_| "lang/src/runner/stage1_cli.hako".to_string());
|
||||
let entry = stage1::entry_override()
|
||||
.unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
|
||||
if !Path::new(&entry).exists() {
|
||||
eprintln!("[stage1-cli] entry not found: {}", entry);
|
||||
return Some(97);
|
||||
@ -109,6 +182,64 @@ impl NyashRunner {
|
||||
// Configure environment
|
||||
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
|
||||
|
||||
// Emit-mir mode: capture stdout for Program(JSON v0) and lower in Rust to engage maybe_dump_mir.
|
||||
if args_result.emit_mir {
|
||||
let output = match cmd.output() {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
eprintln!("[stage1-cli] failed to spawn stub: {}", e);
|
||||
return Some(97);
|
||||
}
|
||||
};
|
||||
let code = output.status.code().unwrap_or(1);
|
||||
if code != 0 {
|
||||
if !output.stderr.is_empty() {
|
||||
let _ = std::io::stderr().write_all(&output.stderr);
|
||||
}
|
||||
return Some(code);
|
||||
}
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
eprintln!("[stage1-cli] emit-mir: no Program(JSON v0) found in stub output");
|
||||
return Some(98);
|
||||
}
|
||||
};
|
||||
let module = match super::json_v0_bridge::parse_json_v0_to_module(&line) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("[stage1-cli] emit-mir: Program(JSON v0) parse error: {}", e);
|
||||
return Some(98);
|
||||
}
|
||||
};
|
||||
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||
|
||||
let groups = self.config.as_groups();
|
||||
if groups.debug.dump_mir {
|
||||
let mut printer = if groups.debug.mir_verbose {
|
||||
MirPrinter::verbose()
|
||||
} else {
|
||||
MirPrinter::new()
|
||||
};
|
||||
if groups.debug.mir_verbose_effects {
|
||||
printer.set_show_effects_inline(true);
|
||||
}
|
||||
println!("{}", printer.print_module(&module));
|
||||
}
|
||||
if let Some(path) = groups.emit.emit_mir_json.as_ref() {
|
||||
let p = std::path::Path::new(path);
|
||||
if let Err(e) =
|
||||
crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, p)
|
||||
{
|
||||
eprintln!("❌ MIR JSON emit error: {}", e);
|
||||
return Some(98);
|
||||
}
|
||||
println!("MIR JSON written: {}", p.display());
|
||||
}
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
crate::cli_v!(
|
||||
"[stage1-cli] delegating to stub: {} -- {}",
|
||||
entry,
|
||||
|
||||
Reference in New Issue
Block a user