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:
365
lang/src/runner/stage1_cli.hako
Normal file
365
lang/src/runner/stage1_cli.hako
Normal file
@ -0,0 +1,365 @@
|
||||
// stage1_cli.hako — Stage‑1 self-host CLI skeleton(骨組みのみ)
|
||||
//
|
||||
// 役割: Stage‑B → Stage‑1 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 }
|
||||
|
||||
// Stage‑1 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) } }
|
||||
Reference in New Issue
Block a user