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:
nyash-codex
2025-11-21 06:22:02 +09:00
parent 6865f4acfa
commit 51359574d9
7 changed files with 2278 additions and 0 deletions

View File

@ -0,0 +1,365 @@
// stage1_cli.hako — Stage1 self-host CLI skeleton骨組みのみ
//
// 役割: StageB → Stage1 UsingResolver → MirBuilder を経由する self-host CLI の
// 入口シグネチャを固定するだけの箱。Phase 25.1A-3 で最低限の中身を実装。
// トグル既定OFF:
// - NYASH_USE_STAGE1_CLI=1 : Rust 側がこの CLI を呼ぶときに使用
// - STAGE1_EMIT_PROGRAM_JSON=1 : source → Program(JSON v0) を emit
// - STAGE1_EMIT_MIR_JSON=1 : Program(JSON v0) → MIR(JSON) を emit
using lang.compiler.build.build_box as BuildBox
using lang.compiler.entry.using_resolver_box as Stage1UsingResolverBox
using lang.mir.builder.MirBuilderBox
using selfhost.shared.host_bridge.codegen_bridge as CodegenBridgeBox
static box Stage1Cli {
// Source(.hako) → Program(JSON v0)
method emit_program_json(source) {
local tag = "[stage1-cli] emit program-json"
print("[stage1-cli/debug] emit_program_json ENTRY, source=" + ("" + source))
local src = env.get("STAGE1_SOURCE_TEXT")
if src == null {
if source == null || ("" + source) == "" {
print(tag + ": source path is required")
return null
}
src = me._read_file(tag, "" + source)
if env.get("STAGE1_CLI_DEBUG") == "1" {
local status = "null"
if src != null { status = "non-null (length=" + ("" + src.length()) + ")" }
print("[stage1-cli/debug] emit_program_json: _read_file returned, src=" + status)
}
} else if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] emit_program_json: STAGE1_SOURCE_TEXT provided (length=" + ("" + src.length()) + ")")
}
if src == null { return null }
// Stage1 UsingResolver: prefix concat is no-op when HAKO_STAGEB_APPLY_USINGS=0
local prefix = Stage1UsingResolverBox.resolve_for_source(src)
if prefix == null { prefix = "" }
local merged = prefix + src
print("[stage1-cli/debug] emit_program_json: calling BuildBox.emit_program_json_v0")
local prog = BuildBox.emit_program_json_v0(merged, null)
{
local status = "null"
if prog != null { status = "non-null" }
print("[stage1-cli/debug] emit_program_json: BuildBox.emit_program_json_v0 returned, prog=" + status)
}
if prog == null {
print(tag + ": BuildBox returned null")
return null
}
local ps = "" + prog
print("[stage1-cli/debug] emit_program_json: stringified prog, length=" + ("" + ps.length()))
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print(tag + ": unexpected Program(JSON) output (missing version/kind)")
return null
}
print("[stage1-cli/debug] emit_program_json: validation passed, returning Program JSON")
return ps
}
// Program(JSON v0) → MIR(JSON)
method emit_mir_json(program_json) {
local tag = "[stage1-cli] emit mir-json"
if program_json == null {
print(tag + ": program_json is null")
return null
}
local mir = MirBuilderBox.emit_from_program_json_v0(program_json, null)
if mir == null {
print(tag + ": MirBuilderBox returned null (enable HAKO_MIR_BUILDER_DELEGATE=1 ?)")
return null
}
return "" + mir
}
// Run Program(JSON v0) on selected backend (vm|llvm|pyvm)
// - vm/pyvm: convert to MIR(JSON) and print to stdout実行 path は未配線)
// - llvm : emit object via env.codegen; TODO: link+exec in後続フェーズ
method run_program_json(program_json, backend) {
local tag = "[stage1-cli] run program-json"
if program_json == null {
print(tag + ": program_json is null")
return 98
}
if backend == null { backend = "vm" }
backend = "" + backend
local mir = me.emit_mir_json(program_json)
if mir == null { return 98 }
if backend == "llvm" {
local obj = CodegenBridgeBox.emit_object(mir)
if obj == null || ("" + obj) == "" {
print(tag + ": env.codegen.emit_object failed")
return 98
}
print("[stage1-cli] llvm object: " + ("" + obj))
return 0
}
// vm / pyvm: 現状は MIR(JSON) を吐いて終了(実行は Stage0 側に後続で橋渡し)
print(mir)
return 0
}
// CLI dispatcher (stub)
method stage1_main(args) {
{
local use_cli = env.get("NYASH_USE_STAGE1_CLI")
if use_cli == null || ("" + use_cli) != "1" {
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] stage1_main: NYASH_USE_STAGE1_CLI not set, returning 97")
}
return 97
}
}
// Prefer env-provided mode/source to avoid argv依存の不定値
local emit_prog = env.get("STAGE1_EMIT_PROGRAM_JSON")
local emit_mir = env.get("STAGE1_EMIT_MIR_JSON")
local backend = env.get("STAGE1_BACKEND"); if backend == null { backend = "vm" }
local source = env.get("STAGE1_SOURCE")
local prog_path = env.get("STAGE1_PROGRAM_JSON")
if emit_prog == "1" {
if source == null || source == "" {
print("[stage1-cli] emit program-json: STAGE1_SOURCE is required")
return 96
}
local ps = me.emit_program_json(source)
if ps == null { return 96 }
print(ps)
return 0
}
if emit_mir == "1" {
local prog_json = null
if prog_path != null && prog_path != "" {
prog_json = me._read_file("[stage1-cli] emit mir-json", prog_path)
} else {
if source == null || source == "" {
print("[stage1-cli] emit mir-json: STAGE1_SOURCE or STAGE1_PROGRAM_JSON is required")
return 96
}
prog_json = me.emit_program_json(source)
}
if prog_json == null { return 96 }
local mir = me.emit_mir_json(prog_json)
if mir == null { return 96 }
print(mir)
return 0
}
// Default: run path (env-provided backend/source only)
if source == null || source == "" {
print("[stage1-cli] run: source path is required (set STAGE1_SOURCE)")
return 96
}
local prog_json = me.emit_program_json(source)
if prog_json == null { return 96 }
return me.run_program_json(prog_json, backend)
}
// hakorune emit {program-json|mir-json} ...
method _cmd_emit(args){
local argc = 0; if args != null { argc = args.size() }
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit: argc=" + ("" + argc))
}
if argc < 2 {
print("[stage1-cli] emit: usage: emit program-json <source.hako> | emit mir-json [--from-program-json <file>] <source.hako>")
return 96
}
local sub = "" + args.get(1)
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit: sub=" + sub)
}
if sub == "program-json" { return me._cmd_emit_program_json(args) }
if sub == "mir-json" { return me._cmd_emit_mir_json(args) }
print("[stage1-cli] emit: unknown subcommand: " + sub)
return 96
}
method _cmd_emit_program_json(args){
local argc = 0; if args != null { argc = args.size() }
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit_program_json: argc=" + ("" + argc))
}
if argc < 3 {
print("[stage1-cli] emit program-json: usage: emit program-json <source.hako>")
return 96
}
local path = "" + args.get(2)
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit_program_json: calling emit_program_json with path=" + path)
}
local ps = me.emit_program_json(path)
if env.get("STAGE1_CLI_DEBUG") == "1" {
local status = "null"
if ps != null { status = "non-null" }
print("[stage1-cli/debug] _cmd_emit_program_json: emit_program_json returned, ps=" + status)
}
if ps == null { return 96 }
print(ps)
return 0
}
method _cmd_emit_mir_json(args){
local argc = 0; if args != null { argc = args.size() }
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit_mir_json: argc=" + ("" + argc))
}
local prog_path = null
local source_path = null
local i = 2
loop(i < argc) {
local arg = "" + args.get(i)
if arg == "--from-program-json" {
if i + 1 >= argc {
print("[stage1-cli] emit mir-json: --from-program-json requires a path")
return 96
}
prog_path = "" + args.get(i + 1)
i = i + 2
} else {
if source_path == null {
source_path = arg
i = i + 1
} else {
print("[stage1-cli] emit mir-json: unexpected extra argument: " + arg)
return 96
}
}
}
if env.get("STAGE1_CLI_DEBUG") == "1" {
print("[stage1-cli/debug] _cmd_emit_mir_json: source_path=" + source_path + " prog_path=" + prog_path)
}
if prog_path != null && prog_path != "" && source_path != null && source_path != "" {
print("[stage1-cli] emit mir-json: specify either --from-program-json or <source.hako>")
return 96
}
if (prog_path == null || prog_path == "") && (source_path == null || source_path == "") {
print("[stage1-cli] emit mir-json: require --from-program-json <file> or <source.hako>")
return 96
}
local prog_json = null
if prog_path != null && prog_path != "" {
prog_json = me._read_file("[stage1-cli] emit mir-json", prog_path)
} else {
prog_json = me.emit_program_json(source_path)
}
if prog_json == null { return 96 }
local mir = me.emit_mir_json(prog_json)
if env.get("STAGE1_CLI_DEBUG") == "1" {
local status = "null"
if mir != null { status = "non-null" }
print("[stage1-cli/debug] _cmd_emit_mir_json: emit_mir_json returned, mir=" + status)
}
if mir == null { return 96 }
print(mir)
return 0
}
// hakorune run --backend <b> <source.hako> [-- args...]
method _cmd_run(args){
local argc = 0; if args != null { argc = args.size() }
local backend = "vm"
local source_path = null
local i = 1
loop(i < argc) {
local arg = "" + args.get(i)
if arg == "--backend" {
if i + 1 >= argc {
print("[stage1-cli] run: --backend requires a value (vm|llvm|pyvm)")
return 96
}
backend = "" + args.get(i + 1)
i = i + 2
} else {
source_path = arg
i = i + 1
break
}
}
if source_path == null || source_path == "" {
print("[stage1-cli] run: source path is required")
return 96
}
// Remaining args after source are preserved as NYASH_SCRIPT_ARGS_JSON when unset
{
local extras = new ArrayBox()
loop(i < argc) {
extras.push("" + args.get(i))
i = i + 1
}
if extras.length() > 0 {
// Build a minimal JSON array string; avoid env.set unless already available
local json = "["
local j = 0; loop(j < extras.length()) {
if j > 0 { json = json + "," }
local s = extras.get(j)
json = json + "\"" + me._escape_json_string("" + s) + "\""
j = j + 1
}
json = json + "]"
// env.set may be unavailable on some runtimes; best-effort
env.set("NYASH_SCRIPT_ARGS_JSON", json)
}
}
local prog_json = me.emit_program_json(source_path)
if prog_json == null { return 96 }
return me.run_program_json(prog_json, backend)
}
method _read_file(tag, path) {
if path == null || path == "" {
print(tag + ": source path is required")
return null
}
local fb = new FileBox()
local ok = fb.open(path, "r")
if ok != 1 {
print(tag + ": failed to open " + path)
return null
}
local content = fb.read()
fb.close()
if content == null || content == "" {
print(tag + ": source is empty")
return null
}
return "" + content
}
method _escape_json_string(s) {
local out = ""
if s == null { return out }
local i = 0
local n = s.length()
loop(i < n) {
local ch = s.substring(i, i + 1)
if ch == "\\" { out = out + "\\\\" }
else if ch == "\"" { out = out + "\\\"" }
else if ch == "\n" { out = out + "\\n" }
else if ch == "\r" { out = out + "\\r" }
else if ch == "\t" { out = out + "\\t" }
else { out = out + ch }
i = i + 1
}
return out
}
}
static box Stage1CliMain { main(args) { return Stage1Cli.stage1_main(args) } }