feat(stage1): Phase 25.1 - Stage1 CLI デバッグ改善 + 環境変数削減計画
- ✅ Stage1 CLI デバッグログ追加 - lang/src/runner/stage1_cli.hako: STAGE1_CLI_DEBUG対応 - 各関数でentry/exit/状態ログ出力 - SSAバグ調査を容易化 - ✅ Rust bridge 実装 - src/runner/stage1_bridge.rs: 子プロセス起動・環境変数配線 - NYASH_ENTRY設定、モジュールリスト生成 - ✅ デバッグヘルパースクリプト - tools/stage1_debug.sh: 環境変数自動診断・詳細ログ - tools/stage1_minimal.sh: 推奨5変数のみで実行 - ✅ 環境変数削減計画(25個→5個) - docs/development/proposals/env-var-reduction-report.md - 使用箇所マトリックス、依存関係グラフ - 6段階削減ロードマップ(80%削減目標) - docs/development/proposals/stage1-architecture-improvement.md - 他言語事例調査(Rust/Go/Nim) - アーキテクチャ統一案、実装ロードマップ - ✅ LoopForm v2 設計ドキュメント - docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md 🎯 成果: Stage1起動の複雑さを可視化、80%削減計画確立
This commit is contained in:
271
src/runner/stage1_bridge.rs
Normal file
271
src/runner/stage1_bridge.rs
Normal file
@ -0,0 +1,271 @@
|
||||
/*!
|
||||
* Stage‑1 CLI bridge — delegate to Hako Stage1 stub when explicitly enabled.
|
||||
*
|
||||
* - Entry: NYASH_USE_STAGE1_CLI=1 (default OFF).
|
||||
* - Toggle guard for child recursion: NYASH_STAGE1_CLI_CHILD=1 (set by bridge).
|
||||
* - Entry path override: STAGE1_CLI_ENTRY or HAKORUNE_STAGE1_ENTRY (optional).
|
||||
* - Mode toggles:
|
||||
* - STAGE1_EMIT_PROGRAM_JSON=1 : emit program-json <source.hako>
|
||||
* - STAGE1_EMIT_MIR_JSON=1 : emit mir-json (<source.hako> or STAGE1_PROGRAM_JSON)
|
||||
* - STAGE1_BACKEND={vm|llvm|pyvm} hint for run path (default: CLI backend)
|
||||
*
|
||||
* Notes
|
||||
* - This bridge aims to keep Rust Stage0 thin: it only invokes the Stage1 stub
|
||||
* (lang/src/runner/stage1_cli.hako) with script args and exits with the stub's code.
|
||||
* - When toggles are unset or this is a child invocation, the bridge is a no-op.
|
||||
*/
|
||||
|
||||
use super::NyashRunner;
|
||||
use crate::cli::CliGroups;
|
||||
use serde_json;
|
||||
use std::{path::Path, process};
|
||||
|
||||
impl NyashRunner {
|
||||
fn collect_modules_list() -> Option<String> {
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let path = PathBuf::from("nyash.toml");
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
let content = fs::read_to_string(&path).ok()?;
|
||||
let mut entries: Vec<String> = Vec::new();
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
let mut in_modules = false;
|
||||
for raw in content.lines() {
|
||||
let line = raw.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
if line.starts_with('[') {
|
||||
in_modules = line == "[modules]";
|
||||
continue;
|
||||
}
|
||||
if !in_modules {
|
||||
continue;
|
||||
}
|
||||
if let Some((k, v_raw)) = line.split_once('=') {
|
||||
let key = k.trim().trim_matches('"');
|
||||
let mut v = v_raw.trim();
|
||||
if let Some((head, _comment)) = v.split_once('#') {
|
||||
v = head.trim();
|
||||
}
|
||||
if v.starts_with('"') && v.ends_with('"') && v.len() >= 2 {
|
||||
v = &v[1..v.len().saturating_sub(1)];
|
||||
}
|
||||
if !key.is_empty() && !v.is_empty() {
|
||||
if seen.insert(key.to_string()) {
|
||||
entries.push(format!("{key}={v}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add a few well-known aliases required by Stage‑1 CLI if they are absent in nyash.toml.
|
||||
for (k, v) in [
|
||||
(
|
||||
"lang.compiler.entry.using_resolver_box",
|
||||
"lang/src/compiler/entry/using_resolver_box.hako",
|
||||
),
|
||||
(
|
||||
"selfhost.shared.host_bridge.codegen_bridge",
|
||||
"lang/src/shared/host_bridge/codegen_bridge_box.hako",
|
||||
),
|
||||
] {
|
||||
if seen.insert(k.to_string()) {
|
||||
entries.push(format!("{k}={v}"));
|
||||
}
|
||||
}
|
||||
if entries.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(entries.join("|||"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") {
|
||||
return None;
|
||||
}
|
||||
if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("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")
|
||||
{
|
||||
eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
|
||||
}
|
||||
|
||||
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());
|
||||
if !Path::new(&entry).exists() {
|
||||
eprintln!("[stage1-cli] entry not found: {}", entry);
|
||||
return Some(97);
|
||||
}
|
||||
|
||||
let source = groups
|
||||
.input
|
||||
.file
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("STAGE1_SOURCE").ok())
|
||||
.or_else(|| std::env::var("STAGE1_INPUT").ok());
|
||||
|
||||
let emit_program = std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1");
|
||||
let emit_mir = std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
|
||||
let mut stage1_args: Vec<String> = Vec::new();
|
||||
let mut stage1_env_script_args: Option<String> = None;
|
||||
let mut stage1_source_env: Option<String> = None;
|
||||
let mut stage1_progjson_env: Option<String> = None;
|
||||
if emit_program {
|
||||
let src = source.as_ref().cloned().unwrap_or_else(|| {
|
||||
eprintln!("[stage1-cli] STAGE1_EMIT_PROGRAM_JSON=1 but no input file provided");
|
||||
process::exit(97);
|
||||
});
|
||||
stage1_args.push("emit".into());
|
||||
stage1_args.push("program-json".into());
|
||||
stage1_args.push(src);
|
||||
stage1_source_env = stage1_args.last().cloned();
|
||||
} else if emit_mir {
|
||||
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
|
||||
stage1_args.push("emit".into());
|
||||
stage1_args.push("mir-json".into());
|
||||
stage1_args.push("--from-program-json".into());
|
||||
stage1_args.push(pjson);
|
||||
stage1_progjson_env = stage1_args.last().cloned();
|
||||
} else {
|
||||
let src = source.as_ref().cloned().unwrap_or_else(|| {
|
||||
eprintln!("[stage1-cli] STAGE1_EMIT_MIR_JSON=1 but no input file provided");
|
||||
process::exit(97);
|
||||
});
|
||||
stage1_args.push("emit".into());
|
||||
stage1_args.push("mir-json".into());
|
||||
stage1_args.push(src);
|
||||
stage1_source_env = stage1_args.last().cloned();
|
||||
}
|
||||
} else {
|
||||
let src = source.as_ref().cloned().unwrap_or_else(|| {
|
||||
eprintln!("[stage1-cli] NYASH_USE_STAGE1_CLI=1 requires an input file to run");
|
||||
process::exit(97);
|
||||
});
|
||||
stage1_args.push("run".into());
|
||||
let backend = std::env::var("STAGE1_BACKEND")
|
||||
.ok()
|
||||
.unwrap_or_else(|| groups.backend.backend.clone());
|
||||
stage1_args.push("--backend".into());
|
||||
stage1_args.push(backend);
|
||||
stage1_args.push(src);
|
||||
stage1_source_env = stage1_args.last().cloned();
|
||||
}
|
||||
|
||||
// Forward script args provided to the parent process (via -- arg1 arg2 ...)
|
||||
if let Ok(json) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
|
||||
if let Ok(mut extras) = serde_json::from_str::<Vec<String>>(&json) {
|
||||
stage1_args.append(&mut extras);
|
||||
}
|
||||
}
|
||||
// Also pass args via env to guarantee argv is well-defined in the stub.
|
||||
if std::env::var("NYASH_SCRIPT_ARGS_JSON").is_err() {
|
||||
if let Ok(json) = serde_json::to_string(&stage1_args) {
|
||||
stage1_env_script_args = Some(json);
|
||||
}
|
||||
}
|
||||
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| {
|
||||
// Fallback to release binary path when current_exe is unavailable
|
||||
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/1".to_string());
|
||||
cmd.arg(&entry).arg("--");
|
||||
for a in &stage1_args {
|
||||
cmd.arg(a);
|
||||
}
|
||||
if let Some(json) = stage1_env_script_args.as_ref() {
|
||||
cmd.env("NYASH_SCRIPT_ARGS_JSON", json);
|
||||
}
|
||||
if let Some(src) = stage1_source_env.as_ref() {
|
||||
cmd.env("STAGE1_SOURCE", src);
|
||||
}
|
||||
if let Some(pjson) = stage1_progjson_env.as_ref() {
|
||||
cmd.env("STAGE1_PROGRAM_JSON", pjson);
|
||||
}
|
||||
// Pass source text inline to avoid FileBox dependency when possible.
|
||||
if stage1_source_env.is_none() {
|
||||
if let Some(src_path) = source.as_ref() {
|
||||
if let Ok(text) = std::fs::read_to_string(&src_path) {
|
||||
cmd.env("STAGE1_SOURCE_TEXT", text);
|
||||
}
|
||||
}
|
||||
} else if let Some(src_path) = stage1_source_env.as_ref() {
|
||||
if let Ok(text) = std::fs::read_to_string(src_path) {
|
||||
cmd.env("STAGE1_SOURCE_TEXT", text);
|
||||
}
|
||||
}
|
||||
cmd.env("NYASH_STAGE1_CLI_CHILD", "1");
|
||||
if std::env::var("NYASH_NYRT_SILENT_RESULT").is_err() {
|
||||
cmd.env("NYASH_NYRT_SILENT_RESULT", "1");
|
||||
}
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").is_err() {
|
||||
cmd.env("NYASH_DISABLE_PLUGINS", "0");
|
||||
}
|
||||
if std::env::var("NYASH_FILEBOX_MODE").is_err() {
|
||||
cmd.env("NYASH_FILEBOX_MODE", "auto");
|
||||
}
|
||||
if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() {
|
||||
cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first");
|
||||
}
|
||||
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
|
||||
cmd.env("HAKO_STAGEB_APPLY_USINGS", "1");
|
||||
}
|
||||
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
||||
cmd.env("NYASH_ENABLE_USING", "1");
|
||||
}
|
||||
if std::env::var("HAKO_ENABLE_USING").is_err() {
|
||||
cmd.env("HAKO_ENABLE_USING", "1");
|
||||
}
|
||||
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
|
||||
cmd.env("NYASH_PARSER_STAGE3", "1");
|
||||
}
|
||||
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
|
||||
cmd.env("HAKO_PARSER_STAGE3", "1");
|
||||
}
|
||||
if std::env::var("HAKO_STAGEB_MODULES_LIST").is_err() {
|
||||
if let Some(mods) = Self::collect_modules_list() {
|
||||
cmd.env("HAKO_STAGEB_MODULES_LIST", mods);
|
||||
}
|
||||
}
|
||||
if std::env::var("NYASH_ENTRY").is_err() {
|
||||
cmd.env("NYASH_ENTRY", &entry_fn);
|
||||
}
|
||||
if std::env::var("STAGE1_BACKEND").is_err() {
|
||||
if let Some(be) = stage1_args
|
||||
.windows(2)
|
||||
.find(|w| w[0] == "--backend")
|
||||
.map(|w| w[1].clone())
|
||||
{
|
||||
cmd.env("STAGE1_BACKEND", be);
|
||||
}
|
||||
}
|
||||
|
||||
crate::cli_v!(
|
||||
"[stage1-cli] delegating to stub: {} -- {}",
|
||||
entry,
|
||||
stage1_args.join(" ")
|
||||
);
|
||||
let status = match cmd.status() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("[stage1-cli] failed to spawn stub: {}", e);
|
||||
return Some(97);
|
||||
}
|
||||
};
|
||||
Some(status.code().unwrap_or(1))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user