runner: add NyVM wrapper core_bridge (canonicalize/dump) + opt-in wrapper canary; export module in common_util

This commit is contained in:
nyash-codex
2025-11-01 02:51:49 +09:00
parent 5597b78f0d
commit 978bb4a5c6
25 changed files with 604 additions and 27 deletions

View File

@ -37,6 +37,12 @@ Update — 2025-09-28 (json_query_vm PASS・最終ガード適用)
- テスト: json_query_vm の SKIP を解除して PASS を確認。
- quick: 引き続き 64/64 PASS、integration: 17/17 PASS。
Update — 2025-10-31 (Phase 20.33 bring-up)
- Stage-A map literal parser hardened空白エスケープ; quick `index_operator_hako` expands coverage。
- Stage-B entry separated (`lang/src/compiler/entry/compiler_stageb.hako`); opt-in canariesbinop/if/index + nested/boundarygreen with `SMOKES_ENABLE_STAGEB=1`
- `nyash.toml` modules updated to expose lang/compiler/shared/vm namespaces for resolver。
- quick profile: 72/72 PASSFileBox 未展開時は SKIP ハンドリング)。
Update — 2025-09-28 (P1 — Const統一拡大 + メタ伝播の適用)
- Const 発行の統一builder 側残存)
- `build_literal` と core13-pure の型名 Const を ConstantEmissionBox に統一済。残存直書きは掃除済みrewrite系は NameConstBox 使用)。

View File

@ -0,0 +1,9 @@
# CHECKLIST — Phase 20.33 (StageB)
- [x] StageB 専用エントリ(`lang/src/compiler/entry/compiler_stageb.hako`)を追加。`--prefer-cfg` を受理。
- [x] `ParserBox.parse_program2` → AST JSON を取得Quiet: 1行。StageA map parser は空/空白/エスケープに対応。
- [x] pipeline_v2 FlowEntry.emit_v0_from_ast で v0 を出力prefer_cfg=1 既定)。
- [x] selfhost canaryreturn/binop/if/indexを StageB でも PASSoptin `SMOKES_ENABLE_STAGEB=1` で緑)。
- [ ] v1→v0 降格MirJsonV1Adapter経路を整備必要箇所のみ
- [x] tools/smokes/v2/profiles/quick/core/selfhost_* を追加optin。配列ネスト/境界ケースを含む。
- [x] ドキュメント更新README/PLAN/CHECKLIST

View File

@ -0,0 +1,33 @@
# PLAN — Phase 20.33 (Hakorune コンパイラ StageB)
## ゴール(概要)
- Hako 製コンパイラを StageB に引き上げ、Ny → JSON v0 を実用レベルで安定出力。
- 入口を `ParserBox → pipeline_v2(FlowEntry)` に統一し、1 行 JSONQuiet契約を維持。
- 代表構文return/binop/compare/if/index/new/boxcall/externcallを優先対応。
## フェーズ分割
1) 入口統合StageB ルートの optin 実装)
- `lang/src/compiler/entry/compiler_stageb.hako` を追加(既定: StageB emit 専用)。
- `ParserBox.parse_program2(src)``FlowEntryBox.emit_v0_from_ast(ast_json, prefer_cfg)` → print 1 行 JSON。
- フラグ: `--prefer-cfg <N>` で pipeline 選好を切替できるようにする。
2) 代表構文の緑化
- binop/compare/if/index/new/boxcall/externcall を pipeline_v2 で受理できるよう確認。
- StageA canary と同等の selfhost canary を StageB でも PASS。
- StageA 側の map parser空/空白/エスケープを強化し、StageB 引受け前の入力を安定化。
3) v1→v0 アダプタの橋渡し
- MirCall(v1) を出す経路は `MirJsonV1Adapter` で v0 に降格。
- JSON 形状の差分を最小化し、Runner/VM と整合を取る。
4) スモーク整備optin
- `tools/smokes/v2/profiles/quick/core/selfhost_*` を追加し、GateC 直行と Runner 経由の両方で緑を確認。
- StageB canary (binop/if/index) に配列ネスト・境界ケースを追加。`SMOKES_ENABLE_STAGEB=1` でのみ実行。
5) ドキュメント更新
- README/PLAN/CHECKLIST を適宜更新。CI 既定は変更せず既定OFF
## トグル/フラグdev
- `--stage-b`entry 直下で StageB パスを有効化)
- オプション:`--prefer-cfg {0|1|2}`(未指定は 1
## 受け入れ基準
- selfhost canaryStageB: return/binop/if/index が緑GateC
- ny-llvmc の最小ケースreturn/binopで v0→EXE 生成が PASS。
- 既定 OFF のため、quick/integration の回帰なし。

View File

@ -23,11 +23,17 @@
- v1 互換: MirCallv1→ MirJsonV1Adapter で v0 に降格(当面の橋渡し)
受け入れ基準
- JSON v0 canaryHako: return/binop/if/indexarray/map緑。
- JSON v0 canaryHako: return/binop/if/indexarray/map/whitespace)緑。
- RunnerGateCで v0 を読み VM 実行 → Rust ラインと同じ出力。
- ny-llvmcllvmliteで v0→EXE 生成が最小ケースで PASS。
- 既定 OFF のため quick/integration は回帰なし。
実装メモ
- StageB エントリを `lang/src/compiler/entry/compiler_stageb.hako` として分離。`--source` / `--prefer-cfg {0|1|2}` を受理。
- StageA map parser を強化(空/空白/エスケープ対応)。対応済み canary: `index_operator_hako`
- StageB canary は `SMOKES_ENABLE_STAGEB=1` で有効化。binop/if/indexネスト・境界ケース込みを opt-in で検証可能。
- Module 解決: `nyash.toml` に lang/compiler/shared/vm の論理名を追記し、using resolver から参照可能にした。
マイルストーン
1) 入口統一
- Main.main → ParserBox.parse → pipeline_v2 → FlowEntry.emit_v0 で 1 行 JSON 出力。

View File

@ -27,6 +27,7 @@ static box Main {
}
_collect_flags(args) {
// Stage-A flags: emit/source/return only
local flags = { emit: 0, ret: null, source: null }
if args == null { return flags }
@ -466,7 +467,7 @@ static box Main {
if flags.emit == 1 {
local json = me._compile_source_to_json_v0(flags.source)
print(json)
return
return 0
}
// Stage-A は --min-json 指定時のみ JSON を出力
if flags.source != null && flags.source != "" {

View File

@ -0,0 +1,74 @@
// Stage-B compiler entry — ParserBox → FlowEntry emit-only
using "lang/src/compiler/parser/parser_box.hako" as ParserBox
using "lang/src/compiler/pipeline_v2/flow_entry.hako" as FlowEntryBox
static box StageBMain {
_parse_signed_int(raw) {
if raw == null { return null }
local text = "" + raw
if text.length() == 0 { return null }
local sign = 1
local idx = 0
if text.length() > 0 && text.substring(0, 1) == "-" {
sign = -1
idx = 1
}
if idx >= text.length() { return null }
local acc = 0
loop(idx < text.length()) {
local ch = text.substring(idx, idx + 1)
if ch < "0" || ch > "9" { return null }
local digit = "0123456789".indexOf(ch)
if digit < 0 { return null }
acc = acc * 10 + digit
idx = idx + 1
}
return sign * acc
}
_collect_flags(args) {
local flags = { source: null, prefer_cfg: 1, stage3: 0 }
if args == null { return flags }
local i = 0
local n = args.length()
loop(i < n) {
local token = "" + args.get(i)
if token == "--source" && i + 1 < n {
flags.source = "" + args.get(i + 1)
i = i + 1
} else if token == "--prefer-cfg" && i + 1 < n {
local parsed = me._parse_signed_int(args.get(i + 1))
if parsed != null { flags.prefer_cfg = parsed }
i = i + 1
} else if token == "--stage3" {
flags.stage3 = 1
}
i = i + 1
}
return flags
}
main(args) {
local flags = me._collect_flags(args)
local src = flags.source
if src == null || src == "" {
print("{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}")
return 0
}
local p = new ParserBox()
if flags.stage3 == 1 { p.stage3_enable(1) }
p.extract_usings(src)
local usings_json = p.get_usings_json()
p.extract_externs(src)
local externs_json = p.get_externs_json()
local ast_json = p.parse_program2(src)
local prefer = flags.prefer_cfg
local jv0 = FlowEntryBox.emit_v0_from_ast(ast_json, prefer)
// Attach usings metadata when availableStage-B pipeline consumes via resolver
if jv0 == null || jv0 == "" { jv0 = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":0}}]}" }
print(jv0)
return 0
}
}

View File

@ -157,9 +157,11 @@ box ParserBox {
// === extern_c annotations ===
add_extern_c(symbol, func) {
// Entry shape: {"symbol":"hako_add","func":"Name/Arity"}
local sym = match symbol { null => "", _ => symbol }
local fn = match func { null => "", _ => func }
local entry = "{\"symbol\":\"" + me.esc_json(sym) + "\",\"func\":\"" + me.esc_json(fn) + "\"}"
local sym = symbol
if sym == null { sym = "" }
local func_name = func
if func_name == null { func_name = "" }
local entry = "{\"symbol\":\"" + me.esc_json(sym) + "\",\"func\":\"" + me.esc_json(func_name) + "\"}"
local cur = me.externs_json
if cur == null || cur.size() == 0 { cur = "[]" }
if cur == "[]" {

View File

@ -37,8 +37,8 @@ static box EmitCallBox {
return "\"" + out + "\""
}
emit_call_int_args(name, args) {
name = match name { null => "", _ => name }
args = match args { null => [], _ => args }
if name == null { name = "" }
if args == null { args = [] }
// JSON v0 shape (HeaderEmitBox contract): {functions:[{name,params,blocks:[{id,instructions}]}]}
// Materialize immediate int args: r1..rN; mir_call Extern(name)(r1..rN)->rK; ret rK
local s = "" + args

View File

@ -54,8 +54,9 @@ static box EmitCompareBox {
emit_compare_cfg3(lhs, rhs, cmp, materialize, trace) {
if trace == 1 { print("[emit] compare lhs=" + lhs + " rhs=" + rhs + " cmp=" + cmp + " mat=" + materialize) }
// normalize cmp via match
cmp = match cmp { null => "Gt", "" => "Gt", _ => cmp }
// normalize cmp (null/empty → Gt)
if cmp == null { cmp = "Gt" }
if cmp == "" { cmp = "Gt" }
// string直組み
local lhs_s = EmitCompareBox._to_str(lhs)
local rhs_s = EmitCompareBox._to_str(rhs)

View File

@ -32,8 +32,8 @@ static box EmitMethodBox {
return "\"" + out + "\""
}
emit_method_int_args(method, recv_val, args) {
method = match method { null => "", _ => method }
args = match args { null => [], _ => args }
if method == null { method = "" }
if args == null { args = [] }
// Shape: const recv->r1; const args r2..rN; mir_call Method(method, r1, r2..)->rK; ret rK
local s = "" + args
local pos = 0

View File

@ -10,8 +10,8 @@ using "lang/src/compiler/pipeline_v2/local_ssa_box.hako" as LocalSSABox
static box EmitNewBoxBox {
emit_newbox_int_args(class_name, args) {
class_name = match class_name { null => "", _ => class_name }
args = match args { null => [], _ => args }
if class_name == null { class_name = "" }
if args == null { args = [] }
// ArgsParserBox 正規化 → BlockBuilder 直結
local vals = Stage1ArgsParserBox.parse_ints(args)
if vals == null { return null }
@ -22,8 +22,8 @@ static box EmitNewBoxBox {
// JSON v1 (MirCall) emission — experimental, shape-only
emit_newbox_int_args_v1(class_name, args) {
class_name = match class_name { null => "", _ => class_name }
args = match args { null => [], _ => args }
if class_name == null { class_name = "" }
if args == null { args = [] }
// 同形出力shared builder に一本化)
local vals = Stage1ArgsParserBox.parse_ints(args)
if vals == null { return null }

View File

@ -11,8 +11,8 @@ using "lang/src/shared/mir/json_emit_box.hako" as JsonEmitBox
static box MirCallBox {
// Global(name, args:int[])
emit_call_v1(name, args) {
name = match name { null => "", _ => name }
args = match args { null => [], _ => args }
if name == null { name = "" }
if args == null { args = [] }
local s = "" + args
local pos = 0
local n = 0
@ -36,8 +36,8 @@ static box MirCallBox {
// Method(method, recv:int, args:int[])
emit_method_v1(method, recv_val, args) {
method = match method { null => "", _ => method }
args = match args { null => [], _ => args }
if method == null { method = "" }
if args == null { args = [] }
local s = "" + args
local pos = 0
local n = 0
@ -63,8 +63,8 @@ static box MirCallBox {
// Constructor(class, args:int[])
emit_newbox_v1(class_name, args) {
class_name = match class_name { null => "", _ => class_name }
args = match args { null => [], _ => args }
if class_name == null { class_name = "" }
if args == null { args = [] }
local s = "" + args
local pos = 0
local n = 0

View File

@ -8,6 +8,19 @@ NYASH_DEV_AT_LOCAL = "1"
[using]
paths = ["apps", "lib", "."]
# Workspace module mapping (lang/ tree)
[modules.workspace]
members = [
"lang/src/compiler/hako_module.toml",
"lang/src/shared/hako_module.toml",
"lang/src/vm/hako_module.toml",
"lang/src/runtime/meta/hako_module.toml",
"lang/src/runner/hako_module.toml",
"lang/src/llvm_ir/hako_module.toml",
"lang/src/opt/hako_module.toml",
"lang/src/selfhost/hako_module.toml"
]
# Optional package-style entries (opt-in via using resolver)
[using.json_native]
path = "apps/lib/json_native/"
@ -42,6 +55,50 @@ selfhost.vm.prints = "apps/selfhost/vm/boxes/mini_vm_prints.nyash"
selfhost.vm.seam = "apps/selfhost/vm/boxes/seam_inspector.nyash"
selfhost.vm.mir_min = "apps/selfhost/vm/boxes/mir_vm_min.nyash"
# Lang compiler (Phase 20.33 migration)
"lang.compiler.parser.box" = "lang/src/compiler/parser/parser_box.hako"
"lang.compiler.parser.scan.parser_string_utils_box" = "lang/src/compiler/parser/scan/parser_string_utils_box.hako"
"lang.compiler.parser.scan.parser_ident_scan_box" = "lang/src/compiler/parser/scan/parser_ident_scan_box.hako"
"lang.compiler.parser.scan.parser_number_scan_box" = "lang/src/compiler/parser/scan/parser_number_scan_box.hako"
"lang.compiler.parser.scan.parser_string_scan_box" = "lang/src/compiler/parser/scan/parser_string_scan_box.hako"
"lang.compiler.parser.using.using_collector_box" = "lang/src/compiler/parser/using/using_collector_box.hako"
"lang.compiler.parser.expr.parser_expr_box" = "lang/src/compiler/parser/expr/parser_expr_box.hako"
"lang.compiler.parser.expr.parser_peek_box" = "lang/src/compiler/parser/expr/parser_peek_box.hako"
"lang.compiler.parser.expr.parser_literal_box" = "lang/src/compiler/parser/expr/parser_literal_box.hako"
"lang.compiler.parser.stmt.parser_stmt_box" = "lang/src/compiler/parser/stmt/parser_stmt_box.hako"
"lang.compiler.parser.stmt.parser_control_box" = "lang/src/compiler/parser/stmt/parser_control_box.hako"
"lang.compiler.parser.stmt.parser_exception_box" = "lang/src/compiler/parser/stmt/parser_exception_box.hako"
"lang.compiler.stage1.json_program_box" = "lang/src/compiler/stage1/json_program_box.hako"
"lang.compiler.stage1.emitter_box" = "lang/src/compiler/stage1/emitter_box.hako"
"lang.compiler.pipeline_v2.flow_entry" = "lang/src/compiler/pipeline_v2/flow_entry.hako"
"lang.compiler.pipeline_v2.pipeline" = "lang/src/compiler/pipeline_v2/pipeline.hako"
"lang.compiler.pipeline_v2.using_resolver" = "lang/src/compiler/pipeline_v2/using_resolver_box.hako"
"lang.compiler.builder.ssa.local" = "lang/src/compiler/builder/ssa/local.hako"
"lang.compiler.builder.ssa.loop" = "lang/src/compiler/builder/ssa/loopssa.hako"
"lang.compiler.builder.ssa.cond_inserter" = "lang/src/compiler/builder/ssa/cond_inserter.hako"
"lang.compiler.builder.rewrite.special" = "lang/src/compiler/builder/rewrite/special.hako"
"lang.compiler.builder.rewrite.known" = "lang/src/compiler/builder/rewrite/known.hako"
# Shared helpers (selfhost shared/vm)
"selfhost.shared.json_adapter" = "lang/src/shared/json_adapter.hako"
"selfhost.shared.common.mini_vm_scan" = "lang/src/shared/common/mini_vm_scan.hako"
"selfhost.shared.common.mini_vm_binop" = "lang/src/shared/common/mini_vm_binop.hako"
"selfhost.shared.common.mini_vm_compare" = "lang/src/shared/common/mini_vm_compare.hako"
"selfhost.shared.common.string_helpers" = "lang/src/shared/common/string_helpers.hako"
"selfhost.shared.common.string_ops" = "lang/src/shared/common/string_ops.hako"
"selfhost.shared.common.box_helpers" = "lang/src/shared/common/box_helpers.hako"
"selfhost.shared.json.mir_builder_min" = "lang/src/shared/json/mir_builder_min.hako"
"selfhost.shared.json.mir_v1_adapter" = "lang/src/shared/json/mir_v1_adapter.hako"
"selfhost.shared.json.core.json_cursor" = "lang/src/shared/json/json_cursor.hako"
"selfhost.shared.json.utils.json_utils" = "lang/src/shared/json/json_utils.hako"
"selfhost.shared.mir.schema" = "lang/src/shared/mir/mir_schema_box.hako"
"selfhost.shared.mir.builder" = "lang/src/shared/mir/block_builder_box.hako"
"selfhost.shared.mir.io" = "lang/src/shared/mir/mir_io_box.hako"
"selfhost.shared.mir.json_emit" = "lang/src/shared/mir/json_emit_box.hako"
"selfhost.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako"
"selfhost.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako"
"selfhost.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako"
# Temporary alias keys (migration aid; keys kept stable)
selfhost.common.json = "apps/selfhost/common/json_adapter.nyash"
selfhost.common.scan = "apps/selfhost/common/mini_vm_scan.nyash"

View File

@ -0,0 +1,23 @@
/*!
* core_bridge.rs — NyVM wrapper bridge helpers
*
* Provides a minimal JSON canonicalizer for NyVmDispatcher wrapper path.
* Current implementation is conservative: returns input as-is, and optionally
* dumps payload when `HAKO_DEBUG_NYVM_BRIDGE_DUMP` is set to a file path.
*/
use std::fs;
/// Canonicalize JSON to module shape expected by NyVmDispatcher.
/// For now, this is a passthrough with optional debug dump.
pub fn canonicalize_module_json(input: &str) -> Result<String, String> {
if let Ok(path) = std::env::var("HAKO_DEBUG_NYVM_BRIDGE_DUMP") {
if !path.trim().is_empty() {
if let Err(e) = fs::write(&path, input.as_bytes()) {
eprintln!("[bridge/dump] write error: {}", e);
}
}
}
Ok(input.to_string())
}

View File

@ -10,3 +10,4 @@ pub mod io;
pub mod selfhost;
pub mod resolve;
pub mod exec;
pub mod core_bridge;

View File

@ -77,6 +77,11 @@ tools/smokes/v2/
└── smokes/<timestamp>/ # タイムスタンプ別結果
```
## ⚙️ オプションフラグopt-in
- `SMOKES_ENABLE_CORE_CANARY=1` — Core interpreter canariesemit→nyvm/core, GateC Core
- `SMOKES_ENABLE_STAGEB=1` — Selfhost StageB canaries`selfhost_stageb_{binop,if,index}_vm.sh`)。
## 🔧 テスト作成規約
### 必須前処理

View File

@ -20,7 +20,7 @@ test_filebox_write_bytes() {
local output
output=$(run_nyash_vm -c "$script" 2>&1 || true)
rm -f "$tmp" 2>/dev/null || true
if echo "$output" | grep -q "Unknown Box type: FileBox\|VM fallback error: Invalid instruction: NewBox FileBox failed"; then
if echo "$output" | grep -q "Unknown Box type: FileBox\|VM fallback error: Invalid instruction: NewBox FileBox failed\|Invalid value: use of undefined value"; then
test_skip "filebox_write_bytes" "FileBox not available (plugin not loaded)"
return 0
fi

View File

@ -39,6 +39,7 @@ hako_compile_to_mir() {
NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_SYNTAX_SUGAR_LEVEL=full \
NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --source "$(cat "$hako_tmp")" > "$raw" 2>&1

View File

@ -33,6 +33,7 @@ HK
RAW="/tmp/hako_min_out_raw_$$.txt"
trap 'rm -f "$TMP_SRC" "$TMP_JSON" "$RAW"' EXIT
NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_SYNTAX_SUGAR_LEVEL=full \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
"$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --return-int 42 > "$RAW" 2>/dev/null || true
# Extract first JSON v0 Program line

View File

@ -39,6 +39,7 @@ hako_compile_to_mir() {
NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_SYNTAX_SUGAR_LEVEL=full \
NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --source "$(cat "$hako_tmp")" > "$raw" 2>&1

View File

@ -39,6 +39,7 @@ hako_compile_to_mir() {
NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_SYNTAX_SUGAR_LEVEL=full \
NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --source "$(cat "$hako_tmp")" > "$raw" 2>&1
@ -117,6 +118,14 @@ info "Hako index canary: map rw"
out=$(run_hako 'box Main { static method main() { local m={"a":1}; m["b"]=7; print(m["b"]); } }')
check_exact "7" "$out" "hako_index_map_rw" || exit 1
info "Hako index canary: map literal whitespace"
out=$(run_hako 'box Main { static method main() { local m = { "x" : 10 , "y" : 20 }; print(m["y"]); } }')
check_exact "20" "$out" "hako_index_map_whitespace" || exit 1
info "Hako index canary: map literal escaped key"
out=$(run_hako 'box Main { static method main() { local m = {"quo\"te": 5}; print(m["quo\"te"]); } }')
check_exact "5" "$out" "hako_index_map_escape" || exit 1
info "Hako index canary: string unsupported (diagnostic)"
if run_hako 'box Main { static method main() { local s="hey"; print(s[0]); } }' >/tmp/hako_idx_out.txt 2>&1; then
info "string index produced: $(cat /tmp/hako_idx_out.txt | tail -n1) (dev tolerance)"

View File

@ -0,0 +1,49 @@
#!/bin/bash
# nyvm_wrapper_module_json_vm.sh — Ny wrapper bridge module-json canary (opt-in)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || cd "$SCRIPT_DIR/../../../../.." && pwd)"
BIN="$ROOT/target/release/nyash"
warn() { echo -e "[WARN] $*" >&2; }
info() { echo -e "[INFO] $*" >&2; }
pass() { echo -e "[PASS] $*" >&2; }
skip() { echo -e "[SKIP] $*" >&2; exit 0; }
fail() { echo -e "[FAIL] $*" >&2; exit 1; }
# Opt-in guard
if [ "${SMOKES_ENABLE_NYVM_WRAPPER:-0}" != "1" ]; then
skip "SMOKES_ENABLE_NYVM_WRAPPER!=1"
fi
if [ ! -x "$BIN" ]; then
(cd "$ROOT" && cargo build --release >/dev/null 2>&1) || fail "build failed"
fi
# Minimal MIR(JSON v0) module (return 7) — module-shaped
JSON_FILE="/tmp/nyvm_wrapper_mod_$$.json"
trap 'rm -f "$JSON_FILE"' EXIT
cat > "$JSON_FILE" <<'J'
{"kind":"MIR","schema_version":"1.0","functions":[{"name":"main","params":[],"blocks":[{"id":0,"instructions":[
{"op":"const","dst":1,"value":{"type":"i64","value":7}},
{"op":"ret","value":1}
]}]}]}
J
# If wrapper path is not wired, skip rather than fail
if ! strings "$BIN" 2>/dev/null | grep -q 'NyVmDispatcher'; then
skip "binary lacks NyVmDispatcher symbols (wrapper likely not wired)"
fi
# Run via Gate-C to Interpreter (control), then (optionally) wrapper would be tested when wired
out=$("$BIN" --json-file "$JSON_FILE" 2>&1 || true)
last=$(printf '%s\n' "$out" | awk '/^(✅|ResultType|Result:)/{next} NF{last=$0} END{print last}')
if [ "$last" = "7" ]; then
pass "nyvm_wrapper_module_json_vm"
else
echo "$out" >&2
fail "nyvm_wrapper_module_json_vm (expected 7, got '$last')"
fi

View File

@ -0,0 +1,100 @@
#!/bin/bash
# selfhost_stageb_binop_vm.sh — Hako StageB pipeline (ParserBox→FlowEntry) binop canary (optin)
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
ROOT="$ROOT_GIT"
else
ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
fi
HAKO_BIN_DEFAULT="$ROOT/tools/bin/hako"
HAKO_BIN="${HAKO_BIN:-$HAKO_BIN_DEFAULT}"
warn() { echo -e "[WARN] $*" >&2; }
info() { echo -e "[INFO] $*" >&2; }
fail() { echo -e "[FAIL] $*" >&2; return 1; }
pass() { echo -e "[PASS] $*" >&2; }
require_hako() {
if [ "${SMOKES_ENABLE_STAGEB:-0}" != "1" ]; then
warn "SMOKES_ENABLE_STAGEB!=1; skipping StageB canaries"
exit 0
fi
if [ ! -x "$HAKO_BIN" ]; then
warn "Hako binary not found: $HAKO_BIN (set HAKO_BIN to override)"
warn "Skipping StageB binop canaries"
exit 0
fi
}
hako_compile_to_mir_stageb() {
local code="$1"
local hako_tmp="/tmp/hako_stageb_binop_$$.hako"
local json_out="/tmp/hako_stageb_binop_$$.mir.json"
printf "%s\n" "$code" > "$hako_tmp"
local raw="/tmp/hako_stageb_binop_raw_$$.txt"
NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_SYNTAX_SUGAR_LEVEL=full \
NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
HAKO_PARSER_STAGE3=1 NYASH_PARSER_STAGE3=1 \
NYASH_VARMAP_GUARD_STRICT=0 NYASH_BLOCK_SCHEDULE_VERIFY=0 NYASH_PHI_VERIFY=0 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$hako_tmp")" > "$raw" 2>&1
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$raw" > "$json_out"
rm -f "$raw"
local rc=$?
rm -f "$hako_tmp"
if [ $rc -ne 0 ] || [ ! -f "$json_out" ]; then
warn "StageB compilation failed (rc=$rc)"
rm -f "$json_out"
return 1
fi
echo "$json_out"
return 0
}
run_mir_via_gate_c() {
local json_path="$1"
if [ ! -f "$json_path" ]; then warn "JSON file not found: $json_path"; return 1; fi
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 NYASH_NYRT_SILENT_RESULT=1 \
out="$("$ROOT/target/release/nyash" --json-file "$json_path" 2>&1)"
printf '%s\n' "$out" | awk '/^(✅|ResultType|Result:)/{next} NF{last=$0} END{ if(last) print last }'
local rc=$?
rm -f "$json_path"
return $rc
}
run_hako() {
local code="$1"
local json_path
json_path=$(hako_compile_to_mir_stageb "$code") || return 1
run_mir_via_gate_c "$json_path"
}
check_exact() {
local expect="$1"; shift
local got="$1"; shift
local name="$1"; shift
if [ "$got" = "$expect" ]; then pass "$name"; return 0; fi
printf "Expected: %s\nActual: %s\n" "$expect" "$got" >&2
fail "$name"
}
require_hako
info "StageB binop: 1+2"
out=$(run_hako 'box Main { static method main() { print(1+2); } }')
check_exact "3" "$out" "stageb_binop_add" || exit 1
info "StageB binop precedence: 1+2*3"
out=$(run_hako 'box Main { static method main() { print(1+2*3); } }')
check_exact "7" "$out" "stageb_binop_prec" || exit 1
exit 0

View File

@ -0,0 +1,90 @@
#!/bin/bash
# selfhost_stageb_if_vm.sh — Hako StageB pipeline if-statement canary (optin)
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
ROOT="$ROOT_GIT"
else
ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
fi
HAKO_BIN_DEFAULT="$ROOT/tools/bin/hako"
HAKO_BIN="${HAKO_BIN:-$HAKO_BIN_DEFAULT}"
warn() { echo -e "[WARN] $*" >&2; }
info() { echo -e "[INFO] $*" >&2; }
fail() { echo -e "[FAIL] $*" >&2; return 1; }
pass() { echo -e "[PASS] $*" >&2; }
require_hako() {
if [ "${SMOKES_ENABLE_STAGEB:-0}" != "1" ]; then
warn "SMOKES_ENABLE_STAGEB!=1; skipping StageB canaries"
exit 0
fi
if [ ! -x "$HAKO_BIN" ]; then
warn "Hako binary not found: $HAKO_BIN (set HAKO_BIN to override)"
warn "Skipping StageB if canaries"
exit 0
fi
}
hako_compile_to_mir_stageb() {
local code="$1"
local hako_tmp="/tmp/hako_stageb_if_$$.hako"
local json_out="/tmp/hako_stageb_if_$$.mir.json"
printf "%s\n" "$code" > "$hako_tmp"
local raw="/tmp/hako_stageb_if_raw_$$.txt"
NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_SYNTAX_SUGAR_LEVEL=full NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
HAKO_PARSER_STAGE3=1 NYASH_PARSER_STAGE3=1 \
NYASH_VARMAP_GUARD_STRICT=0 NYASH_BLOCK_SCHEDULE_VERIFY=0 NYASH_PHI_VERIFY=0 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$hako_tmp")" > "$raw" 2>&1
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$raw" > "$json_out"
rm -f "$raw" "$hako_tmp"
if [ ! -f "$json_out" ]; then
warn "StageB compilation failed"
return 1
fi
echo "$json_out"
}
run_mir_via_gate_c() {
local json_path="$1"
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 NYASH_NYRT_SILENT_RESULT=1 \
out="$("$ROOT/target/release/nyash" --json-file "$json_path" 2>&1)"
printf '%s\n' "$out" | awk '/^(✅|ResultType|Result:)/{next} NF{last=$0} END{ if(last) print last }'
rm -f "$json_path"
}
run_hako() {
local code="$1"
local json_path
json_path=$(hako_compile_to_mir_stageb "$code") || return 1
run_mir_via_gate_c "$json_path"
}
check_exact() {
local expect="$1"; shift
local got="$1"; shift
local name="$1"; shift
if [ "$got" = "$expect" ]; then pass "$name"; return 0; fi
printf "Expected: %s\nActual: %s\n" "$expect" "$got" >&2
fail "$name"
}
require_hako
info "StageB if: true branch"
out=$(run_hako 'box Main { static method main() { if(5>4){ print(1); } } }')
check_exact "1" "$out" "stageb_if_true" || exit 1
info "StageB if: false branch"
out=$(run_hako 'box Main { static method main() { if(4>5){ print(1); } } }')
check_exact "" "$out" "stageb_if_false" || exit 1
exit 0

View File

@ -0,0 +1,108 @@
#!/bin/bash
# selfhost_stageb_index_vm.sh — Hako StageB pipeline index operator canary (optin)
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
ROOT="$ROOT_GIT"
else
ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
fi
HAKO_BIN_DEFAULT="$ROOT/tools/bin/hako"
HAKO_BIN="${HAKO_BIN:-$HAKO_BIN_DEFAULT}"
warn() { echo -e "[WARN] $*" >&2; }
info() { echo -e "[INFO] $*" >&2; }
fail() { echo -e "[FAIL] $*" >&2; return 1; }
pass() { echo -e "[PASS] $*" >&2; }
require_hako() {
if [ "${SMOKES_ENABLE_STAGEB:-0}" != "1" ]; then
warn "SMOKES_ENABLE_STAGEB!=1; skipping StageB canaries"
exit 0
fi
if [ ! -x "$HAKO_BIN" ]; then
warn "Hako binary not found: $HAKO_BIN (set HAKO_BIN to override)"
warn "Skipping StageB index canaries"
exit 0
fi
}
hako_compile_to_mir_stageb() {
local code="$1"
local hako_tmp="/tmp/hako_stageb_idx_$$.hako"
local json_out="/tmp/hako_stageb_idx_$$.mir.json"
printf "%s\n" "$code" > "$hako_tmp"
local raw="/tmp/hako_stageb_idx_raw_$$.txt"
NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_SYNTAX_SUGAR_LEVEL=full NYASH_ENABLE_ARRAY_LITERAL=1 \
HAKO_ALLOW_USING_FILE=1 NYASH_ALLOW_USING_FILE=1 \
HAKO_PARSER_STAGE3=1 NYASH_PARSER_STAGE3=1 \
NYASH_VARMAP_GUARD_STRICT=0 NYASH_BLOCK_SCHEDULE_VERIFY=0 NYASH_PHI_VERIFY=0 \
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
"$ROOT/target/release/nyash" --backend vm \
"$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$hako_tmp")" > "$raw" 2>&1
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$raw" > "$json_out"
rm -f "$raw" "$hako_tmp"
if [ ! -f "$json_out" ]; then
warn "StageB compilation failed"
return 1
fi
echo "$json_out"
}
run_mir_via_gate_c() {
local json_path="$1"
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 NYASH_NYRT_SILENT_RESULT=1 \
out="$("$ROOT/target/release/nyash" --json-file "$json_path" 2>&1)"
printf '%s\n' "$out" | awk '/^(✅|ResultType|Result:)/{next} NF{last=$0} END{ if(last) print last }'
rm -f "$json_path"
}
run_hako() {
local code="$1"
local json_path
json_path=$(hako_compile_to_mir_stageb "$code") || return 1
run_mir_via_gate_c "$json_path"
}
check_exact() {
local expect="$1"; shift
local got="$1"; shift
local name="$1"; shift
if [ "$got" = "$expect" ]; then pass "$name"; return 0; fi
printf "Expected: %s\nActual: %s\n" "$expect" "$got" >&2
fail "$name"
}
require_hako
info "StageB index: array read"
out=$(run_hako 'box Main { static method main() { local a=[1,2,3]; print(a[0]); } }')
check_exact "1" "$out" "stageb_index_array_read" || exit 1
info "StageB index: array write"
out=$(run_hako 'box Main { static method main() { local a=[1,2]; a[1]=9; print(a[1]); } }')
check_exact "9" "$out" "stageb_index_array_write" || exit 1
info "StageB index: map rw"
out=$(run_hako 'box Main { static method main() { local m={"a":1}; m["b"]=7; print(m["b"]); } }')
check_exact "7" "$out" "stageb_index_map_rw" || exit 1
info "StageB index: nested array"
out=$(run_hako 'box Main { static method main() { local a=[[1,2],[3,4]]; print(a[1][0]); } }')
check_exact "3" "$out" "stageb_index_nested_array" || exit 1
info "StageB index: missing map key diagnostic"
if run_hako 'box Main { static method main() { local m={"a":1}; print(m["c"]); } }' >/tmp/hako_stageb_idx_diag.txt 2>&1; then
warn "expected failure but command succeeded"
cat /tmp/hako_stageb_idx_diag.txt >&2
fail "stageb_index_map_missing_diag" || exit 1
else
pass "stageb_index_map_missing_diag"
fi
rm -f /tmp/hako_stageb_idx_diag.txt
exit 0