Phase 25.1a: selfhost builder hotfix (fn rename, docs)
This commit is contained in:
@ -1,28 +1,44 @@
|
||||
# Runner Facade (Script‑First) — Phase 20.10
|
||||
# Runner Facade / Stage1 CLI — Runner Layer Guide
|
||||
|
||||
Responsibility
|
||||
- Provide a thin, opt‑in runner facade in Hakorune (script) to orchestrate entry selection and pre/post hooks.
|
||||
- Delegates actual execution to existing backends (Rust VM / LLVM). No behavior change by default.
|
||||
## Responsibility
|
||||
- Provide script-side orchestration primitives for execution:
|
||||
- Runner facade (`runner_facade.hako`) for entry selection and pre/post hooks.
|
||||
- Stage1 CLI launcher (`launcher.hako`) for top-level command dispatch.
|
||||
- Delegate actual execution to existing backends(Rust VM / LLVM / ny-llvmc)。既定挙動は変えない。
|
||||
|
||||
Gate
|
||||
- Enable with `HAKO_SCRIPT_RUNNER=1` (default OFF). In 20.10 this runner is not wired by default; wiring will be added behind the gate.
|
||||
## Files
|
||||
|
||||
Contracts (draft)
|
||||
- Entry: `Runner.run(entry: string, args: array<string>) -> i64`
|
||||
- Pre‑hooks: validate entry, shape args, emit diagnostics (short tokens) on failure.
|
||||
- Post‑hooks: normalize result (e.g., quiet result mode), optional metrics/logging.
|
||||
- `runner_facade.hako`
|
||||
- Contract(draft):
|
||||
- Entry: `Runner.run(entry: string, args: array<string>) -> i64`
|
||||
- Gate: `HAKO_SCRIPT_RUNNER=1`(default OFF)。
|
||||
- Role:
|
||||
- Script-first runner facade(Phase 20.10)。
|
||||
- Pre-hooks: validate entry/args, emit short diagnostics。
|
||||
- Post-hooks: normalize result / metrics(将来)。
|
||||
- Notes:
|
||||
- Keep this layer pure; platform I/O は C-ABI 側に委譲。
|
||||
- Fail-Fast: invalid entry/args は非0で即終了。
|
||||
- Short diagnostics:
|
||||
- Success: `[script-runner] invoke`
|
||||
- Failure: `[script-runner] invoke: FAIL`
|
||||
|
||||
Notes
|
||||
- Keep this layer pure and free of platform I/O. Defer I/O to C‑ABI utilities (`hako_*`).
|
||||
- Fail‑Fast: invalid entry/args → emit short diagnostics and return non‑zero.
|
||||
- Box‑First: add adapters for boundary objects (args, env) instead of sprinkling conditions.
|
||||
- `launcher.hako`
|
||||
- Contract(draft):
|
||||
- Entry: `Main.main(args: array<string>) -> i64`
|
||||
- Role: Stage1 hakorune CLI のトップレベル dispatcher。
|
||||
- コマンド: `run` / `build` / `emit` / `check`(詳細は docs/development/runtime/cli-hakorune-stage1.md)。
|
||||
- Current status(Phase 25.1):
|
||||
- 構造のみ実装(`HakoCli` box にコマンド別のメソッドを定義)。
|
||||
- 各コマンドはまだプレースホルダで、`"[hakorune] <cmd>: not implemented yet"` を出力して終了コード 90–93 を返す。
|
||||
- 実際のパイプライン(Stage‑B / MirBuilder / AotPrep / ny-llvmc など)への接続は後続フェーズで段階的に実装する。
|
||||
- Design reference:
|
||||
- `docs/development/runtime/cli-hakorune-stage1.md` を Stage1 CLI の仕様 SSOT として参照すること。
|
||||
|
||||
Short Diagnostics & Observability
|
||||
- Pre‑invoke emits stable one‑liners for smokes:
|
||||
- Success: `[script-runner] invoke` (stdout)
|
||||
- Failure (dev injection or panic): `[script-runner] invoke: FAIL` (stdout)
|
||||
- The runner wiring prints a gate trace to stderr: `[script-runner] gate=on (facade)`.
|
||||
|
||||
Dev‑only toggle (TTL: Phase 20.10 bring‑up)
|
||||
- `HAKO_SCRIPT_RUNNER_FORCE_FAIL=1` forces the pre‑invoke to emit `[script-runner] invoke: FAIL` without executing the facade program.
|
||||
- Scope: tests/smokes only. Remove after runner wiring stabilizes (documented here as a temporary aid).
|
||||
## Notes
|
||||
- Runner 層は「構造とオーケストレーション専用レイヤ」として扱う。
|
||||
- 言語意味論・最適化ロジックは compiler / opt / AotPrep に留める。
|
||||
- VM/LLVM の実行コアは Rust 側(Stage0 / NyRT)に委譲する。
|
||||
- Fail-Fast 原則:
|
||||
- 未実装コマンドや不正な引数は明示的なメッセージ+非0終了コードで返す。
|
||||
- 暗黙のフォールバックや静かな無視は行わない。
|
||||
|
||||
@ -1,9 +1,441 @@
|
||||
// launcher.hako — Minimal selfhost launcher (AOT target)
|
||||
// Goal: produce lang/bin/hakorune via tools/build_llvm.sh
|
||||
// launcher.hako — Stage1 Hakorune CLI launcher (draft)
|
||||
//
|
||||
// Responsibility
|
||||
// - Provide a thin, script-side CLI entrypoint for Stage1 hakorune.
|
||||
// - Parse top-level command/subcommand and delegate to dedicated handlers.
|
||||
// - Keep implementation fail-fast and minimal; real work is delegated to
|
||||
// runner / compiler / AOT helpers and ultimately Stage0 (Rust) services.
|
||||
//
|
||||
// Notes
|
||||
// - CLI surface is specified in docs/development/runtime/cli-hakorune-stage1.md.
|
||||
// - This file only defines the structural dispatcher; individual commands
|
||||
// are implemented incrementally in later phases.
|
||||
|
||||
static box Main {
|
||||
// No-args main() to avoid runtime Array allocation in AOT
|
||||
main(){
|
||||
return 0;
|
||||
using lang.mir.builder.MirBuilderBox
|
||||
using lang.compiler.build.build_box as BuildBox
|
||||
|
||||
static box HakoCli {
|
||||
// Entry from Main.main(args)
|
||||
method run(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
if argc == 0 {
|
||||
// No command: print minimal usage and fail-fast
|
||||
print("[hakorune] usage: hakorune <command> [options]")
|
||||
return 1
|
||||
}
|
||||
|
||||
@cmd_raw = args.get(0)
|
||||
@cmd = "" + cmd_raw
|
||||
|
||||
if cmd == "run" {
|
||||
return self.cmd_run(args)
|
||||
} else if cmd == "build" {
|
||||
return self.cmd_build(args)
|
||||
} else if cmd == "emit" {
|
||||
return self.cmd_emit(args)
|
||||
} else if cmd == "check" {
|
||||
return self.cmd_check(args)
|
||||
}
|
||||
|
||||
print("[hakorune] unknown command: " + cmd)
|
||||
return 2
|
||||
}
|
||||
|
||||
// hakorune run [options] <entry.hako> [-- script_args...]
|
||||
// - See docs/development/runtime/cli-hakorune-stage1.md for semantics.
|
||||
// - Phase 25.1: placeholder only(構造だけ定義し、後続フェーズで実装)。
|
||||
method cmd_run(args){
|
||||
print("[hakorune] run: not implemented yet")
|
||||
return 90
|
||||
}
|
||||
|
||||
// hakorune build exe [options] <entry.hako>
|
||||
// - High-level wrapper around selfhost_exe_stageb / ny_mir_builder.
|
||||
method cmd_build(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
if argc < 2 {
|
||||
print("[hakorune] build: usage: hakorune build exe <source.hako>")
|
||||
return 91
|
||||
}
|
||||
|
||||
@sub_raw = args.get(1)
|
||||
@sub = "" + sub_raw
|
||||
if sub == "exe" {
|
||||
return self.cmd_build_exe(args)
|
||||
}
|
||||
|
||||
print("[hakorune] build: unknown subcommand: " + sub)
|
||||
return 91
|
||||
}
|
||||
|
||||
// hakorune build exe [-o <out>] [--quiet] <source.hako>
|
||||
// - Pipeline: .hako → Program(JSON v0) → MIR(JSON) → env.codegen.emit_object/link_object → EXE
|
||||
// - Requires env.codegen C-API route to be有効化されていること(NYASH_LLVM_USE_CAPI=1, HAKO_V1_EXTERN_PROVIDER_C_ABI=1 等)。
|
||||
method cmd_build_exe(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
if argc < 3 {
|
||||
print("[hakorune] build exe: usage: hakorune build exe [-o <out>] [--quiet] <source.hako>")
|
||||
return 91
|
||||
}
|
||||
|
||||
@out_path = null
|
||||
@quiet = 0
|
||||
@source_path = null
|
||||
|
||||
@i = 2
|
||||
while i < argc {
|
||||
@arg_raw = args.get(i)
|
||||
@arg = "" + arg_raw
|
||||
if arg == "-o" || arg == "--out" {
|
||||
if i + 1 >= argc {
|
||||
print("[hakorune] build exe: -o/--out requires a path")
|
||||
return 91
|
||||
}
|
||||
@op_raw = args.get(i + 1)
|
||||
out_path = "" + op_raw
|
||||
i = i + 2
|
||||
} else if arg == "--quiet" {
|
||||
quiet = 1
|
||||
i = i + 1
|
||||
} else {
|
||||
if source_path == null {
|
||||
source_path = arg
|
||||
i = i + 1
|
||||
} else {
|
||||
print("[hakorune] build exe: unexpected extra argument: " + arg)
|
||||
return 91
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if source_path == null || source_path == "" {
|
||||
print("[hakorune] build exe: source path is required")
|
||||
return 91
|
||||
}
|
||||
|
||||
// Read Hako source
|
||||
@fb = new FileBox()
|
||||
@ok = fb.open(source_path, "r")
|
||||
if ok != 1 {
|
||||
print("[hakorune] build exe: failed to open source " + source_path)
|
||||
return 91
|
||||
}
|
||||
@src = fb.read()
|
||||
fb.close()
|
||||
if src == null || src == "" {
|
||||
print("[hakorune] build exe: source is empty")
|
||||
return 91
|
||||
}
|
||||
|
||||
// .hako → Program(JSON v0)
|
||||
@prog = BuildBox.emit_program_json_v0(src, null)
|
||||
if prog == null {
|
||||
print("[hakorune] build exe: BuildBox returned null")
|
||||
return 91
|
||||
}
|
||||
@ps = "" + prog
|
||||
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
|
||||
print("[hakorune] build exe: unexpected Program(JSON) output (missing version/kind)")
|
||||
return 91
|
||||
}
|
||||
|
||||
// Program(JSON v0) → MIR(JSON)
|
||||
@mir = MirBuilderBox.emit_from_program_json_v0(ps, null)
|
||||
if mir == null {
|
||||
print("[hakorune] build exe: MirBuilderBox returned null")
|
||||
return 91
|
||||
}
|
||||
@ms = "" + mir
|
||||
|
||||
// MIR(JSON) → object via env.codegen.emit_object
|
||||
@emit_args = new ArrayBox()
|
||||
emit_args.push(ms)
|
||||
@obj = hostbridge.extern_invoke("env.codegen", "emit_object", emit_args)
|
||||
if obj == null || ("" + obj) == "" {
|
||||
print("[hakorune] build exe: env.codegen.emit_object failed")
|
||||
return 91
|
||||
}
|
||||
@obj_path = "" + obj
|
||||
|
||||
// object → EXE via env.codegen.link_object
|
||||
@link_args = new ArrayBox()
|
||||
link_args.push(obj_path)
|
||||
if out_path != null && out_path != "" {
|
||||
link_args.push(out_path)
|
||||
}
|
||||
@exe = hostbridge.extern_invoke("env.codegen", "link_object", link_args)
|
||||
if exe == null || ("" + exe) == "" {
|
||||
print("[hakorune] build exe: env.codegen.link_object failed")
|
||||
return 91
|
||||
}
|
||||
@exe_path = "" + exe
|
||||
|
||||
if quiet != 1 {
|
||||
print("[hakorune] build exe: " + exe_path)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// hakorune emit {program-json|mir-json} ...
|
||||
// - Surface for Stage-B / MirBuilder pipeline.
|
||||
method cmd_emit(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
if argc < 2 {
|
||||
print("[hakorune] emit: usage: hakorune emit mir-json --from-program-json <file>")
|
||||
return 92
|
||||
}
|
||||
|
||||
@sub_raw = args.get(1)
|
||||
@sub = "" + sub_raw
|
||||
if sub == "mir-json" {
|
||||
return self.cmd_emit_mir_json(args)
|
||||
} else if sub == "program-json" {
|
||||
return self.cmd_emit_program_json(args)
|
||||
}
|
||||
|
||||
print("[hakorune] emit: unknown subcommand: " + sub)
|
||||
return 92
|
||||
}
|
||||
|
||||
// hakorune emit program-json <source.hako>
|
||||
// - Compile Hako source to Program(JSON v0) using BuildBox.emit_program_json_v0.
|
||||
// - Output is written to stdout on success.
|
||||
method cmd_emit_program_json(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
if argc < 3 {
|
||||
print("[hakorune] emit program-json: usage: hakorune emit program-json [-o <out>] [--quiet] <source.hako>")
|
||||
return 92
|
||||
}
|
||||
|
||||
@path = null
|
||||
@out_path = null
|
||||
@quiet = 0
|
||||
|
||||
@i = 2
|
||||
while i < argc {
|
||||
@arg_raw = args.get(i)
|
||||
@arg = "" + arg_raw
|
||||
if arg == "-o" || arg == "--out" {
|
||||
if i + 1 >= argc {
|
||||
print("[hakorune] emit program-json: -o/--out requires a path")
|
||||
return 92
|
||||
}
|
||||
@op_raw = args.get(i + 1)
|
||||
out_path = "" + op_raw
|
||||
i = i + 2
|
||||
} else if arg == "--quiet" {
|
||||
quiet = 1
|
||||
i = i + 1
|
||||
} else {
|
||||
if path == null {
|
||||
path = arg
|
||||
i = i + 1
|
||||
} else {
|
||||
print("[hakorune] emit program-json: unexpected extra argument: " + arg)
|
||||
return 92
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if path == null || path == "" {
|
||||
print("[hakorune] emit program-json: source path is required")
|
||||
return 92
|
||||
}
|
||||
|
||||
// Read Hako source from file
|
||||
@fb = new FileBox()
|
||||
@ok = fb.open(path, "r")
|
||||
if ok != 1 {
|
||||
print("[hakorune] emit program-json: failed to open " + path)
|
||||
return 92
|
||||
}
|
||||
@src = fb.read()
|
||||
fb.close()
|
||||
if src == null || src == "" {
|
||||
print("[hakorune] emit program-json: source is empty")
|
||||
return 92
|
||||
}
|
||||
|
||||
// Compile to Program(JSON v0) via BuildBox
|
||||
@prog = BuildBox.emit_program_json_v0(src, null)
|
||||
if prog == null {
|
||||
print("[hakorune] emit program-json: BuildBox returned null")
|
||||
return 92
|
||||
}
|
||||
|
||||
// Minimal validation: require Program(version 0)
|
||||
@ps = "" + prog
|
||||
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
|
||||
print("[hakorune] emit program-json: unexpected output (missing version/kind)")
|
||||
return 92
|
||||
}
|
||||
|
||||
if out_path != null && out_path != "" {
|
||||
@out_fb = new FileBox()
|
||||
@ok2 = out_fb.open(out_path, "w")
|
||||
if ok2 != 1 {
|
||||
print("[hakorune] emit program-json: failed to open output " + out_path)
|
||||
return 92
|
||||
}
|
||||
out_fb.write(ps)
|
||||
out_fb.close()
|
||||
if quiet != 1 {
|
||||
print("[hakorune] emit program-json: written " + out_path)
|
||||
}
|
||||
} else {
|
||||
print(ps)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// hakorune emit mir-json [--from-program-json <program.json>] [-o <out>] [--quiet] [<source.hako>]
|
||||
// - Program(JSON v0) から MIR(JSON) を生成する経路に加えて、
|
||||
// .hako ソースから直接 Program(JSON v0)→MIR(JSON) まで進める経路もサポートする。
|
||||
method cmd_emit_mir_json(args){
|
||||
@argc = 0
|
||||
if args { argc = args.size() }
|
||||
|
||||
@prog_path = null
|
||||
@source_path = null
|
||||
@out_path = null
|
||||
@quiet = 0
|
||||
@i = 2
|
||||
while i < argc {
|
||||
@arg_raw = args.get(i)
|
||||
@arg = "" + arg_raw
|
||||
if arg == "--from-program-json" {
|
||||
if i + 1 >= argc {
|
||||
print("[hakorune] emit mir-json: --from-program-json requires a path")
|
||||
return 92
|
||||
}
|
||||
@p_raw = args.get(i + 1)
|
||||
prog_path = "" + p_raw
|
||||
i = i + 2
|
||||
} else if arg == "-o" || arg == "--out" {
|
||||
if i + 1 >= argc {
|
||||
print("[hakorune] emit mir-json: -o/--out requires a path")
|
||||
return 92
|
||||
}
|
||||
@op_raw = args.get(i + 1)
|
||||
out_path = "" + op_raw
|
||||
i = i + 2
|
||||
} else if arg == "--quiet" {
|
||||
quiet = 1
|
||||
i = i + 1
|
||||
} else {
|
||||
// Interpret as <source.hako> when not already set
|
||||
if source_path == null {
|
||||
source_path = arg
|
||||
i = i + 1
|
||||
} else {
|
||||
print("[hakorune] emit mir-json: unknown or unsupported flag: " + arg)
|
||||
return 92
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent ambiguous usage
|
||||
if prog_path != null && prog_path != "" && source_path != null && source_path != "" {
|
||||
print("[hakorune] emit mir-json: specify either --from-program-json or <source.hako>, not both")
|
||||
return 92
|
||||
}
|
||||
|
||||
@prog_json = null
|
||||
|
||||
if prog_path != null && prog_path != "" {
|
||||
// Read Program(JSON v0) from file
|
||||
@fb = new FileBox()
|
||||
@ok = fb.open(prog_path, "r")
|
||||
if ok != 1 {
|
||||
print("[hakorune] emit mir-json: failed to open " + prog_path)
|
||||
return 92
|
||||
}
|
||||
@p = fb.read()
|
||||
fb.close()
|
||||
if p == null || p == "" {
|
||||
print("[hakorune] emit mir-json: program JSON is empty")
|
||||
return 92
|
||||
}
|
||||
prog_json = p
|
||||
} else {
|
||||
// Expect .hako source path
|
||||
if source_path == null || source_path == "" {
|
||||
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>")
|
||||
return 92
|
||||
}
|
||||
@fb2 = new FileBox()
|
||||
@ok2 = fb2.open(source_path, "r")
|
||||
if ok2 != 1 {
|
||||
print("[hakorune] emit mir-json: failed to open source " + source_path)
|
||||
return 92
|
||||
}
|
||||
@src = fb2.read()
|
||||
fb2.close()
|
||||
if src == null || src == "" {
|
||||
print("[hakorune] emit mir-json: source is empty")
|
||||
return 92
|
||||
}
|
||||
// Compile to Program(JSON v0) via BuildBox
|
||||
@prog = BuildBox.emit_program_json_v0(src, null)
|
||||
if prog == null {
|
||||
print("[hakorune] emit mir-json: BuildBox returned null")
|
||||
return 92
|
||||
}
|
||||
@ps = "" + prog
|
||||
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
|
||||
print("[hakorune] emit mir-json: unexpected Program(JSON) output (missing version/kind)")
|
||||
return 92
|
||||
}
|
||||
prog_json = ps
|
||||
}
|
||||
|
||||
// Convert Program(JSON v0) → MIR(JSON) via MirBuilderBox
|
||||
@mir = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
|
||||
if mir == null {
|
||||
print("[hakorune] emit mir-json: MirBuilderBox returned null")
|
||||
return 92
|
||||
}
|
||||
|
||||
// Success: emit MIR(JSON) to stdout or file
|
||||
@ms = "" + mir
|
||||
if out_path != null && out_path != "" {
|
||||
@out_fb = new FileBox()
|
||||
@ok3 = out_fb.open(out_path, "w")
|
||||
if ok3 != 1 {
|
||||
print("[hakorune] emit mir-json: failed to open output " + out_path)
|
||||
return 92
|
||||
}
|
||||
out_fb.write(ms)
|
||||
out_fb.close()
|
||||
if quiet != 1 {
|
||||
print("[hakorune] emit mir-json: written " + out_path)
|
||||
}
|
||||
} else {
|
||||
print(ms)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// hakorune check <entry.hako>
|
||||
// - Reserved for future static checks (syntax/using/subset).
|
||||
method cmd_check(args){
|
||||
print("[hakorune] check: not implemented yet")
|
||||
return 93
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
// Main entrypoint for Stage1 hakorune CLI.
|
||||
// args: raw argv array (excluding executable name).
|
||||
main(args){
|
||||
@cli = new HakoCli()
|
||||
return cli.run(args)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user