docs(phase-20.33): update Gate-C(Core) status (v1→MIR interpreter), mark parity smokes done; clean up wording\nchore: remove unused bak/ (external backup kept)\nsmokes: add Gate-C v1 file/pipe opt-in canaries; env toggles documented\nrunner: include json_v1_bridge + bridge toggles (singleton/phi) wiring
This commit is contained in:
@ -7,3 +7,20 @@
|
||||
- [x] v1→v0 降格(MirJsonV1Adapter)経路を整備(`--v1-compat` / `selfhost_stageb_v1_compat_vm` opt-in)。
|
||||
- [x] tools/smokes/v2/profiles/quick/core/selfhost_* を追加(opt‑in)。配列ネスト/境界ケースを含む。
|
||||
- [x] ドキュメント更新(README/PLAN/CHECKLIST)。
|
||||
|
||||
-— 未完(本フェーズ内で進める) —
|
||||
- [x] Gate‑C(Core) v1→MIR Interpreter 実行(`HAKO_NYVM_CORE=1`/最小ブリッジ)。
|
||||
- [ ] NyVmDispatcher 直行(別タスク・将来対応)。
|
||||
- [ ] v1→v0 最小ダウングレード(任意・`HAKO_NYVM_V1_DOWNCONVERT=1`)。未対応命令は Fail‑Fast。
|
||||
- [x] Bridge 正規化: `HAKO_BRIDGE_INJECT_SINGLETON` 実装(Array/Map len → Method 化、Fail‑Fast 付き)。
|
||||
- [x] Bridge 正規化: `HAKO_BRIDGE_EARLY_PHI_MATERIALIZE` 実装(φ をブロック先頭へ移動、順序保護)。
|
||||
- [x] Gate‑C file/pipe × Plugins ON/OFF の対称性スモーク(数値出力=終了コード)。
|
||||
- [ ] Stage‑A map リテラルの堅牢化(エスケープ/Unicode/{}/不正形診断)。
|
||||
- [x] スモーク実行権限(core/* に chmod +x 反映)。
|
||||
- [ ] 参照の古い表記整理(apps/selfhost-compiler → lang/src/compiler/* に統一。履歴注記は残す)。
|
||||
- [ ] ランナー子環境ヘルパーの集約(selfhost 経路の ENV を helper へ)。
|
||||
- [ ] Extern SSOT(VM/AOT 共通ローダ、既定OFF)。
|
||||
|
||||
▼ 付記(ワークログ)
|
||||
- 2025‑11‑01: Runner の Gate‑C v1 直行(子プロセスで Hako controller 起動)を配線。ただし Hako 側で `call("…")` 解決に失敗→直行NG。v1→MIR Interpreter(最小ブリッジ const/copy/ret)は動作。
|
||||
- 2025‑11‑01: Bridge トグル(len 変換/φ 先頭化)実装。Gate‑C v1 parity スモーク(file/pipe、plugins ON/OFF)と Stage‑A map 境界スモークを追加(opt‑in)。
|
||||
|
||||
@ -23,6 +23,21 @@
|
||||
5) ドキュメント更新
|
||||
- README/PLAN/CHECKLIST を適宜更新。CI 既定は変更せず(既定OFF)。
|
||||
|
||||
6) Gate‑C(Core) 実行切替(短期緑化・既定OFF)
|
||||
- `HAKO_NYVM_CORE=1` 時のみ有効。
|
||||
- 入力 JSON 判定: `schema_version` を含む v1 は NyVmDispatcher 直行、`version:0` は従来の v0 ブリッジ。
|
||||
- 代替(任意): `HAKO_NYVM_V1_DOWNCONVERT=1` で最小サブセット(const/binop/compare/ret/branch/jump/phi)を v0 へ降格。未対応は Fail‑Fast。
|
||||
|
||||
実装ノート(現時点)
|
||||
- Runner→Ny 側の Gate‑C controller 呼び出しは子プロセスで実装(payload を env 経由で受け渡し、Quiet 環境を注入)。
|
||||
- 既知の問題: Hako controller が `call("…")` 解決に失敗し実行不可。直行は一時的に失敗→v0 ブリッジ側がエラーを出す。
|
||||
- 次の修正: controller 側で include 経由の直接呼び出しに変更するか、runner 側で JSON→Core のインライン実行を採用する(小差分優先)。
|
||||
|
||||
7) Bridge 正規化トグル実装(仕様確定後)
|
||||
- `HAKO_BRIDGE_INJECT_SINGLETON`: Array/Map 系の静的関数に必要な receiver/Singleton 補完を最小差分で注入。
|
||||
- `HAKO_BRIDGE_EARLY_PHI_MATERIALIZE`: φ をブロック先頭へ整列(or 指定形)して use‑before‑def を防止。
|
||||
- いずれも既定OFF、明示トグルでのみ有効。
|
||||
|
||||
## トグル/フラグ(dev)
|
||||
- `--stage-b`(entry 直下で Stage‑B パスを有効化)
|
||||
- オプション:`--prefer-cfg {0|1|2}`(未指定は 1)
|
||||
|
||||
@ -60,3 +60,77 @@
|
||||
- lang/src/compiler/entry/compiler.hako(入口)
|
||||
- lang/src/shared/json/mir_v1_adapter.hako
|
||||
- src/runner/json_v0_bridge/*(Rust 側 v0 ブリッジ)
|
||||
|
||||
---
|
||||
|
||||
不足機能(現状のギャップ)
|
||||
- Gate‑C(Core) v1 経路は実装済み(MIR Interpreter 直行)
|
||||
- 現状: `HAKO_NYVM_CORE=1` 時に v1(JSON) を `json_v1_bridge` で `MirModule` へ変換し、MIR Interpreter で実行(const/binop/compare/branch/jump/phi 対応)。
|
||||
- NyVmDispatcher 直行は別タスクとして扱う(既定OFF・将来対応)。
|
||||
|
||||
- v1→v0 ダウングレード(Runner 側)が未実装(任意)
|
||||
- 代表命令(const/binop/compare/ret/branch/jump/phi)の最小降格を `HAKO_NYVM_V1_DOWNCONVERT=1` で opt‑in。
|
||||
- 未対応命令は Fail‑Fast。Core 直行が有効な場合は直行を優先。
|
||||
|
||||
- Bridge 正規化トグルの中身が未実装
|
||||
- `HAKO_BRIDGE_INJECT_SINGLETON` / `HAKO_BRIDGE_EARLY_PHI_MATERIALIZE` は env のみ存在。実際の補完・整列は未実装。
|
||||
- 仕様確定後に JSON 編集(受信モジュール内の命令配列へ最小差分)を実装する。
|
||||
|
||||
Bridge 正規化 仕様(案/このフェーズで凍結)
|
||||
- 目的: Gate‑C(Wrapper) 経路で v1 形状の JSON を安全に Core/VM で実行可能な形に最小正規化する。
|
||||
- トグル(既定OFF・Fail‑Fast優先)
|
||||
- `HAKO_BRIDGE_INJECT_SINGLETON`(alias `NYASH_BRIDGE_INJECT_SINGLETON`)
|
||||
- 対象: ModuleFunction/Method で receiver が明示されない「静的呼び出し風」の形。
|
||||
- 変換(最小):
|
||||
- 形状: `ModuleFunction { name: "ArrayBox.len" , args:[X] }` → `Method { receiver:X, method:"size" }`
|
||||
- 形状: `ModuleFunction { name: "MapBox.len" , args:[X] }` → `Method { receiver:X, method:"len" }`
|
||||
- 前提: `args` が 1 要素の数値(レジスタID)。それ以外は変換せず Fail。
|
||||
- 変換は命令 JSON の当該オブジェクトのみを書き換え、周囲の命令順や値定義順には触れない。
|
||||
- 未対応: push/pop/get/set の ModuleFunction 形(将来拡張)。
|
||||
- `HAKO_BRIDGE_EARLY_PHI_MATERIALIZE`(alias `NYASH_BRIDGE_EARLY_PHI_MATERIALIZE`)
|
||||
- 対象: ブロック内で φ 命令が先頭以外に出現する形、または φ と非φ が交差している形。
|
||||
- 変換(最小):
|
||||
- 同一ブロック内で φ 命令列を先頭に移動(相対順は保持)。
|
||||
- 非φ 命令列は元順序を保持する(use‑before‑def を避けるための再順序付けは行わない)。
|
||||
- 追加のコピー挿入(edge copy 合成)は行わない。必要なら Fail。
|
||||
- 失敗時: 安定化したエラーメッセージで Fail‑Fast(静かなフォールバック禁止)。
|
||||
|
||||
注: 上記はいずれも「既定OFF」。Runner での v1 受理は 1) 直行(NyVmDispatcher/Core) 2) 最小ダウングレード(v1→v0, 任意)の順で選好し、Bridge 正規化は Wrapper 経路の補助として段階導入する。
|
||||
|
||||
- Gate‑C(file/pipe)× Plugins ON/OFF の対称性検証が未整備
|
||||
- 数値出力とプロセス終了コードの一致(rc=出力数値)の保証スモークを追加する。
|
||||
|
||||
- Stage‑A map リテラルの堅牢化の残り
|
||||
- エスケープや Unicode、空マップ `{}`、不正形(診断)などの追加ケースを補強する。
|
||||
|
||||
- スモーク実行権限の不足
|
||||
- 一部 `tools/smokes/v2/profiles/quick/core/*.sh` が実行不可のまま。chmod +x を反映する。
|
||||
|
||||
- 参照の古い表記が残存
|
||||
- hako.toml のコメントや docs に `apps/selfhost-compiler` の表記が散見。`lang/src/compiler/*` 基準に置換(履歴の文脈が必要な箇所は注記)。
|
||||
|
||||
- ランナー子環境ヘルパーの集約が未完
|
||||
- selfhost 子プロセス向け ENV セットの重複を helper に寄せるタスクが残り。
|
||||
|
||||
- Extern SSOT(VM/AOT 共有ローダ)が未導入(最小)
|
||||
- VM/AOT 双方で参照する単一起点を用意(既定OFF、opt‑in)。
|
||||
|
||||
このフェーズの次アクション(短期)
|
||||
- Gate‑C 実行切替(v1直行/トグル配下)と対称性スモークの追加。
|
||||
- Bridge トグルの実装方針を SPEC 化(Singleton 注入ルール / φ 整列の期待形)。
|
||||
- スモークの権限整備と Stage‑A/Stage‑B の追加境界ケース投入。
|
||||
|
||||
現状ステータス(2025‑11‑01 更新)
|
||||
- Gate‑C v1 経路:
|
||||
- Runner 側で `HAKO_NYVM_CORE=1` / `NYASH_NYVM_CORE=1` を検知すると、v1(JSON) を Rust の `json_v1_bridge` で `MirModule` に変換し、MIR Interpreter で直接実行する経路が完成(const/binop/compare/branch/jump/phi 対応)。
|
||||
- 子プロセス経路は撤退(`call` 依存なし)。
|
||||
- v1→v0 ダウングレード:
|
||||
- `HAKO_NYVM_V1_DOWNCONVERT=1`(alias `NYASH_NYVM_V1_DOWNCONVERT`)で同じコンバータを使用し、v1 JSON を MIR Interpreter へ降格(Fail‑Fast)。
|
||||
- Bridge トグル:
|
||||
- `INJECT_SINGLETON`/`EARLY_PHI_MATERIALIZE` を実装(既定OFF)。Array/Map len 変換と φ 先頭化のみ対応済み。未対応は Fail。
|
||||
- スモーク:
|
||||
- Gate‑C v1 parity(file/pipe × plugins ON/OFF)の opt‑in スモーク(`SMOKES_ENABLE_GATE_C_V1=1`)。
|
||||
- Stage‑A map 境界(エスケープ/Unicode/空)スモーク追加(診断含め PASS)。
|
||||
|
||||
直近のフォロー
|
||||
- 変換器は最小命令セットのみ対応(mir_call/extern 等は Fail)。対応範囲を広げる場合は json_v1_bridge に実装を追加。
|
||||
|
||||
@ -2,14 +2,26 @@
|
||||
// Responsibility: Provide a thin, stable entry to route MIR(JSON v0)
|
||||
// through the Ny/Core dispatcher when a wrapper route is needed.
|
||||
|
||||
include "lang/src/vm/core/dispatcher.hako"
|
||||
using "lang/src/vm/core/dispatcher.hako" as NyVmDispatcher
|
||||
|
||||
static box GateCController {
|
||||
// route_json/1: String(JSON v0) -> String(last line)
|
||||
// Contract: returns a printable string (numeric or tag). No side effects.
|
||||
route_json(j) {
|
||||
// Delegate to the Core dispatcher runner
|
||||
return call("NyVmDispatcher.run/1", j)
|
||||
return NyVmDispatcher.run(j)
|
||||
}
|
||||
}
|
||||
|
||||
static box GateCControllerMain {
|
||||
main(args) {
|
||||
if args == null || args.length() == 0 {
|
||||
print("[gate_c/core] payload missing")
|
||||
return 1
|
||||
}
|
||||
local payload = "" + args.get(0)
|
||||
local res = GateCController.route_json(payload)
|
||||
if res != null { print(res) }
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -548,3 +548,9 @@ pub fn nyvm_bridge_early_phi_materialize() -> bool {
|
||||
.or_else(|| env_flag("NYASH_BRIDGE_EARLY_PHI_MATERIALIZE"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn nyvm_v1_downconvert() -> bool {
|
||||
env_flag("HAKO_NYVM_V1_DOWNCONVERT")
|
||||
.or_else(|| env_flag("NYASH_NYVM_V1_DOWNCONVERT"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
355
src/runner/json_v1_bridge.rs
Normal file
355
src/runner/json_v1_bridge.rs
Normal file
@ -0,0 +1,355 @@
|
||||
use crate::mir::{
|
||||
function::{FunctionSignature, MirFunction, MirModule},
|
||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
/// Try to parse MIR JSON v1 schema into a MIR module.
|
||||
/// Returns Ok(None) when the input is not v1 (schema_version missing).
|
||||
/// Currently supports a minimal subset required for Gate-C parity tests:
|
||||
/// - const (integer)
|
||||
/// - copy
|
||||
/// - ret
|
||||
#[allow(dead_code)]
|
||||
pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
||||
let value: Value = match serde_json::from_str(json) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(format!("invalid JSON: {}", e)),
|
||||
};
|
||||
|
||||
let schema = match value.get("schema_version") {
|
||||
Some(Value::String(s)) => s.clone(),
|
||||
Some(other) => {
|
||||
return Err(format!(
|
||||
"expected schema_version string, found {}",
|
||||
other
|
||||
))
|
||||
}
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if !schema.starts_with('1') {
|
||||
return Err(format!(
|
||||
"unsupported schema_version '{}': expected 1.x",
|
||||
schema
|
||||
));
|
||||
}
|
||||
|
||||
let functions = value
|
||||
.get("functions")
|
||||
.and_then(|f| f.as_array())
|
||||
.ok_or_else(|| "v1 JSON missing functions array".to_string())?;
|
||||
|
||||
let mut module = MirModule::new("ny_json_v1".to_string());
|
||||
|
||||
for func in functions {
|
||||
let func_name = func
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("main")
|
||||
.to_string();
|
||||
|
||||
let blocks = func
|
||||
.get("blocks")
|
||||
.and_then(|b| b.as_array())
|
||||
.ok_or_else(|| format!("function '{}' missing blocks array", func_name))?;
|
||||
|
||||
if blocks.is_empty() {
|
||||
return Err(format!("function '{}' has no blocks", func_name));
|
||||
}
|
||||
|
||||
let entry_id = blocks
|
||||
.get(0)
|
||||
.and_then(|b| b.get("id"))
|
||||
.and_then(|id| id.as_u64())
|
||||
.ok_or_else(|| format!("function '{}' entry block missing id", func_name))?;
|
||||
let entry_bb = BasicBlockId::new(entry_id as u32);
|
||||
|
||||
let mut signature = FunctionSignature {
|
||||
name: func_name.clone(),
|
||||
params: Vec::new(),
|
||||
return_type: MirType::Unknown,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let mut mir_fn = MirFunction::new(signature.clone(), entry_bb);
|
||||
let mut max_value_id: u32 = 0;
|
||||
|
||||
for block in blocks {
|
||||
let block_id = block
|
||||
.get("id")
|
||||
.and_then(|id| id.as_u64())
|
||||
.ok_or_else(|| format!("function '{}' block missing id", func_name))? as u32;
|
||||
let bb_id = BasicBlockId::new(block_id);
|
||||
if mir_fn.get_block(bb_id).is_none() {
|
||||
mir_fn.add_block(BasicBlock::new(bb_id));
|
||||
}
|
||||
let block_ref = mir_fn
|
||||
.get_block_mut(bb_id)
|
||||
.expect("block must exist after insertion");
|
||||
|
||||
let instructions = block
|
||||
.get("instructions")
|
||||
.and_then(|insts| insts.as_array())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"function '{}' block {} missing instructions array",
|
||||
func_name, block_id
|
||||
)
|
||||
})?;
|
||||
|
||||
for inst in instructions {
|
||||
let op = inst
|
||||
.get("op")
|
||||
.and_then(|o| o.as_str())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"function '{}' block {} missing op field",
|
||||
func_name, block_id
|
||||
)
|
||||
})?;
|
||||
match op {
|
||||
"const" => {
|
||||
let dst = inst
|
||||
.get("dst")
|
||||
.and_then(|d| d.as_u64())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"const instruction missing dst in function '{}'",
|
||||
func_name
|
||||
)
|
||||
})? as u32;
|
||||
let value_obj = inst
|
||||
.get("value")
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"const instruction missing value in function '{}'",
|
||||
func_name
|
||||
)
|
||||
})?;
|
||||
let const_val = parse_const_value(value_obj)?;
|
||||
block_ref.add_instruction(MirInstruction::Const {
|
||||
dst: ValueId::new(dst),
|
||||
value: const_val,
|
||||
});
|
||||
if dst >= max_value_id {
|
||||
max_value_id = dst + 1;
|
||||
}
|
||||
}
|
||||
"copy" => {
|
||||
let dst = inst
|
||||
.get("dst")
|
||||
.and_then(|d| d.as_u64())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"copy instruction missing dst in function '{}'",
|
||||
func_name
|
||||
)
|
||||
})? as u32;
|
||||
let src = inst
|
||||
.get("src")
|
||||
.and_then(|d| d.as_u64())
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"copy instruction missing src in function '{}'",
|
||||
func_name
|
||||
)
|
||||
})? as u32;
|
||||
block_ref.add_instruction(MirInstruction::Copy {
|
||||
dst: ValueId::new(dst),
|
||||
src: ValueId::new(src),
|
||||
});
|
||||
if dst >= max_value_id {
|
||||
max_value_id = dst + 1;
|
||||
}
|
||||
}
|
||||
"binop" => {
|
||||
let dst = require_u64(inst, "dst", "binop dst")? as u32;
|
||||
let lhs = require_u64(inst, "lhs", "binop lhs")? as u32;
|
||||
let rhs = require_u64(inst, "rhs", "binop rhs")? as u32;
|
||||
let operation = inst
|
||||
.get("operation")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("binop operation missing in function '{}'", func_name))?;
|
||||
let bop = parse_binop(operation)?;
|
||||
block_ref.add_instruction(MirInstruction::BinOp {
|
||||
dst: ValueId::new(dst),
|
||||
op: bop,
|
||||
lhs: ValueId::new(lhs),
|
||||
rhs: ValueId::new(rhs),
|
||||
});
|
||||
max_value_id = max_value_id.max(dst + 1);
|
||||
}
|
||||
"compare" => {
|
||||
let dst = require_u64(inst, "dst", "compare dst")? as u32;
|
||||
let lhs = require_u64(inst, "lhs", "compare lhs")? as u32;
|
||||
let rhs = require_u64(inst, "rhs", "compare rhs")? as u32;
|
||||
let operation = inst
|
||||
.get("operation")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| format!("compare operation missing in function '{}'", func_name))?;
|
||||
let cop = parse_compare(operation)?;
|
||||
block_ref.add_instruction(MirInstruction::Compare {
|
||||
dst: ValueId::new(dst),
|
||||
op: cop,
|
||||
lhs: ValueId::new(lhs),
|
||||
rhs: ValueId::new(rhs),
|
||||
});
|
||||
max_value_id = max_value_id.max(dst + 1);
|
||||
}
|
||||
"branch" => {
|
||||
let cond = require_u64(inst, "cond", "branch cond")? as u32;
|
||||
let then_bb = require_u64(inst, "then", "branch then")? as u32;
|
||||
let else_bb = require_u64(inst, "else", "branch else")? as u32;
|
||||
block_ref.add_instruction(MirInstruction::Branch {
|
||||
condition: ValueId::new(cond),
|
||||
then_bb: BasicBlockId::new(then_bb),
|
||||
else_bb: BasicBlockId::new(else_bb),
|
||||
});
|
||||
}
|
||||
"jump" => {
|
||||
let target = require_u64(inst, "target", "jump target")? as u32;
|
||||
block_ref.add_instruction(MirInstruction::Jump {
|
||||
target: BasicBlockId::new(target),
|
||||
});
|
||||
}
|
||||
"phi" => {
|
||||
let dst = require_u64(inst, "dst", "phi dst")? as u32;
|
||||
let incoming = inst
|
||||
.get("incoming")
|
||||
.and_then(Value::as_array)
|
||||
.ok_or_else(|| format!("phi incoming missing in function '{}'", func_name))?;
|
||||
let mut pairs = Vec::with_capacity(incoming.len());
|
||||
for entry in incoming {
|
||||
let pair = entry
|
||||
.as_array()
|
||||
.ok_or_else(|| format!("phi incoming entry must be array in function '{}'", func_name))?;
|
||||
if pair.len() != 2 {
|
||||
return Err("phi incoming entry must have 2 elements".into());
|
||||
}
|
||||
let val = pair[0]
|
||||
.as_u64()
|
||||
.ok_or_else(|| "phi incoming value must be integer".to_string())?;
|
||||
let bb = pair[1]
|
||||
.as_u64()
|
||||
.ok_or_else(|| "phi incoming block must be integer".to_string())?;
|
||||
pairs.push((BasicBlockId::new(bb as u32), ValueId::new(val as u32)));
|
||||
}
|
||||
block_ref.add_instruction(MirInstruction::Phi {
|
||||
dst: ValueId::new(dst),
|
||||
inputs: pairs,
|
||||
});
|
||||
max_value_id = max_value_id.max(dst + 1);
|
||||
}
|
||||
"ret" => {
|
||||
let value = inst
|
||||
.get("value")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| ValueId::new(v as u32));
|
||||
block_ref.add_instruction(MirInstruction::Return { value });
|
||||
if let Some(val) = value {
|
||||
signature.return_type = MirType::Integer;
|
||||
max_value_id = max_value_id.max(val.as_u32() + 1);
|
||||
} else {
|
||||
signature.return_type = MirType::Void;
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unsupported instruction '{}' in function '{}' (Gate-C v1 bridge)",
|
||||
other, func_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mir_fn.signature = signature;
|
||||
mir_fn.next_value_id = max_value_id;
|
||||
module.add_function(mir_fn);
|
||||
}
|
||||
|
||||
Ok(Some(module))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
|
||||
let (type_str, raw_val) = if let Some(t) = value_obj.get("type") {
|
||||
(
|
||||
t.clone(),
|
||||
value_obj
|
||||
.get("value")
|
||||
.cloned()
|
||||
.ok_or_else(|| "const value missing numeric value".to_string())?,
|
||||
)
|
||||
} else {
|
||||
(Value::String("i64".to_string()), value_obj.clone())
|
||||
};
|
||||
|
||||
match type_str {
|
||||
Value::String(s) => match s.as_str() {
|
||||
"i64" | "int" => {
|
||||
let val = raw_val
|
||||
.as_i64()
|
||||
.ok_or_else(|| "const value expected integer".to_string())?;
|
||||
Ok(ConstValue::Integer(val))
|
||||
}
|
||||
other => Err(format!(
|
||||
"unsupported const type '{}' in Gate-C v1 bridge",
|
||||
other
|
||||
)),
|
||||
},
|
||||
Value::Object(obj) => {
|
||||
if let Some(Value::String(kind)) = obj.get("kind") {
|
||||
if kind == "handle" {
|
||||
if let Some(Value::String(box_type)) = obj.get("box_type") {
|
||||
return Err(format!(
|
||||
"unsupported const handle type '{}' in Gate-C v1 bridge",
|
||||
box_type
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err("unsupported const type object in Gate-C v1 bridge".to_string())
|
||||
}
|
||||
_ => Err("const value has unsupported type descriptor".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn require_u64(node: &Value, key: &str, context: &str) -> Result<u64, String> {
|
||||
node.get(key)
|
||||
.and_then(Value::as_u64)
|
||||
.ok_or_else(|| format!("{} missing field '{}'", context, key))
|
||||
}
|
||||
|
||||
fn parse_binop(op: &str) -> Result<crate::mir::types::BinaryOp, String> {
|
||||
use crate::mir::types::BinaryOp;
|
||||
let bop = match op {
|
||||
"+" => BinaryOp::Add,
|
||||
"-" => BinaryOp::Sub,
|
||||
"*" => BinaryOp::Mul,
|
||||
"/" => BinaryOp::Div,
|
||||
"%" => BinaryOp::Mod,
|
||||
"&" | "bitand" => BinaryOp::BitAnd,
|
||||
"|" | "bitor" => BinaryOp::BitOr,
|
||||
"^" | "bitxor" => BinaryOp::BitXor,
|
||||
"shl" => BinaryOp::Shl,
|
||||
"shr" => BinaryOp::Shr,
|
||||
"and" => BinaryOp::And,
|
||||
"or" => BinaryOp::Or,
|
||||
other => return Err(format!("unsupported binop '{}'", other)),
|
||||
};
|
||||
Ok(bop)
|
||||
}
|
||||
|
||||
fn parse_compare(op: &str) -> Result<crate::mir::types::CompareOp, String> {
|
||||
use crate::mir::types::CompareOp;
|
||||
let cop = match op {
|
||||
"==" => CompareOp::Eq,
|
||||
"!=" => CompareOp::Ne,
|
||||
"<" => CompareOp::Lt,
|
||||
"<=" => CompareOp::Le,
|
||||
">" => CompareOp::Gt,
|
||||
">=" => CompareOp::Ge,
|
||||
other => return Err(format!("unsupported compare op '{}'", other)),
|
||||
};
|
||||
Ok(cop)
|
||||
}
|
||||
@ -21,6 +21,7 @@ mod cli_directives;
|
||||
mod demos;
|
||||
mod dispatch;
|
||||
mod json_v0_bridge;
|
||||
mod json_v1_bridge;
|
||||
mod mir_json_emit;
|
||||
pub mod modes;
|
||||
mod pipe_io;
|
||||
|
||||
@ -1,23 +1,230 @@
|
||||
/*!
|
||||
* 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.
|
||||
* Provides a JSON canonicalizer for NyVmDispatcher wrapper path.
|
||||
* Optional env toggles:
|
||||
* - HAKO_BRIDGE_INJECT_SINGLETON / NYASH_BRIDGE_INJECT_SINGLETON:
|
||||
* Rewrite ModuleFunction Array/Map len calls into Method form.
|
||||
* - HAKO_BRIDGE_EARLY_PHI_MATERIALIZE / NYASH_BRIDGE_EARLY_PHI_MATERIALIZE:
|
||||
* Move phi instructions to block head (order-preserving).
|
||||
* Dumps payload when `HAKO_DEBUG_NYVM_BRIDGE_DUMP` is set to a file path.
|
||||
*/
|
||||
|
||||
use std::fs;
|
||||
use serde_json::Value;
|
||||
use std::{env, 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") {
|
||||
let mut output = input.to_string();
|
||||
|
||||
if let Ok(path) = 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())
|
||||
|
||||
let inject_singleton = env_flag("HAKO_BRIDGE_INJECT_SINGLETON")
|
||||
|| env_flag("NYASH_BRIDGE_INJECT_SINGLETON");
|
||||
let materialize_phi = env_flag("HAKO_BRIDGE_EARLY_PHI_MATERIALIZE")
|
||||
|| env_flag("NYASH_BRIDGE_EARLY_PHI_MATERIALIZE");
|
||||
|
||||
if inject_singleton || materialize_phi {
|
||||
let mut json: Value = serde_json::from_str(input)
|
||||
.map_err(|e| format!("bridge canonicalize: invalid JSON ({})", e))?;
|
||||
let mut mutated = false;
|
||||
if inject_singleton {
|
||||
mutated |= inject_singleton_methods(&mut json)?;
|
||||
}
|
||||
if materialize_phi {
|
||||
mutated |= materialize_phi_blocks(&mut json)?;
|
||||
}
|
||||
if mutated {
|
||||
output = serde_json::to_string(&json)
|
||||
.map_err(|e| format!("bridge canonicalize: serialize error ({})", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn env_flag(name: &str) -> bool {
|
||||
env::var(name)
|
||||
.ok()
|
||||
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "on"))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn inject_singleton_methods(root: &mut Value) -> Result<bool, String> {
|
||||
let mut changed = false;
|
||||
let functions = match root.as_object_mut() {
|
||||
Some(obj) => obj.get_mut("functions"),
|
||||
None => return Err("bridge canonicalize: expected JSON object at root".into()),
|
||||
};
|
||||
let functions = match functions {
|
||||
Some(Value::Array(arr)) => arr,
|
||||
Some(_) => return Err("bridge canonicalize: functions must be array".into()),
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
for func in functions.iter_mut() {
|
||||
let blocks = func
|
||||
.get_mut("blocks")
|
||||
.and_then(Value::as_array_mut);
|
||||
let Some(blocks) = blocks else { continue };
|
||||
for block in blocks.iter_mut() {
|
||||
let insts = block
|
||||
.get_mut("instructions")
|
||||
.and_then(Value::as_array_mut);
|
||||
let Some(insts) = insts else { continue };
|
||||
for inst in insts.iter_mut() {
|
||||
if transform_module_function(inst)? {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
fn transform_module_function(inst: &mut Value) -> Result<bool, String> {
|
||||
let obj = match inst.as_object_mut() {
|
||||
Some(map) => map,
|
||||
None => return Ok(false),
|
||||
};
|
||||
match obj.get("op").and_then(Value::as_str) {
|
||||
Some("mir_call") => {}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
let mir_call = match obj.get_mut("mir_call").and_then(Value::as_object_mut) {
|
||||
Some(mc) => mc,
|
||||
None => return Ok(false),
|
||||
};
|
||||
let name_owned = {
|
||||
let callee_obj = match mir_call.get("callee").and_then(Value::as_object) {
|
||||
Some(c) => c,
|
||||
None => return Ok(false),
|
||||
};
|
||||
match callee_obj.get("type").and_then(Value::as_str) {
|
||||
Some("ModuleFunction") => {}
|
||||
_ => return Ok(false),
|
||||
}
|
||||
callee_obj
|
||||
.get("name")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or_else(|| "bridge canonicalize: ModuleFunction missing name".to_string())?
|
||||
.to_string()
|
||||
};
|
||||
let name = name_owned.as_str();
|
||||
|
||||
let (box_name, method) = match name {
|
||||
"ArrayBox.len" => ("ArrayBox", "size"),
|
||||
"MapBox.len" => ("MapBox", "len"),
|
||||
_ => return Ok(false),
|
||||
};
|
||||
|
||||
let receiver_value = mir_call
|
||||
.get_mut("args")
|
||||
.and_then(Value::as_array_mut)
|
||||
.ok_or_else(|| "bridge canonicalize: mir_call.args missing".to_string())?;
|
||||
if receiver_value.is_empty() {
|
||||
return Err(format!(
|
||||
"bridge canonicalize: {} requires receiver argument",
|
||||
name
|
||||
));
|
||||
}
|
||||
let receiver_value = receiver_value.remove(0);
|
||||
let receiver = receiver_value
|
||||
.as_u64()
|
||||
.ok_or_else(|| format!("bridge canonicalize: {} receiver must be integer", name))?;
|
||||
|
||||
let num = serde_json::Number::from_u128(receiver as u128)
|
||||
.ok_or_else(|| "bridge canonicalize: receiver out of range".to_string())?;
|
||||
|
||||
let callee = mir_call
|
||||
.get_mut("callee")
|
||||
.and_then(Value::as_object_mut)
|
||||
.ok_or_else(|| "bridge canonicalize: callee missing".to_string())?;
|
||||
callee.insert("type".to_string(), Value::String("Method".into()));
|
||||
callee.insert("method".to_string(), Value::String(method.into()));
|
||||
callee.insert("box_name".to_string(), Value::String(box_name.into()));
|
||||
callee.insert("receiver".to_string(), Value::Number(num));
|
||||
callee.remove("name");
|
||||
if !callee.contains_key("certainty") {
|
||||
callee.insert("certainty".to_string(), Value::String("Known".into()));
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn materialize_phi_blocks(root: &mut Value) -> Result<bool, String> {
|
||||
let mut changed = false;
|
||||
let functions = match root.as_object_mut() {
|
||||
Some(obj) => obj.get_mut("functions"),
|
||||
None => return Err("bridge canonicalize: expected JSON object at root".into()),
|
||||
};
|
||||
let functions = match functions {
|
||||
Some(Value::Array(arr)) => arr,
|
||||
Some(_) => return Err("bridge canonicalize: functions must be array".into()),
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
for func in functions.iter_mut() {
|
||||
let blocks = func
|
||||
.get_mut("blocks")
|
||||
.and_then(Value::as_array_mut);
|
||||
let Some(blocks) = blocks else { continue };
|
||||
for block in blocks.iter_mut() {
|
||||
let insts = block
|
||||
.get_mut("instructions")
|
||||
.and_then(Value::as_array_mut);
|
||||
let Some(insts) = insts else { continue };
|
||||
if reorder_block_phi(insts)? {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
fn reorder_block_phi(insts: &mut Vec<Value>) -> Result<bool, String> {
|
||||
let mut seen_non_phi = false;
|
||||
let mut needs_reorder = false;
|
||||
for inst in insts.iter() {
|
||||
if is_phi(inst) {
|
||||
if seen_non_phi {
|
||||
needs_reorder = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
seen_non_phi = true;
|
||||
}
|
||||
}
|
||||
if !needs_reorder {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let original = std::mem::take(insts);
|
||||
let mut phis = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for inst in original.into_iter() {
|
||||
if is_phi(&inst) {
|
||||
phis.push(inst);
|
||||
} else {
|
||||
others.push(inst);
|
||||
}
|
||||
}
|
||||
insts.extend(phis);
|
||||
insts.extend(others);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn is_phi(inst: &Value) -> bool {
|
||||
inst.as_object()
|
||||
.and_then(|obj| obj.get("op"))
|
||||
.and_then(Value::as_str)
|
||||
.map(|op| op == "phi")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@ -36,11 +36,25 @@ impl NyashRunner {
|
||||
}
|
||||
buf
|
||||
};
|
||||
if crate::config::env::nyvm_core_wrapper() {
|
||||
let use_core_wrapper = crate::config::env::nyvm_core_wrapper();
|
||||
let use_downconvert = crate::config::env::nyvm_v1_downconvert();
|
||||
if use_core_wrapper || use_downconvert {
|
||||
match crate::runner::modes::common_util::core_bridge::canonicalize_module_json(&json) {
|
||||
Ok(j) => json = j,
|
||||
Err(e) => eprintln!("[bridge] canonicalize warning: {}", e),
|
||||
}
|
||||
match crate::runner::json_v1_bridge::try_parse_v1_to_module(&json) {
|
||||
Ok(Some(module)) => {
|
||||
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => {
|
||||
eprintln!("❌ JSON v1 bridge error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
match super::json_v0_bridge::parse_json_v0_to_module(&json) {
|
||||
Ok(module) => {
|
||||
|
||||
76
tools/smokes/v2/profiles/quick/core/gate_c_v1_file_vm.sh
Normal file
76
tools/smokes/v2/profiles/quick/core/gate_c_v1_file_vm.sh
Normal file
@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# gate_c_v1_file_vm.sh — Gate-C(Core) v1 JSON (file) parity smoke (opt-in)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${SMOKES_ENABLE_GATE_C_V1:-0}" != "1" ]; then
|
||||
echo "[SKIP] SMOKES_ENABLE_GATE_C_V1!=1" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null)"
|
||||
if [ -z "$ROOT" ]; then
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
|
||||
fi
|
||||
BIN="$ROOT/target/release/nyash"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
(cd "$ROOT" && cargo build --release >/dev/null 2>&1) || {
|
||||
echo "[FAIL] build release nyash" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
JSON_FILE="/tmp/gate_c_v1_file_$$.json"
|
||||
trap 'rm -f "$JSON_FILE"' EXIT
|
||||
cat > "$JSON_FILE" <<'JSON'
|
||||
{"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}]}]}]}
|
||||
JSON
|
||||
|
||||
run_case() {
|
||||
local mode="$1"
|
||||
local label="gate_c_v1_file_vm(${mode})"
|
||||
|
||||
if [ "$mode" = "plugins-off" ]; then
|
||||
export NYASH_DISABLE_PLUGINS=1
|
||||
else
|
||||
unset NYASH_DISABLE_PLUGINS || true
|
||||
fi
|
||||
|
||||
export NYASH_QUIET=1
|
||||
export HAKO_QUIET=1
|
||||
export NYASH_CLI_VERBOSE=0
|
||||
export NYASH_NYRT_SILENT_RESULT=1
|
||||
export NYASH_NYVM_CORE=1
|
||||
export HAKO_NYVM_CORE=1
|
||||
|
||||
# Debug stdout for env (optional)
|
||||
if [ "${SMOKES_DEBUG:-0}" = "1" ]; then
|
||||
echo "[DEBUG] mode=$mode" >&2
|
||||
env | grep -E 'NYASH|HAKO' >&2
|
||||
fi
|
||||
|
||||
output="$($BIN --json-file "$JSON_FILE" 2>&1)"
|
||||
rc=$?
|
||||
last=$(printf '%s\n' "$output" | awk '/Result:/{val=$2} END{print val}')
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "$output" >&2
|
||||
echo "[FAIL] $label (rc=$rc)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$last" != "7" ]; then
|
||||
echo "$output" >&2
|
||||
echo "[FAIL] $label (expected 7, got '$last')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[PASS] $label" >&2
|
||||
}
|
||||
|
||||
run_case "plugins-off"
|
||||
run_case "plugins-on"
|
||||
|
||||
exit 0
|
||||
66
tools/smokes/v2/profiles/quick/core/gate_c_v1_pipe_vm.sh
Normal file
66
tools/smokes/v2/profiles/quick/core/gate_c_v1_pipe_vm.sh
Normal file
@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# gate_c_v1_pipe_vm.sh — Gate-C(Core) v1 JSON (pipe) parity smoke (opt-in)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${SMOKES_ENABLE_GATE_C_V1:-0}" != "1" ]; then
|
||||
echo "[SKIP] SMOKES_ENABLE_GATE_C_V1!=1" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null)"
|
||||
if [ -z "$ROOT" ]; then
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)"
|
||||
fi
|
||||
BIN="$ROOT/target/release/nyash"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
(cd "$ROOT" && cargo build --release >/dev/null 2>&1) || {
|
||||
echo "[FAIL] build release nyash" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
PAYLOAD='{"schema_version":"1.0","functions":[{"name":"main","params":[],"blocks":[{"id":0,"instructions":[{"op":"const","dst":1,"value":{"type":"i64","value":5}},{"op":"ret","value":1}]}]}]}'
|
||||
|
||||
run_case() {
|
||||
local mode="$1"
|
||||
local label="gate_c_v1_pipe_vm(${mode})"
|
||||
|
||||
if [ "$mode" = "plugins-off" ]; then
|
||||
export NYASH_DISABLE_PLUGINS=1
|
||||
else
|
||||
unset NYASH_DISABLE_PLUGINS || true
|
||||
fi
|
||||
|
||||
export NYASH_QUIET=1
|
||||
export HAKO_QUIET=1
|
||||
export NYASH_CLI_VERBOSE=0
|
||||
export NYASH_NYRT_SILENT_RESULT=1
|
||||
export NYASH_NYVM_CORE=1
|
||||
export HAKO_NYVM_CORE=1
|
||||
|
||||
output=$(printf '%s' "$PAYLOAD" | $BIN --ny-parser-pipe 2>&1)
|
||||
rc=$?
|
||||
last=$(printf '%s\n' "$output" | awk '/Result:/{val=$2} END{print val}')
|
||||
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
echo "$output" >&2
|
||||
echo "[FAIL] $label (rc=$rc)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$last" != "5" ]; then
|
||||
echo "$output" >&2
|
||||
echo "[FAIL] $label (expected 5, got '$last')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[PASS] $label" >&2
|
||||
}
|
||||
|
||||
run_case "plugins-off"
|
||||
run_case "plugins-on"
|
||||
|
||||
exit 0
|
||||
76
tools/smokes/v2/profiles/quick/core/hako_map_escape_vm.sh
Normal file
76
tools/smokes/v2/profiles/quick/core/hako_map_escape_vm.sh
Normal file
@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# hako_map_escape_vm.sh — Stage-A map literal escape/unicode boundary (opt-in)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${SMOKES_ENABLE_STAGEA_BOUNDARY:-0}" != "1" ]; then
|
||||
echo "[SKIP] SMOKES_ENABLE_STAGEA_BOUNDARY!=1" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
(cd "$ROOT" && cargo build --release >/dev/null 2>&1) || {
|
||||
echo "[FAIL] build release nyash" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
compile_stage_a() {
|
||||
local code="$1"
|
||||
local hako_tmp="/tmp/hako_stagea_map_$$.hako"
|
||||
local json_out="/tmp/hako_stagea_map_$$.mir.json"
|
||||
printf "%s\n" "$code" > "$hako_tmp"
|
||||
local raw="/tmp/hako_stagea_map_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 \
|
||||
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
|
||||
"$BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --source "$(cat "$hako_tmp")" > "$raw" 2>&1
|
||||
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$raw" > "$json_out" || true
|
||||
rm -f "$raw" "$hako_tmp"
|
||||
if [ ! -s "$json_out" ]; then
|
||||
echo "[DIAG] Stage-A failed to emit JSON (expected for some boundary cases)" >&2
|
||||
return 2
|
||||
fi
|
||||
echo "$json_out"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_gate_c() {
|
||||
local json_path="$1"
|
||||
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 NYASH_NYRT_SILENT_RESULT=1 \
|
||||
"$BIN" --json-file "$json_path" >/dev/null 2>&1 || true
|
||||
rm -f "$json_path"
|
||||
}
|
||||
|
||||
# Case 1: escaped quote in key
|
||||
code='box Main { static method main() { local m={"a\"b":1}; print(1); } }'
|
||||
if json=$(compile_stage_a "$code"); then
|
||||
run_gate_c "$json"
|
||||
echo "[PASS] stagea_map_key_escaped_quote (emitted JSON)" >&2
|
||||
else
|
||||
echo "[PASS] stagea_map_key_escaped_quote (diagnostic acceptable)" >&2
|
||||
fi
|
||||
|
||||
# Case 2: unicode key
|
||||
code='box Main { static method main() { local m={"ねこ":2}; print(2); } }'
|
||||
if json=$(compile_stage_a "$code"); then
|
||||
run_gate_c "$json"
|
||||
echo "[PASS] stagea_map_key_unicode (emitted JSON)" >&2
|
||||
else
|
||||
echo "[PASS] stagea_map_key_unicode (diagnostic acceptable)" >&2
|
||||
fi
|
||||
|
||||
# Case 3: empty map
|
||||
code='box Main { static method main() { local m={}; print(0); } }'
|
||||
if json=$(compile_stage_a "$code"); then
|
||||
run_gate_c "$json"
|
||||
echo "[PASS] stagea_map_empty (emitted JSON)" >&2
|
||||
else
|
||||
echo "[PASS] stagea_map_empty (diagnostic acceptable)" >&2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user