vm(hako): add v1 reader/dispatcher (flagged), commonize mir_call handler, share block scan; smokes: add v1 hakovm canary; docs: 20.37/20.38 plans, OOB policy; runner: v1 hakovm toggle; include SKIP summary

This commit is contained in:
nyash-codex
2025-11-03 23:21:48 +09:00
parent a4f30ae827
commit 06a729ff40
67 changed files with 3340 additions and 1520 deletions

44
src/abi/nyrt_shim.rs Normal file
View File

@ -0,0 +1,44 @@
// CABI shim (PoC). These functions are noops for 20.36/20.37.
// Kept tiny and isolated. Linkage names match include/nyrt.h.
#[no_mangle]
pub extern "C" fn nyrt_init() -> i32 { 0 }
#[no_mangle]
pub extern "C" fn nyrt_teardown() { }
#[no_mangle]
pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { 1 }
#[no_mangle]
pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { 0 }
#[no_mangle]
pub extern "C" fn nyrt_hostcall(
_name: *const ::std::os::raw::c_char,
_method: *const ::std::os::raw::c_char,
_payload_json: *const ::std::os::raw::c_char,
_out_buf: *mut ::std::os::raw::c_char,
_out_buf_len: u32,
) -> i32 { 0 }
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn load_and_exec_noop_returns_zero() {
// init/teardown are no-ops but should stay callable
assert_eq!(unsafe { nyrt_init() }, 0);
let json = CString::new("{}").expect("CString");
let handle = unsafe { nyrt_load_mir_json(json.as_ptr()) };
assert_eq!(handle, 1);
assert_eq!(unsafe { nyrt_exec_main(handle) }, 0);
// ensure teardown does not panic even when called after exec
unsafe { nyrt_teardown() };
}
}

View File

@ -100,9 +100,25 @@ impl MirInterpreter {
block: &BasicBlock,
last_pred: Option<BasicBlockId>,
) -> Result<(), VMError> {
let trace_phi = std::env::var("NYASH_VM_TRACE_PHI").ok().as_deref() == Some("1");
if trace_phi {
eprintln!(
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
block.id,
last_pred,
block.predecessors
);
}
for inst in block.phi_instructions() {
if let MirInstruction::Phi { dst, inputs } = inst {
let dst_id = *dst;
if trace_phi {
let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect();
eprintln!(
"[vm-trace-phi] phi dst={:?} inputs.pred={:?}",
dst_id, in_preds
);
}
if let Some(pred) = last_pred {
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
let v = match self.reg_load(*val) {
@ -150,6 +166,15 @@ impl MirInterpreter {
}
};
if strict {
if trace_phi {
eprintln!(
"[vm-trace-phi][strict] mismatch dst={:?} last_pred={:?} inputs={:?} preds_of_bb={:?}",
dst_id,
pred,
inputs,
block.predecessors
);
}
return Err(VMError::InvalidInstruction(format!(
"phi pred mismatch at {:?}: no input for predecessor {:?}",
dst_id, pred

View File

@ -645,6 +645,7 @@ impl MirInterpreter {
extern_name: &str,
args: &[ValueId],
) -> Result<VMValue, VMError> {
if let Some(res) = self.extern_provider_dispatch(extern_name, args) { return res; }
match extern_name {
// Minimal console externs
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
@ -656,6 +657,8 @@ impl MirInterpreter {
}
Ok(VMValue::Void)
}
// Direct provider calls (bypass hostbridge.extern_invoke)
// Above provider covers env.* family; keep legacy fallbacks below
"exit" => {
let code = if let Some(arg_id) = args.get(0) {
self.reg_load(*arg_id)?.as_integer().unwrap_or(0)

View File

@ -0,0 +1,142 @@
use super::*;
use serde_json::Value as JsonValue;
impl MirInterpreter {
fn patch_mir_json_version(s: &str) -> String {
match serde_json::from_str::<JsonValue>(s) {
Ok(mut v) => {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
}
}
s.to_string()
}
Err(_) => s.to_string(),
}
}
/// Central extern dispatcher used by both execute_extern_function (calls.rs)
/// and handle_extern_call (externals.rs). Returns a VMValue; callers are
/// responsible for writing it to registers when needed.
pub(super) fn extern_provider_dispatch(
&mut self,
extern_name: &str,
args: &[ValueId],
) -> Option<Result<VMValue, VMError>> {
match extern_name {
// Console/print family (minimal)
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
let s = if let Some(a0) = args.get(0) { self.reg_load(*a0).ok() } else { None };
if let Some(v) = s { println!("{}", v.to_string()); } else { println!(""); }
Some(Ok(VMValue::Void))
}
// Extern providers (env.mirbuilder / env.codegen)
"env.mirbuilder.emit" => {
if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into()))); }
let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let res = match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) {
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))),
};
Some(res)
}
"env.codegen.emit_object" => {
if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into()))); }
let mir_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(),
timeout_ms: None,
};
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))),
};
Some(res)
}
// Environment
"env.get" => {
if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.get expects 1 arg".into()))); }
let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let val = std::env::var(&key).ok();
Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void }))
}
// Legacy global-call form: hostbridge.extern_invoke(name, method, args?)
"hostbridge.extern_invoke" => {
if args.len() < 2 {
return Some(Err(VMError::InvalidInstruction(
"extern_invoke expects at least 2 args".into(),
)));
}
let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
// Extract first payload arg (optional)
let mut first_arg_str: Option<String> = None;
if let Some(a2) = args.get(2) {
let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) };
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem = ab.get(idx);
first_arg_str = Some(elem.to_string_box().value);
} else {
first_arg_str = Some(b.to_string_box().value);
}
}
_ => first_arg_str = Some(v.to_string()),
}
}
// Dispatch to known providers
let out = match (name.as_str(), method.as_str()) {
("env.mirbuilder", "emit") => {
if let Some(s) = first_arg_str {
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
Ok(out) => Ok(VMValue::String(Self::patch_mir_json_version(&out))),
Err(e) => Err(VMError::InvalidInstruction(format!(
"env.mirbuilder.emit: {}",
e
))),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.mirbuilder.emit expects 1 arg".into(),
))
}
}
("env.codegen", "emit_object") => {
if let Some(s) = first_arg_str {
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(VMError::InvalidInstruction(format!(
"env.codegen.emit_object: {}",
e
))),
}
} else {
Err(VMError::InvalidInstruction(
"extern_invoke env.codegen.emit_object expects 1 arg".into(),
))
}
}
_ => Err(VMError::InvalidInstruction(format!(
"hostbridge.extern_invoke unsupported for {}.{}",
name, method
))),
};
Some(out)
}
_ => None,
}
}
}

View File

@ -1,6 +1,22 @@
use super::*;
use serde_json::{Value as JsonValue, Map as JsonMap};
impl MirInterpreter {
#[inline]
fn ensure_mir_json_version_field(s: &str) -> String {
match serde_json::from_str::<JsonValue>(s) {
Ok(mut v) => {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
}
}
s.to_string()
}
Err(_) => s.to_string(),
}
}
pub(super) fn handle_extern_call(
&mut self,
dst: Option<ValueId>,
@ -131,50 +147,20 @@ impl MirInterpreter {
Ok(())
}
("env", "get") => {
// env.get(key) - get environment variable
if let Some(a0) = args.get(0) {
let k = self.reg_load(*a0)?.to_string();
let val = std::env::var(&k).ok();
if let Some(d) = dst {
if let Some(v) = val {
self.regs.insert(d, VMValue::String(v));
} else {
self.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::VoidBox::new())));
}
}
}
// Delegate to provider
let ret = self.extern_provider_dispatch("env.get", args).unwrap_or(Ok(VMValue::Void))?;
if let Some(d) = dst { self.regs.insert(d, ret); }
Ok(())
}
("env.mirbuilder", "emit") => {
// program_json -> mir_json (delegate provider)
if let Some(a0) = args.get(0) {
let program_json = self.reg_load(*a0)?.to_string();
match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) {
Ok(s) => {
if let Some(d) = dst { self.regs.insert(d, VMValue::String(s)); }
Ok(())
}
Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))),
}
} else {
Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into()))
}
let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?;
if let Some(d) = dst { self.regs.insert(d, ret); }
Ok(())
}
("env.codegen", "emit_object") => {
// mir_json -> object path (ny-llvmc or harness)
if let Some(a0) = args.get(0) {
let mir_json = self.reg_load(*a0)?.to_string();
let opts = crate::host_providers::llvm_codegen::Opts { out: None, nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from), opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(), timeout_ms: None };
match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
Ok(p) => {
if let Some(d) = dst { self.regs.insert(d, VMValue::String(p.to_string_lossy().into_owned())); }
Ok(())
}
Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))),
}
} else {
Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into()))
}
let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?;
if let Some(d) = dst { self.regs.insert(d, ret); }
Ok(())
}
("hostbridge", "extern_invoke") => {
// hostbridge.extern_invoke(name, method, args?)
@ -215,9 +201,8 @@ impl MirInterpreter {
if let Some(s) = first_arg_str {
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
Ok(out) => {
if let Some(d) = dst {
self.regs.insert(d, VMValue::String(out));
}
let patched = Self::ensure_mir_json_version_field(&out);
if let Some(d) = dst { self.regs.insert(d, VMValue::String(patched)); }
Ok(())
}
Err(e) => Err(VMError::InvalidInstruction(format!(

View File

@ -23,6 +23,7 @@ mod boxes_void_guards;
mod call_resolution;
mod calls;
mod externals;
mod extern_provider;
mod memory;
mod misc;

View File

@ -41,6 +41,7 @@ pub fn build_command() -> Command {
.arg(Arg::new("parser").long("parser").value_name("{rust|ny}").help("Choose parser: 'rust' (default) or 'ny' (direct v0 bridge)"))
.arg(Arg::new("ny-parser-pipe").long("ny-parser-pipe").help("Read Ny JSON IR v0 from stdin and execute via MIR Interpreter").action(clap::ArgAction::SetTrue))
.arg(Arg::new("json-file").long("json-file").value_name("FILE").help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter"))
.arg(Arg::new("mir-json-file").long("mir-json-file").value_name("FILE").help("[Diagnostic] Read MIR JSON v0 from a file and perform minimal validation/inspection (experimental)") )
.arg(Arg::new("emit-mir-json").long("emit-mir-json").value_name("FILE").help("Emit MIR JSON v0 to file and exit"))
.arg(Arg::new("program-json-to-mir").long("program-json-to-mir").value_name("FILE").help("Convert Program(JSON v0) to MIR(JSON) and exit (use with --json-file)"))
.arg(Arg::new("emit-exe").long("emit-exe").value_name("FILE").help("Emit native executable via ny-llvmc and exit"))
@ -140,6 +141,7 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
json_file: matches.get_one::<String>("json-file").cloned(),
mir_json_file: matches.get_one::<String>("mir-json-file").cloned(),
build_path: matches.get_one::<String>("build").cloned(),
build_app: matches.get_one::<String>("build-app").cloned(),
build_out: matches.get_one::<String>("build-out").cloned(),

View File

@ -68,6 +68,7 @@ pub struct ParserPipeConfig {
pub parser_ny: bool,
pub ny_parser_pipe: bool,
pub json_file: Option<String>,
pub mir_json_file: Option<String>,
}
#[derive(Debug, Clone)]

View File

@ -50,6 +50,7 @@ pub struct CliConfig {
pub parser_ny: bool,
pub ny_parser_pipe: bool,
pub json_file: Option<String>,
pub mir_json_file: Option<String>,
pub gc_mode: Option<String>,
pub build_path: Option<String>,
pub build_app: Option<String>,
@ -128,6 +129,7 @@ impl CliConfig {
parser_ny: self.parser_ny,
ny_parser_pipe: self.ny_parser_pipe,
json_file: self.json_file.clone(),
mir_json_file: self.mir_json_file.clone(),
},
gc_mode: self.gc_mode.clone(),
compile_wasm: self.compile_wasm,
@ -184,6 +186,7 @@ impl Default for CliConfig {
parser_ny: false,
ny_parser_pipe: false,
json_file: None,
mir_json_file: None,
build_path: None,
build_app: None,
build_out: None,

View File

@ -72,6 +72,9 @@ pub mod using; // using resolver scaffolding (Phase 15)
// Host providers (extern bridge for Hako boxes)
pub mod host_providers;
// CABI PoC shim (20.36/20.37)
pub mod abi { pub mod nyrt_shim; }
// Expose the macro engine module under a raw identifier; the source lives under `src/macro/`.
#[path = "macro/mod.rs"]
pub mod r#macro;

View File

@ -44,7 +44,7 @@ impl MirBuilder {
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new();
if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); }
if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_v)); }
if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); }
match inputs.len() {
0 => {}
1 => {
@ -77,7 +77,7 @@ impl MirBuilder {
.unwrap_or(*pre_val);
let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new();
if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); }
if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_v)); }
if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); }
match inputs.len() {
0 => {}
1 => {
@ -146,7 +146,7 @@ impl MirBuilder {
// Build inputs from reachable predecessors only
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_for_var)); }
if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_value_for_var)); }
if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_for_var)); }
match inputs.len() {
0 => {}
1 => {
@ -169,7 +169,7 @@ impl MirBuilder {
// No variable assignment pattern detected just emit Phi for expression result
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_raw)); }
if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_value_raw)); }
if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_raw)); }
match inputs.len() {
0 => { /* leave result_val as fresh, but unused; synthesize void */
let v = crate::mir::builder::emission::constant::emit_void(self);

View File

@ -178,8 +178,9 @@ pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
// Build incoming pairs from reachable predecessors only
let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new();
// Only include reachable predecessors; do not synthesize else_block when unreachable.
if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); }
if let Some(ep) = else_pred_opt.or(Some(else_block)) { inputs.push((ep, else_v)); }
if let Some(ep) = else_pred_opt { inputs.push((ep, else_v)); }
match inputs.len() {
0 => {}

View File

@ -42,6 +42,47 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
// Direct v0 bridge when requested via CLI/env
let groups = runner.config.as_groups();
// Diagnostic/Exec: accept MIR JSON file direct (experimental; default OFF)
if let Some(path) = groups.parser.mir_json_file.as_ref() {
match std::fs::read_to_string(path) {
Ok(text) => {
// Try schema v1 first (preferred by emitter)
match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) {
Ok(Some(module)) => {
crate::cli_v!("[mir-json] schema=v1 executing {} (len={})", path, text.len());
let rc = runner.execute_mir_module_quiet_exit(&module);
std::process::exit(rc);
}
Ok(None) => {
// Not v1 schema; attempt minimal v0 loader
if text.contains("\"functions\"") && text.contains("\"blocks\"") {
match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) {
Ok(module) => {
crate::cli_v!("[mir-json] schema=v0 executing {} (len={})", path, text.len());
let rc = runner.execute_mir_module_quiet_exit(&module);
std::process::exit(rc);
}
Err(e) => {
eprintln!("❌ MIR JSON v0 parse error: {}", e);
std::process::exit(1);
}
}
}
eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path);
std::process::exit(1);
}
Err(e) => {
eprintln!("❌ MIR JSON parse error (v1): {}", e);
std::process::exit(1);
}
}
}
Err(e) => {
eprintln!("❌ Error reading MIR JSON {}: {}", path, e);
std::process::exit(1);
}
}
}
let use_ny_parser = groups.parser.parser_ny
|| std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
if use_ny_parser {
@ -236,10 +277,13 @@ impl NyashRunner {
if let Some(_sb) = result.as_any().downcast_ref::<StringBox>() {
return 0; // strings do not define rc semantics yet
}
0
} else {
0
}
// Global fallbacks when signature is missing or imprecise
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() { return to_rc(ib.value); }
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() { return if bb.value { 1 } else { 0 }; }
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() { return to_rc(fb.value as i64); }
if let Some(_sb) = result.as_any().downcast_ref::<StringBox>() { return 0; }
0
}
Err(_) => 1,
}

View File

@ -13,6 +13,14 @@ pub(super) fn lower_loop_stmt(
loop_stack: &mut Vec<LoopContext>,
env: &BridgeEnv,
) -> Result<BasicBlockId, String> {
// Unification toggle (default ON). For now legacy path is removed; when OFF, warn and proceed unified.
let unify_on = std::env::var("NYASH_MIR_UNIFY_LOOPFORM")
.ok()
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1"|"true"|"on"))
.unwrap_or(true);
if !unify_on {
crate::cli_v!("[loopform] NYASH_MIR_UNIFY_LOOPFORM=0 requested, but legacy path is unavailable; using unified phi_core path");
}
let cond_bb = new_block(f);
let body_bb = new_block(f);
let exit_bb = new_block(f);

View File

@ -3,6 +3,26 @@ use crate::mir::{
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
};
use serde_json::Value;
use super::mir_json::common as mirjson_common;
fn parse_effects_from(node: &Value) -> EffectMask {
if let Some(arr) = node.get("effects").and_then(Value::as_array) {
let mut m = EffectMask::PURE;
for e in arr {
if let Some(s) = e.as_str() {
match s {
"write" | "mut" | "WriteHeap" => { m = m.union(EffectMask::WRITE); }
"read" | "ReadHeap" => { m = m.union(EffectMask::READ); }
"io" | "IO" | "ffi" | "FFI" | "debug" => { m = m.union(EffectMask::IO); }
"control" | "Control" => { m = m.union(EffectMask::CONTROL); }
_ => {}
}
}
}
return m;
}
EffectMask::PURE
}
/// Try to parse MIR JSON v1 schema into a MIR module.
/// Returns Ok(None) when the input is not v1 (schema_version missing).
@ -126,7 +146,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
func_name
)
})?;
let const_val = parse_const_value(value_obj)?;
let const_val = mirjson_common::parse_const_value_generic(value_obj)?;
block_ref.add_instruction(MirInstruction::Const {
dst: ValueId::new(dst),
value: const_val,
@ -255,11 +275,19 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
}
"mir_call" => {
// Minimal v1 mir_call support (Global/Method/Constructor/Extern/Value + Closure creation)
// dst: optional
// Accept both shapes:
// - flat: { op:"mir_call", callee:{...}, args:[...], effects:[] }
// - nested: { op:"mir_call", mir_call:{ callee:{...}, args:[...], effects:[] } }
// dst remains at the instruction root level in both forms.
let dst_opt = inst.get("dst").and_then(|d| d.as_u64()).map(|v| ValueId::new(v as u32));
// args: array of value ids
let effects = if let Some(sub) = inst.get("mir_call") { parse_effects_from(sub) } else { parse_effects_from(inst) };
// args: support both flat/nested placement
let mut argv: Vec<ValueId> = Vec::new();
if let Some(arr) = inst.get("args").and_then(|a| a.as_array()) {
if let Some(arr) = inst
.get("args")
.and_then(|a| a.as_array())
.or_else(|| inst.get("mir_call").and_then(|m| m.get("args").and_then(|a| a.as_array())))
{
for a in arr {
let id = a.as_u64().ok_or_else(|| format!(
"mir_call arg must be integer value id in function '{}'",
@ -268,8 +296,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
argv.push(ValueId::new(id));
}
}
// callee: only Global(name) supported here
let callee_obj = inst.get("callee").ok_or_else(|| {
// callee: support Global/Method/Extern/Value/Closure/Constructor (minimal)
let callee_obj = inst
.get("callee")
.or_else(|| inst.get("mir_call").and_then(|m| m.get("callee")))
.ok_or_else(|| {
format!("mir_call missing callee in function '{}'", func_name)
})?;
let ctype = callee_obj
@ -306,10 +337,31 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
func: ValueId::new(0),
callee: Some(crate::mir::definitions::Callee::Global(mapped)),
args: argv,
effects: EffectMask::PURE,
effects,
});
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
"Constructor" => {
// new box instance: box_type required
let bt = callee_obj
.get("box_type")
.and_then(Value::as_str)
.ok_or_else(|| format!(
"mir_call callee Constructor missing box_type in function '{}'",
func_name
))?;
// dst required for Constructor
let dst = dst_opt.ok_or_else(|| format!(
"mir_call Constructor requires dst in function '{}'",
func_name
))?;
block_ref.add_instruction(MirInstruction::NewBox {
dst,
box_type: bt.to_string(),
args: argv.clone(),
});
max_value_id = max_value_id.max(dst.as_u32() + 1);
}
"Method" => {
// receiver: required u64, method: string, box_name: optional
let method = callee_obj
@ -342,63 +394,96 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
}),
args: argv,
effects: EffectMask::PURE,
effects,
});
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
"Closure" => {
// Closure creation (NewClosure equivalent)
// Requires dst; accepts optional params[], captures[[name, id]...], me_capture
let dst = dst_opt.ok_or_else(|| format!(
"mir_call Closure requires dst in function '{}'",
func_name
))?;
// params: array of strings (optional)
let mut params: Vec<String> = Vec::new();
if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) {
for p in arr {
let s = p.as_str().ok_or_else(|| format!(
"mir_call Closure params must be strings in function '{}'",
func_name
))?;
params.push(s.to_string());
}
}
// captures: array of [name, id]
let mut captures: Vec<(String, ValueId)> = Vec::new();
if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) {
for e in arr {
let pair = e.as_array().ok_or_else(|| format!(
"mir_call Closure capture entry must be array in function '{}'",
func_name
))?;
if pair.len() != 2 {
return Err("mir_call Closure capture entry must have 2 elements".into());
// Two shapes are seen in the wild:
// 1) NewClosure-style descriptor (params/captures/me_capture present) → NewClosure
// 2) Value-style descriptor (func present, optionally captures array) → Call(Callee::Value)
let has_new_fields = callee_obj.get("params").is_some()
|| callee_obj.get("captures").is_some()
|| callee_obj.get("me_capture").is_some();
if has_new_fields {
// Closure creation (NewClosure equivalent)
let dst = dst_opt.ok_or_else(|| format!(
"mir_call Closure requires dst in function '{}'",
func_name
))?;
// params: array of strings (optional)
let mut params: Vec<String> = Vec::new();
if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) {
for p in arr {
let s = p.as_str().ok_or_else(|| format!(
"mir_call Closure params must be strings in function '{}'",
func_name
))?;
params.push(s.to_string());
}
let name = pair[0].as_str().ok_or_else(|| {
"mir_call Closure capture[0] must be string".to_string()
})?;
let id = pair[1].as_u64().ok_or_else(|| {
"mir_call Closure capture[1] must be integer".to_string()
})? as u32;
captures.push((name.to_string(), ValueId::new(id)));
}
// captures: array of [name, id]
let mut captures: Vec<(String, ValueId)> = Vec::new();
if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) {
for e in arr {
let pair = e.as_array().ok_or_else(|| format!(
"mir_call Closure capture entry must be array in function '{}'",
func_name
))?;
if pair.len() != 2 {
return Err("mir_call Closure capture entry must have 2 elements".into());
}
let name = pair[0].as_str().ok_or_else(|| {
"mir_call Closure capture[0] must be string".to_string()
})?;
let id = pair[1].as_u64().ok_or_else(|| {
"mir_call Closure capture[1] must be integer".to_string()
})? as u32;
captures.push((name.to_string(), ValueId::new(id)));
}
}
// me_capture: optional u64
let me_capture = callee_obj
.get("me_capture")
.and_then(Value::as_u64)
.map(|v| ValueId::new(v as u32));
// Body is not carried in v1; create empty body vector as placeholder
block_ref.add_instruction(MirInstruction::NewClosure {
dst,
params,
body: Vec::new(),
captures,
me: me_capture,
});
max_value_id = max_value_id.max(dst.as_u32() + 1);
} else {
// Value-style closure: treat like Value(func id)
let fid = callee_obj
.get("func")
.and_then(Value::as_u64)
.ok_or_else(|| format!(
"mir_call callee Closure missing func in function '{}'",
func_name
))? as u32;
// Captures array (if present) are appended to argv for minimal parity
if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) {
for c in caps {
let id = c.as_u64().ok_or_else(|| format!(
"mir_call Closure capture must be integer in function '{}'",
func_name
))? as u32;
argv.push(ValueId::new(id));
}
}
block_ref.add_instruction(MirInstruction::Call {
dst: dst_opt,
func: ValueId::new(0),
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
args: argv,
effects,
});
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
// me_capture: optional u64
let me_capture = callee_obj
.get("me_capture")
.and_then(Value::as_u64)
.map(|v| ValueId::new(v as u32));
// Body is not carried in v1; create empty body vector as placeholder
block_ref.add_instruction(MirInstruction::NewClosure {
dst,
params,
body: Vec::new(),
captures,
me: me_capture,
});
max_value_id = max_value_id.max(dst.as_u32() + 1);
}
"Constructor" => {
// box_type: string, dst: required
@ -453,41 +538,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
func: ValueId::new(0),
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
args: argv,
effects: EffectMask::PURE,
});
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
"Closure" => {
// Minimal closure support: treat as Value(func id) and ignore captures here.
// Schema: { type: "Closure", func: <u64>, captures?: [u64, ...] }
let fid = callee_obj
.get("func")
.and_then(Value::as_u64)
.ok_or_else(|| format!(
"mir_call callee Closure missing func in function '{}'",
func_name
))? as u32;
// If captures exist, append them to argv (best-effort minimal semantics)
if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) {
for c in caps {
let id = c.as_u64().ok_or_else(|| format!(
"mir_call Closure capture must be integer in function '{}'",
func_name
))? as u32;
argv.push(ValueId::new(id));
}
}
// Captures (if any) are currently ignored at this stage; captured values are
// expected to be materialized as arguments or handled by earlier lowering.
block_ref.add_instruction(MirInstruction::Call {
dst: dst_opt,
func: ValueId::new(0),
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
args: argv,
effects: EffectMask::PURE,
effects,
});
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
}
// (no duplicate Closure arm; handled above)
other => {
return Err(format!(
"unsupported callee type '{}' in mir_call (Gate-C v1 bridge)",
@ -515,44 +570,101 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
#[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") {
// Accept both shapes:
// 1) { "type": "i64", "value": 123 }
// 2) { "type": {"kind":"handle","box_type":"StringBox"}, "value": "str" }
// 3) Minimal fallback: when "type" is omitted, assume integer/string directly
let (type_desc, raw_val) = if let Some(t) = value_obj.get("type") {
(
t.clone(),
Some(t.clone()),
value_obj
.get("value")
.cloned()
.ok_or_else(|| "const value missing numeric value".to_string())?,
.ok_or_else(|| "const value missing 'value' field".to_string())?,
)
} else {
(Value::String("i64".to_string()), value_obj.clone())
(None, value_obj.clone())
};
match type_str {
Value::String(s) => match s.as_str() {
// String type descriptor
if let Some(Value::String(s)) = type_desc.as_ref() {
match s.as_str() {
// Integer
"i64" | "int" => {
let val = raw_val
.as_i64()
.ok_or_else(|| "const value expected integer".to_string())?;
Ok(ConstValue::Integer(val))
return 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
));
// Float
"f64" | "float" => {
let val = raw_val
.as_f64()
.ok_or_else(|| "const value expected float".to_string())?;
return Ok(ConstValue::Float(val));
}
// Bool (allow explicit bool schema even if current emitter uses i64)
"i1" | "bool" => {
let b = match raw_val {
Value::Bool(v) => v,
Value::Number(n) => n.as_i64().unwrap_or(0) != 0,
Value::String(ref s) => s == "true" || s == "1",
_ => false,
};
return Ok(ConstValue::Bool(b));
}
// String explicit
"string" | "String" => {
let s = raw_val
.as_str()
.ok_or_else(|| "const value expected string".to_string())?;
return Ok(ConstValue::String(s.to_string()));
}
// Void/Null
"void" => {
return Ok(ConstValue::Void);
}
other => {
return Err(format!(
"unsupported const type '{}' in Gate-C v1 bridge",
other
));
}
}
}
// Object descriptor (e.g., handle/StringBox)
if let Some(Value::Object(map)) = type_desc.as_ref() {
if let Some(Value::String(kind)) = map.get("kind") {
if kind == "handle" {
if let Some(Value::String(box_type)) = map.get("box_type") {
match box_type.as_str() {
// StringBox handle is serialized with raw string payload
"StringBox" => {
let s = raw_val
.as_str()
.ok_or_else(|| "StringBox const expects string value".to_string())?;
return Ok(ConstValue::String(s.to_string()));
}
// Other handle kinds are not yet supported in the bridge
other => {
return Err(format!(
"unsupported const handle type '{}' in Gate-C v1 bridge",
other
));
}
}
}
}
Err("unsupported const type object in Gate-C v1 bridge".to_string())
}
return Err("unsupported const type object in Gate-C v1 bridge".to_string());
}
// No explicit type: heuristics
match raw_val {
Value::Number(n) => Ok(ConstValue::Integer(n.as_i64().ok_or_else(|| "integer expected".to_string())?)),
Value::Bool(b) => Ok(ConstValue::Bool(b)),
Value::String(s) => Ok(ConstValue::String(s)),
_ => Err("const value has unsupported type descriptor".to_string()),
}
}

View File

@ -0,0 +1,63 @@
use crate::mir::ConstValue;
use serde_json::Value;
/// Generic const parser used by MIR JSON loaders (v0/v1).
/// Supports minimal set: i64/f64/bool/string and handle(StringBox)->String.
pub fn parse_const_value_generic(value_obj: &Value) -> Result<ConstValue, String> {
// Shapes:
// 1) { "type": "i64", "value": 123 }
// 2) { "type": {"kind":"handle","box_type":"StringBox"}, "value": "str" }
// 3) When "type" is omitted, infer from value
let (type_desc, raw_val) = if let Some(t) = value_obj.get("type") {
(
Some(t.clone()),
value_obj
.get("value")
.cloned()
.ok_or_else(|| "const value missing 'value' field".to_string())?,
)
} else {
(None, value_obj.clone())
};
if let Some(Value::String(s)) = type_desc.as_ref() {
return match s.as_str() {
"i64" | "int" => raw_val.as_i64().map(ConstValue::Integer).ok_or_else(|| "const value expected integer".to_string()),
"f64" | "float" => raw_val.as_f64().map(ConstValue::Float).ok_or_else(|| "const value expected float".to_string()),
"i1" | "bool" => Ok(match raw_val {
Value::Bool(b) => ConstValue::Bool(b),
Value::Number(n) => ConstValue::Bool(n.as_i64().unwrap_or(0) != 0),
Value::String(ref s) => ConstValue::Bool(s == "true" || s == "1"),
_ => ConstValue::Bool(false),
}),
"string" | "String" => raw_val.as_str().map(|s| ConstValue::String(s.to_string())).ok_or_else(|| "const value expected string".to_string()),
"void" => Ok(ConstValue::Void),
other => Err(format!("unsupported const type '{}' in MIR JSON bridge", other)),
};
}
if let Some(Value::Object(map)) = type_desc.as_ref() {
if let Some(Value::String(kind)) = map.get("kind") {
if kind == "handle" {
if let Some(Value::String(box_type)) = map.get("box_type") {
return match box_type.as_str() {
"StringBox" => raw_val
.as_str()
.map(|s| ConstValue::String(s.to_string()))
.ok_or_else(|| "StringBox const expects string value".to_string()),
other => Err(format!("unsupported const handle type '{}' in MIR JSON bridge", other)),
};
}
}
}
return Err("unsupported const type object in MIR JSON bridge".to_string());
}
match raw_val {
Value::Number(n) => n.as_i64().map(ConstValue::Integer).ok_or_else(|| "integer expected".to_string()),
Value::Bool(b) => Ok(ConstValue::Bool(b)),
Value::String(s) => Ok(ConstValue::String(s)),
_ => Err("const value has unsupported type descriptor".to_string()),
}
}

162
src/runner/mir_json_v0.rs Normal file
View File

@ -0,0 +1,162 @@
use crate::mir::{
function::{FunctionSignature, MirFunction, MirModule},
BasicBlock, BasicBlockId, ConstValue, MirInstruction, MirType, ValueId,
};
use serde_json::Value;
use super::mir_json::common as mirjson_common;
/// Parse minimal MIR JSON v0 (no schema_version, root has `functions` and each function has `blocks`).
/// Supported ops (minimal): const(i64), copy, compare(op/lhs/rhs), branch(cond/then/else), jump(target), phi(dst,incoming), ret(value?).
pub fn parse_mir_v0_to_module(json: &str) -> Result<MirModule, String> {
let value: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {}", e))?;
let functions = value
.get("functions")
.and_then(|f| f.as_array())
.ok_or_else(|| "JSON missing functions array".to_string())?;
let mut module = MirModule::new("mir_json_v0".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))? as u32;
let entry_bb = BasicBlockId::new(entry_id);
let mut signature = FunctionSignature {
name: func_name.clone(),
params: Vec::new(),
return_type: MirType::Unknown,
effects: crate::mir::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 = require_u64(inst, "dst", "const dst")? as u32;
let vobj = inst.get("value").ok_or_else(|| "const missing value".to_string())?;
let val = parse_const_value(vobj)?;
block_ref.add_instruction(MirInstruction::Const { dst: ValueId::new(dst), value: val });
max_value_id = max_value_id.max(dst + 1);
}
"copy" => {
let dst = require_u64(inst, "dst", "copy dst")? as u32;
let src = require_u64(inst, "src", "copy src")? as u32;
block_ref.add_instruction(MirInstruction::Copy { dst: ValueId::new(dst), src: ValueId::new(src) });
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).unwrap_or("==");
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(|| "phi incoming missing".to_string())?;
let mut pairs = Vec::with_capacity(incoming.len());
for entry in incoming {
let pair = entry.as_array().ok_or_else(|| "phi incoming entry must be array".to_string())?;
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())? as u32;
let bb = pair[1].as_u64().ok_or_else(|| "phi incoming block must be integer".to_string())? as u32;
pairs.push((BasicBlockId::new(bb), ValueId::new(val)));
}
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 op '{}' in mir_json_v0 loader", other));
}
}
}
}
// Set max value id (best effort)
mir_fn.next_value_id = max_value_id;
module.functions.insert(func_name.clone(), mir_fn);
}
Ok(module)
}
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_const_value(value_obj: &Value) -> Result<ConstValue, String> {
// Delegate to common generic parser (int/float/bool/string/handle(StringBox))
// Keeps behavior superset of previous (int-only) without changing existing callers.
mirjson_common::parse_const_value_generic(value_obj).map_err(|e| format!("{}", e))
}
fn parse_compare(op: &str) -> Result<crate::mir::types::CompareOp, String> {
use crate::mir::types::CompareOp;
Ok(match op {
"==" => CompareOp::Eq,
"!=" => CompareOp::Ne,
"<" => CompareOp::Lt,
"<=" => CompareOp::Le,
">" => CompareOp::Gt,
">=" => CompareOp::Ge,
s => return Err(format!("unsupported compare op '{}'", s)),
})
}

View File

@ -23,6 +23,8 @@ mod demos;
mod dispatch;
pub mod json_v0_bridge;
mod json_v1_bridge;
pub mod mir_json { pub mod common; }
mod mir_json_v0;
pub mod mir_json_emit;
pub mod modes;
mod pipe_io;
@ -87,6 +89,29 @@ impl NyashRunner {
return;
}
let groups = self.config.as_groups();
// Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec.
if let Some(path) = groups.parser.mir_json_file.as_ref() {
match std::fs::read_to_string(path) {
Ok(text) => {
match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) {
Ok(Some(module)) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); }
Ok(None) => {
if text.contains("\"functions\"") && text.contains("\"blocks\"") {
match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) {
Ok(module) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); }
Err(e) => { eprintln!("❌ MIR JSON v0 parse error: {}", e); std::process::exit(1); }
}
} else {
eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path);
std::process::exit(1);
}
}
Err(e) => { eprintln!("❌ MIR JSON parse error (v1): {}", e); std::process::exit(1); }
}
}
Err(e) => { eprintln!("❌ Error reading MIR JSON {}: {}", path, e); std::process::exit(1); }
}
}
// Early: build
if let Some(cfg_path) = groups.build.path.clone() {
if let Err(e) = self.run_build_mvp(&cfg_path) {

View File

@ -496,11 +496,38 @@ pub fn parse_preludes_to_asts(
eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug);
if debug {
eprintln!("[strip-debug] Error: {}", e);
let es = format!("{}", e);
let lines: Vec<&str> = clean_src.lines().collect();
eprintln!("[strip-debug] Total lines: {}", lines.len());
eprintln!("[strip-debug] Lines 15-25:");
for (idx, line) in lines.iter().enumerate().skip(14).take(11) {
eprintln!(" {:3}: {}", idx + 1, line);
// Try to extract error line number (e.g., "at line 451") and show local context
let mut printed = false;
if let Some(pos) = es.rfind("line ") {
let mut j = pos + 5; // after "line "
let bytes = es.as_bytes();
let mut n: usize = 0; let mut had = false;
while j < bytes.len() {
let c = bytes[j];
if c >= b'0' && c <= b'9' { n = n * 10 + (c - b'0') as usize; j += 1; had = true; } else { break; }
}
if had {
let ln = if n == 0 { 1 } else { n };
let from = ln.saturating_sub(3);
let to = std::cmp::min(lines.len(), ln + 3);
eprintln!("[strip-debug] Context around line {} ({}..={}):", ln, from.max(1), to);
for i in from.max(1)..=to {
let mark = if i == ln { ">>" } else { " " };
if let Some(line) = lines.get(i-1) {
eprintln!("{} {:4}: {}", mark, i, line);
}
}
printed = true;
}
}
if !printed {
eprintln!("[strip-debug] Lines 15-25:");
for (idx, line) in lines.iter().enumerate().skip(14).take(11) {
eprintln!(" {:3}: {}", idx + 1, line);
}
}
eprintln!("[strip-debug] Full clean_src:\n{}\n---", clean_src);
}

View File

@ -180,13 +180,29 @@ pub(super) fn resolve_using_target(
}
return Ok(hit);
}
// Resolve aliases early (provided map) — and then recursively resolve the target
if let Some(v) = aliases.get(tgt) {
if trace {
crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v));
// Resolve aliases early(推移的に)
// - ループ/循環を検出して早期エラー
// - 10段まで防衛的
if let Some(_) = aliases.get(tgt) {
use std::collections::HashSet;
let mut seen: HashSet<String> = HashSet::new();
let mut cur = tgt.to_string();
let mut depth = 0usize;
while let Some(next) = aliases.get(&cur).cloned() {
if trace { crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", cur, next)); }
if !seen.insert(cur.clone()) {
return Err(format!("alias cycle detected at '{}'", cur));
}
cur = next;
depth += 1;
if depth > 10 {
return Err(format!("alias resolution too deep starting at '{}'", tgt));
}
// Continue while next is also an alias; break when concrete
if !aliases.contains_key(&cur) { break; }
}
// Recurse to resolve the alias target into a concrete path/token
let rec = resolve_using_target(v, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?;
// Recurse once into final target to materialize path/token
let rec = resolve_using_target(&cur, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?;
crate::runner::box_index::cache_put(&key, rec.clone());
return Ok(rec);
}