refactor(stage1-bridge): モジュール分割 - 275→386行(4ファイル)

**分割構成**:
- modules.rs (73行): collect_modules_list - nyash.toml解析
- args.rs (106行): build_stage1_args - mode別args構築
- env.rs (82行): configure_stage1_env - 環境変数設定
- mod.rs (125行): maybe_run_stage1_cli_stub - エントリポイント

**効果**:
- 単一責任原則: 各ファイルが明確な責務
- テスタビリティ: 個別関数を単体テスト可能
- 可読性: 275行単一ファイル → 4ファイル(73-125行)
- 保守性: 変更影響範囲が明確

**永続性**:
- static instance化と無関係(削除されない)
- Phase 25.x方針適合(新規モジュールの分割は自然)

Phase: 25.x
This commit is contained in:
nyash-codex
2025-11-21 11:26:28 +09:00
parent c344451087
commit b00cc8d579
5 changed files with 386 additions and 275 deletions

View File

@ -1,275 +0,0 @@
/*!
* Stage1 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 Stage1 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 Stage1 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");
}
// Stage1 CLI 経路では既定で using 適用を無効化し、
// prefix は空HAKO_STAGEB_APPLY_USINGS=0とする。
// UsingResolver/UsingCollector の検証は専用テストで行い、
// CLI 本線はシンプルな Program(JSON) 生成に集中させる。
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
cmd.env("HAKO_STAGEB_APPLY_USINGS", "0");
}
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))
}
}

View File

@ -0,0 +1,106 @@
/*!
* Stage-1 CLI bridge - args builder
*
* Constructs stage1_args based on execution mode (emit_program / emit_mir / run).
*/
use crate::cli::CliGroups;
use serde_json;
use std::process;
/// Stage-1 args construction result
#[derive(Debug)]
pub(super) struct Stage1Args {
pub args: Vec<String>,
pub env_script_args: Option<String>,
pub source_env: Option<String>,
pub progjson_env: Option<String>,
}
/// Build stage1_args based on execution mode
///
/// # Modes
/// - emit_program: emit program-json <source.hako>
/// - emit_mir: emit mir-json (<source.hako> or STAGE1_PROGRAM_JSON)
/// - run: run --backend <backend> <source.hako>
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
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 args: Vec<String> = Vec::new();
let mut source_env: Option<String> = None;
let mut 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);
});
args.push("emit".into());
args.push("program-json".into());
args.push(src);
source_env = args.last().cloned();
} else if emit_mir {
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
args.push("emit".into());
args.push("mir-json".into());
args.push("--from-program-json".into());
args.push(pjson);
progjson_env = 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);
});
args.push("emit".into());
args.push("mir-json".into());
args.push(src);
source_env = 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);
});
args.push("run".into());
let backend = std::env::var("STAGE1_BACKEND")
.ok()
.unwrap_or_else(|| groups.backend.backend.clone());
args.push("--backend".into());
args.push(backend);
args.push(src);
source_env = 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) {
args.append(&mut extras);
}
}
// Also pass args via env to guarantee argv is well-defined in the stub.
let env_script_args = if std::env::var("NYASH_SCRIPT_ARGS_JSON").is_err() {
serde_json::to_string(&args).ok()
} else {
None
};
Stage1Args {
args,
env_script_args,
source_env,
progjson_env,
}
}

View File

@ -0,0 +1,82 @@
/*!
* Stage-1 CLI bridge - environment variable configurator
*
* Sets default environment variables for Stage-1 CLI child process.
*/
use std::process::Command;
/// Configure environment variables for Stage-1 CLI child process
///
/// Sets defaults for:
/// - Runtime behavior (NYASH_NYRT_SILENT_RESULT, NYASH_DISABLE_PLUGINS, etc.)
/// - Parser toggles (NYASH_PARSER_STAGE3, NYASH_ENABLE_USING, etc.)
/// - Stage-B configuration (HAKO_STAGEB_APPLY_USINGS, HAKO_STAGEB_MODULES_LIST, etc.)
pub(super) fn configure_stage1_env(
cmd: &mut Command,
entry_fn: &str,
stage1_args: &[String],
modules_list: Option<String>,
) {
// Child recursion guard
cmd.env("NYASH_STAGE1_CLI_CHILD", "1");
// Runtime defaults
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");
}
// Stage-1 CLI 経路では既定で using 適用を無効化し、
// prefix は空HAKO_STAGEB_APPLY_USINGS=0とする。
// UsingResolver/UsingCollector の検証は専用テストで行い、
// CLI 本線はシンプルな Program(JSON) 生成に集中させる。
if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() {
cmd.env("HAKO_STAGEB_APPLY_USINGS", "0");
}
// Parser toggles
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");
}
// Modules list
if std::env::var("HAKO_STAGEB_MODULES_LIST").is_err() {
if let Some(mods) = modules_list {
cmd.env("HAKO_STAGEB_MODULES_LIST", mods);
}
}
// Entry function
if std::env::var("NYASH_ENTRY").is_err() {
cmd.env("NYASH_ENTRY", entry_fn);
}
// Backend hint
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);
}
}
}

View File

@ -0,0 +1,125 @@
/*!
* 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.
*/
mod args;
mod env;
mod modules;
use super::NyashRunner;
use crate::cli::CliGroups;
use std::{path::Path, process};
impl NyashRunner {
/// 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> {
// Guard: skip if child invocation
if std::env::var("NYASH_STAGE1_CLI_CHILD")
.ok()
.as_deref()
== Some("1")
{
return None;
}
// Guard: skip if not enabled
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");
}
// 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());
if !Path::new(&entry).exists() {
eprintln!("[stage1-cli] entry not found: {}", entry);
return Some(97);
}
// Build args
let args_result = args::build_stage1_args(groups);
// Collect modules list
let modules_list = modules::collect_modules_list();
// Prepare command
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 &args_result.args {
cmd.arg(a);
}
// Set environment variables for args
if let Some(json) = args_result.env_script_args.as_ref() {
cmd.env("NYASH_SCRIPT_ARGS_JSON", json);
}
if let Some(src) = args_result.source_env.as_ref() {
cmd.env("STAGE1_SOURCE", src);
}
if let Some(pjson) = args_result.progjson_env.as_ref() {
cmd.env("STAGE1_PROGRAM_JSON", pjson);
}
// Pass source text inline to avoid FileBox dependency when possible.
if args_result.source_env.is_none() {
if let Some(src_path) = groups.input.file.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) = args_result.source_env.as_ref() {
if let Ok(text) = std::fs::read_to_string(src_path) {
cmd.env("STAGE1_SOURCE_TEXT", text);
}
}
// Configure environment
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
crate::cli_v!(
"[stage1-cli] delegating to stub: {} -- {}",
entry,
args_result.args.join(" ")
);
// Execute
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))
}
}

View File

@ -0,0 +1,73 @@
/*!
* Stage-1 CLI bridge - modules list collector
*
* Parses nyash.toml [modules] section and formats module mappings
* for Stage-1 CLI environment variables.
*/
use std::collections::HashSet;
use std::fs;
use std::path::PathBuf;
/// Collect modules list from nyash.toml [modules] section
///
/// Returns a "|||"-separated list of "key=value" entries for HAKO_STAGEB_MODULES_LIST.
/// Includes well-known aliases required by Stage-1 CLI if absent in nyash.toml.
pub(super) fn collect_modules_list() -> Option<String> {
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("|||"))
}
}