📚 Phase 15 - セルフホスティング戦略の明確化とEXE-first実装
## 主な変更点 ### 🎯 戦略の転換と明確化 - PyVMを開発ツールとして位置づけ(本番経路ではない) - EXE-first戦略を明確に優先(build_compiler_exe.sh実装済み) - Phase順序の整理: 15.2(LLVM)→15.3(コンパイラ)→15.4(VM) ### 🚀 セルフホスティング基盤の実装 - apps/selfhost-compiler/にNyashコンパイラMVP実装 - compiler.nyash: メインエントリー(位置引数対応) - boxes/: parser_box, emitter_box, debug_box分離 - tools/build_compiler_exe.sh: ネイティブEXEビルド+dist配布 - Python MVPパーサーStage-2完成(local/if/loop/call/method/new) ### 📝 ドキュメント整備 - Phase 15 README/ROADMAP更新(Self-Hosting優先明記) - docs/guides/exe-first-wsl.md: WSLクイックスタート追加 - docs/private/papers/: 論文G~L、爆速事件簿41事例収録 ### 🔧 技術的改善 - JSON v0 Bridge: If/Loop PHI生成実装(ChatGPT協力) - PyVM/llvmliteパリティ検証スイート追加 - using/namespace機能(gated実装、Phase 15では非解決) ## 次のステップ 1. パーサー無限ループ修正(未実装関数の実装) 2. EXEビルドとセルフホスティング実証 3. c0→c1→c1'ブートストラップループ確立 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
195
src/bin/ny_mir_builder.rs
Normal file
195
src/bin/ny_mir_builder.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use clap::{Arg, ArgAction, Command, value_parser};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command as PCommand, Stdio};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let matches = Command::new("ny_mir_builder")
|
||||
.about("MIR Builder CLI (Phase-15 EXE-first) — consumes Nyash JSON IR and emits {obj|exe|ll|json}")
|
||||
.arg(Arg::new("in").long("in").value_name("FILE").help("Input JSON IR file (v0/v1)").required(false))
|
||||
.arg(Arg::new("stdin").long("stdin").action(ArgAction::SetTrue).help("Read JSON IR from stdin (default)"))
|
||||
.arg(Arg::new("emit").long("emit").value_name("KIND").default_value("obj").value_parser(["obj","exe","ll","json"]))
|
||||
.arg(Arg::new("out").short('o').value_name("OUT").required(false))
|
||||
.arg(Arg::new("target").long("target").value_name("TRIPLE").required(false))
|
||||
.arg(Arg::new("nyrt").long("nyrt").value_name("DIR").required(false))
|
||||
.arg(Arg::new("verify_llvm").long("verify-llvm").action(ArgAction::SetTrue))
|
||||
.arg(Arg::new("quiet").long("quiet").action(ArgAction::SetTrue))
|
||||
.get_matches();
|
||||
|
||||
// Resolve input
|
||||
let stdin_mode = matches.get_flag("stdin") || !matches.contains_id("in");
|
||||
let in_file: PathBuf = if stdin_mode {
|
||||
// Read stdin to tmp/ny_mir_builder_input.json for re-use
|
||||
let mut buf = Vec::new();
|
||||
io::stdin().read_to_end(&mut buf).expect("read stdin");
|
||||
if buf.is_empty() { eprintln!("error: no input on stdin"); std::process::exit(2); }
|
||||
let cwd_tmp = Path::new("tmp"); let _ = fs::create_dir_all(cwd_tmp);
|
||||
let cwd_path = cwd_tmp.join("ny_mir_builder_input.json");
|
||||
fs::write(&cwd_path, &buf).expect("write cwd tmp json");
|
||||
cwd_path
|
||||
} else {
|
||||
let p = PathBuf::from(matches.get_one::<String>("in").unwrap());
|
||||
if !p.exists() { eprintln!("error: input not found: {}", p.display()); std::process::exit(2); }
|
||||
p
|
||||
};
|
||||
|
||||
let emit = matches.get_one::<String>("emit").unwrap().as_str();
|
||||
let out_path = matches.get_one::<String>("out").map(|s| s.to_string()).unwrap_or_else(|| match emit {
|
||||
"obj" => format!("{}/target/aot_objects/a.o", std::env::current_dir().unwrap().display()),
|
||||
"ll" => format!("{}/target/aot_objects/a.ll", std::env::current_dir().unwrap().display()),
|
||||
"exe" => "a.out".to_string(),
|
||||
"json" => "/dev/stdout".to_string(),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let verify = matches.get_flag("verify_llvm");
|
||||
let quiet = matches.get_flag("quiet");
|
||||
let nyrt_dir = matches.get_one::<String>("nyrt").map(|s| s.to_string()).unwrap_or("crates/nyrt".to_string());
|
||||
|
||||
// Determine sibling nyash binary path (target dir)
|
||||
let nyash_bin = current_dir_bin("nyash");
|
||||
if emit == "json" {
|
||||
if out_path == "/dev/stdout" {
|
||||
let mut f = File::open(&in_file).expect("open in");
|
||||
io::copy(&mut f, &mut io::stdout()).ok();
|
||||
} else {
|
||||
fs::copy(&in_file, &out_path).expect("copy json");
|
||||
}
|
||||
if !quiet { println!("OK json:{}", out_path); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure build dirs
|
||||
let aot_dir = Path::new("target/aot_objects"); let _ = fs::create_dir_all(aot_dir);
|
||||
|
||||
match emit {
|
||||
"ll" => {
|
||||
std::env::set_var("NYASH_LLVM_DUMP_LL", "1");
|
||||
std::env::set_var("NYASH_LLVM_LL_OUT", &out_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
|
||||
if !quiet { println!("OK ll:{}", out_path); }
|
||||
}
|
||||
"obj" => {
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &out_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
// remove stale
|
||||
let _ = fs::remove_file(&out_path);
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&out_path).exists() { eprintln!("error: failed to produce {}", out_path); std::process::exit(4); }
|
||||
if !quiet { println!("OK obj:{}", out_path); }
|
||||
}
|
||||
"exe" => {
|
||||
let obj_path = format!("{}/target/aot_objects/__tmp_mir_builder.o", std::env::current_dir().unwrap().display());
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
||||
if verify { std::env::set_var("NYASH_LLVM_VERIFY", "1"); }
|
||||
std::env::set_var("NYASH_LLVM_USE_HARNESS", "1");
|
||||
let _ = fs::remove_file(&obj_path);
|
||||
run_nyash_pipe(&nyash_bin, &in_file);
|
||||
if !Path::new(&obj_path).exists() { eprintln!("error: failed to produce object {}", obj_path); std::process::exit(4); }
|
||||
// Link with NyRT
|
||||
if let Err(e) = link_exe(&obj_path, &out_path, &nyrt_dir) {
|
||||
eprintln!("error: link failed: {}", e);
|
||||
std::process::exit(5);
|
||||
}
|
||||
if !quiet { println!("OK exe:{}", out_path); }
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_dir_bin(name: &str) -> PathBuf {
|
||||
// Resolve sibling binary in target/<profile>
|
||||
// Try current_exe parent dir first
|
||||
if let Ok(cur) = std::env::current_exe() {
|
||||
if let Some(dir) = cur.parent() {
|
||||
let cand = dir.join(name);
|
||||
if cand.exists() { return cand; }
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let cand = dir.join(format!("{}.exe", name));
|
||||
if cand.exists() { return cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to target/release
|
||||
let mut cand = PathBuf::from("target/release").join(name);
|
||||
if cand.exists() { return cand; }
|
||||
#[cfg(windows)]
|
||||
{
|
||||
cand = PathBuf::from("target/release").join(format!("{}.exe", name));
|
||||
}
|
||||
cand
|
||||
}
|
||||
|
||||
fn run_nyash_pipe(nyash_bin: &Path, json_file: &Path) {
|
||||
// Pipe the JSON into nyash with --ny-parser-pipe and llvm backend
|
||||
let file = File::open(json_file).expect("open json");
|
||||
let mut cmd = PCommand::new(nyash_bin);
|
||||
cmd.arg("--backend").arg("llvm").arg("--ny-parser-pipe");
|
||||
cmd.stdin(Stdio::from(file));
|
||||
// Quiet child output
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() != Some("1") {
|
||||
cmd.stdout(Stdio::null());
|
||||
}
|
||||
let status = cmd.status().expect("run nyash");
|
||||
if !status.success() {
|
||||
eprintln!("error: nyash harness failed (status {:?})", status.code());
|
||||
std::process::exit(4);
|
||||
}
|
||||
}
|
||||
|
||||
fn link_exe(obj_path: &str, out_path: &str, nyrt_dir: &str) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Prefer lld-link, then link.exe, fallback to cc
|
||||
let nyrt_release = format!("{}/target/release", nyrt_dir.replace('\\', "/"));
|
||||
let lib_nyrt_lib = format!("{}/nyrt.lib", nyrt_release);
|
||||
let lib_nyrt_a = format!("{}/libnyrt.a", nyrt_release);
|
||||
if which::which("lld-link").is_ok() {
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
args.push(format!("/OUT:{}", out_path));
|
||||
args.push(obj_path.to_string());
|
||||
// Provide LIBPATH and library name (prefer nyrt.lib)
|
||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
|
||||
// lld-link cannot consume .a directly; rely on .lib
|
||||
let status = PCommand::new("lld-link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
return Err(format!("lld-link failed: status {:?}", status.code()));
|
||||
}
|
||||
if which::which("link").is_ok() {
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
args.push(format!("/OUT:{}", out_path));
|
||||
args.push(obj_path.to_string());
|
||||
args.push(format!("/LIBPATH:{}", nyrt_release));
|
||||
if std::path::Path::new(&lib_nyrt_lib).exists() { args.push("nyrt.lib".to_string()); }
|
||||
let status = PCommand::new("link").args(args.iter().map(|s| s.as_str())).status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
return Err(format!("link.exe failed: status {:?}", status.code()));
|
||||
}
|
||||
// Fallback: try cc with MinGW-like flags
|
||||
let status = PCommand::new("cc")
|
||||
.args([obj_path])
|
||||
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||
.args(["-lnyrt", "-o", out_path])
|
||||
.status().map_err(|e| e.to_string())?;
|
||||
if status.success() { return Ok(()); }
|
||||
return Err(format!("cc link failed: status {:?}", status.code()));
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let status = PCommand::new("cc")
|
||||
.args([obj_path])
|
||||
.args(["-L", "target/release"])
|
||||
.args(["-L", &format!("{}/target/release", nyrt_dir)])
|
||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive"])
|
||||
.args(["-lpthread", "-ldl", "-lm", "-o", out_path])
|
||||
.status().map_err(|e| e.to_string())?;
|
||||
if status.success() { Ok(()) } else { Err(format!("cc failed: status {:?}", status.code())) }
|
||||
}
|
||||
}
|
||||
10
src/cli.rs
10
src/cli.rs
@ -103,6 +103,12 @@ impl CliConfig {
|
||||
.value_name("FILE")
|
||||
.help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ny-compiler-args")
|
||||
.long("ny-compiler-args")
|
||||
.value_name("ARGS")
|
||||
.help("Pass additional args to selfhost child compiler (equivalent to NYASH_NY_COMPILER_CHILD_ARGS)")
|
||||
)
|
||||
|
||||
.arg(
|
||||
Arg::new("debug-fuel")
|
||||
@ -366,6 +372,10 @@ impl CliConfig {
|
||||
|
||||
/// Convert ArgMatches to CliConfig
|
||||
fn from_matches(matches: &ArgMatches) -> Self {
|
||||
// Side-effect: forward child args for selfhost compiler via env
|
||||
if let Some(a) = matches.get_one::<String>("ny-compiler-args") {
|
||||
std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a);
|
||||
}
|
||||
Self {
|
||||
file: matches.get_one::<String>("file").cloned(),
|
||||
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
|
||||
|
||||
@ -124,17 +124,31 @@ class PyVM:
|
||||
op = inst.get("op")
|
||||
|
||||
if op == "phi":
|
||||
# incoming: [[vid, pred_bid], ...]
|
||||
# incoming: prefer [[vid, pred_bid]], but accept [pred_bid, vid] robustly
|
||||
incoming = inst.get("incoming", [])
|
||||
chosen: Any = None
|
||||
# Prefer predecessor match; otherwise fallback to first
|
||||
for vid, pb in incoming:
|
||||
if prev is not None and int(pb) == int(prev):
|
||||
chosen = regs.get(int(vid))
|
||||
for pair in incoming:
|
||||
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
||||
continue
|
||||
a, b = pair[0], pair[1]
|
||||
# Case 1: [vid, pred]
|
||||
if prev is not None and int(b) == int(prev) and int(a) in regs:
|
||||
chosen = regs.get(int(a))
|
||||
break
|
||||
# Case 2: [pred, vid]
|
||||
if prev is not None and int(a) == int(prev) and int(b) in regs:
|
||||
chosen = regs.get(int(b))
|
||||
break
|
||||
if chosen is None and incoming:
|
||||
vid, _ = incoming[0]
|
||||
chosen = regs.get(int(vid))
|
||||
# Fallback to first element that resolves to a known vid
|
||||
for pair in incoming:
|
||||
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
||||
continue
|
||||
a, b = pair[0], pair[1]
|
||||
if int(a) in regs:
|
||||
chosen = regs.get(int(a)); break
|
||||
if int(b) in regs:
|
||||
chosen = regs.get(int(b)); break
|
||||
self._set(regs, inst.get("dst"), chosen)
|
||||
i += 1
|
||||
continue
|
||||
@ -229,6 +243,30 @@ class PyVM:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if op == "unop":
|
||||
kind = inst.get("kind")
|
||||
src = self._read(regs, inst.get("src"))
|
||||
out: Any
|
||||
if kind == "neg":
|
||||
if isinstance(src, (int, float)):
|
||||
out = -src
|
||||
elif src is None:
|
||||
out = 0
|
||||
else:
|
||||
try:
|
||||
out = -int(src)
|
||||
except Exception:
|
||||
out = 0
|
||||
elif kind == "not":
|
||||
out = 0 if self._truthy(src) else 1
|
||||
elif kind == "bitnot":
|
||||
out = ~int(src) if src is not None else -1
|
||||
else:
|
||||
out = None
|
||||
self._set(regs, inst.get("dst"), out)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if op == "newbox":
|
||||
btype = inst.get("type")
|
||||
if btype == "ConsoleBox":
|
||||
@ -236,6 +274,10 @@ class PyVM:
|
||||
elif btype == "StringBox":
|
||||
# empty string instance
|
||||
val = ""
|
||||
elif btype == "ArrayBox":
|
||||
val = {"__box__": "ArrayBox", "__arr": []}
|
||||
elif btype == "MapBox":
|
||||
val = {"__box__": "MapBox", "__map": {}}
|
||||
else:
|
||||
# Unknown box -> opaque
|
||||
val = {"__box__": btype}
|
||||
@ -302,6 +344,58 @@ class PyVM:
|
||||
out = os.path.join(base, rel)
|
||||
else:
|
||||
out = None
|
||||
# ArrayBox minimal methods
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "ArrayBox":
|
||||
arr = recv.get("__arr", [])
|
||||
if method in ("len", "size"):
|
||||
out = len(arr)
|
||||
elif method == "get":
|
||||
idx = int(args[0]) if args else 0
|
||||
out = arr[idx] if 0 <= idx < len(arr) else None
|
||||
elif method == "set":
|
||||
idx = int(args[0]) if len(args) > 0 else 0
|
||||
val = args[1] if len(args) > 1 else None
|
||||
if 0 <= idx < len(arr):
|
||||
arr[idx] = val
|
||||
elif idx == len(arr):
|
||||
arr.append(val)
|
||||
else:
|
||||
# extend with None up to idx, then set
|
||||
while len(arr) < idx:
|
||||
arr.append(None)
|
||||
arr.append(val)
|
||||
out = 0
|
||||
elif method == "push":
|
||||
val = args[0] if args else None
|
||||
arr.append(val)
|
||||
out = len(arr)
|
||||
elif method == "toString":
|
||||
out = "[" + ",".join(str(x) for x in arr) + "]"
|
||||
else:
|
||||
out = None
|
||||
recv["__arr"] = arr
|
||||
# MapBox minimal methods
|
||||
elif isinstance(recv, dict) and recv.get("__box__") == "MapBox":
|
||||
m = recv.get("__map", {})
|
||||
if method == "size":
|
||||
out = len(m)
|
||||
elif method == "has":
|
||||
key = str(args[0]) if args else ""
|
||||
out = 1 if key in m else 0
|
||||
elif method == "get":
|
||||
key = str(args[0]) if args else ""
|
||||
out = m.get(key)
|
||||
elif method == "set":
|
||||
key = str(args[0]) if len(args) > 0 else ""
|
||||
val = args[1] if len(args) > 1 else None
|
||||
m[key] = val
|
||||
out = 0
|
||||
elif method == "toString":
|
||||
items = ",".join(f"{k}:{m[k]}" for k in m)
|
||||
out = "{" + items + "}"
|
||||
else:
|
||||
out = None
|
||||
recv["__map"] = m
|
||||
elif method == "esc_json":
|
||||
# Escape backslash and double-quote in the given string argument
|
||||
s = args[0] if args else ""
|
||||
|
||||
@ -180,6 +180,7 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(
|
||||
"shortcircuit",
|
||||
"<json_v0>"
|
||||
);
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] op={} rhs_bb={} fall_bb={} merge_bb={}", if is_and {"and"} else {"or"}, rhs_bb.0, fall_bb.0, merge_bb.0); }
|
||||
// false/true constant in fall_bb depending on op
|
||||
let cdst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(fall_bb) {
|
||||
@ -187,15 +188,16 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(
|
||||
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
// evaluate rhs in rhs_bb
|
||||
let (rval, _rhs_end) = lower_expr(f, rhs_bb, rhs)?;
|
||||
if let Some(bb) = f.get_block_mut(rhs_bb) {
|
||||
// evaluate rhs starting at rhs_bb and ensure the terminal block jumps to merge
|
||||
let (rval, rhs_end) = lower_expr(f, rhs_bb, rhs)?;
|
||||
if let Some(bb) = f.get_block_mut(rhs_end) {
|
||||
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
|
||||
}
|
||||
// merge with phi
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] rhs_end={} jump->merge_bb={}", rhs_end.0, merge_bb.0); }
|
||||
// merge with phi (use actual predecessors rhs_end and fall_bb)
|
||||
let out = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] });
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] });
|
||||
}
|
||||
Ok((out, merge_bb))
|
||||
}
|
||||
@ -213,6 +215,16 @@ fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(
|
||||
Ok((dst, cur))
|
||||
}
|
||||
ExprV0::Method { recv, method, args } => {
|
||||
// Heuristic: new ConsoleBox().println(x) → externcall env.console.log(x)
|
||||
let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox");
|
||||
if recv_is_console_new && (method == "println" || method == "print" || method == "log") {
|
||||
let (arg_ids, cur2) = lower_args(f, cur_bb, args)?;
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur2) {
|
||||
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ });
|
||||
}
|
||||
return Ok((dst, cur2));
|
||||
}
|
||||
let (recv_v, cur) = lower_expr(f, cur_bb, recv)?;
|
||||
let (arg_ids, cur2) = lower_args(f, cur, args)?;
|
||||
let dst = f.next_value_id();
|
||||
@ -275,6 +287,15 @@ fn lower_expr_with_vars(
|
||||
Ok((dst, cur))
|
||||
}
|
||||
ExprV0::Method { recv, method, args } => {
|
||||
let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox");
|
||||
if recv_is_console_new && (method == "println" || method == "print" || method == "log") {
|
||||
let (arg_ids, cur2) = lower_args_with_vars(f, cur_bb, args, vars)?;
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur2) {
|
||||
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ });
|
||||
}
|
||||
return Ok((dst, cur2));
|
||||
}
|
||||
let (recv_v, cur) = lower_expr_with_vars(f, cur_bb, recv, vars)?;
|
||||
let (arg_ids, cur2) = lower_args_with_vars(f, cur, args, vars)?;
|
||||
let dst = f.next_value_id();
|
||||
@ -341,10 +362,10 @@ fn lower_expr_with_vars(
|
||||
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
let (rval, _rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?;
|
||||
if let Some(bb) = f.get_block_mut(rhs_bb) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } }
|
||||
let (rval, rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?;
|
||||
if let Some(bb) = f.get_block_mut(rhs_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } }
|
||||
let out = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] }); }
|
||||
if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] }); }
|
||||
Ok((out, merge_bb))
|
||||
}
|
||||
_ => lower_expr(f, cur_bb, e),
|
||||
@ -603,7 +624,8 @@ fn lex(input: &str) -> Result<Vec<Tok>, String> {
|
||||
let mut toks = Vec::new();
|
||||
while i < n {
|
||||
let c = bytes[i] as char;
|
||||
if c.is_whitespace() { i += 1; continue; }
|
||||
// Treat semicolon as whitespace (Stage-1 minimal ASI: optional ';')
|
||||
if c.is_whitespace() || c == ';' { i += 1; continue; }
|
||||
match c {
|
||||
'+' => { toks.push(Tok::Plus); i+=1; }
|
||||
'-' => { toks.push(Tok::Minus); i+=1; }
|
||||
|
||||
@ -44,6 +44,10 @@ pub fn emit_mir_json_for_harness(
|
||||
// Non-PHI
|
||||
for inst in &bb.instructions {
|
||||
match inst {
|
||||
I::UnaryOp { dst, op, operand } => {
|
||||
let kind = match op { nyash_rust::mir::UnaryOp::Neg => "neg", nyash_rust::mir::UnaryOp::Not => "not", nyash_rust::mir::UnaryOp::BitNot => "bitnot" };
|
||||
insts.push(json!({"op":"unop","kind": kind, "src": operand.as_u32(), "dst": dst.as_u32()}));
|
||||
}
|
||||
I::Const { dst, value } => {
|
||||
match value {
|
||||
nyash_rust::mir::ConstValue::Integer(i) => {
|
||||
@ -185,3 +189,147 @@ pub fn emit_mir_json_for_harness(
|
||||
std::fs::write(path, serde_json::to_string_pretty(&root).unwrap())
|
||||
.map_err(|e| format!("write mir json: {}", e))
|
||||
}
|
||||
|
||||
/// Variant for the bin crate's local MIR type
|
||||
pub fn emit_mir_json_for_harness_bin(
|
||||
module: &crate::mir::MirModule,
|
||||
path: &std::path::Path,
|
||||
) -> Result<(), String> {
|
||||
use crate::mir::{MirInstruction as I, BinaryOp as B, CompareOp as C, MirType};
|
||||
let mut funs = Vec::new();
|
||||
for (name, f) in &module.functions {
|
||||
let mut blocks = Vec::new();
|
||||
let mut ids: Vec<_> = f.blocks.keys().copied().collect();
|
||||
ids.sort();
|
||||
for bid in ids {
|
||||
if let Some(bb) = f.blocks.get(&bid) {
|
||||
let mut insts = Vec::new();
|
||||
for inst in &bb.instructions {
|
||||
if let I::Phi { dst, inputs } = inst {
|
||||
let incoming: Vec<_> = inputs
|
||||
.iter()
|
||||
.map(|(b, v)| json!([v.as_u32(), b.as_u32()]))
|
||||
.collect();
|
||||
let all_str = inputs.iter().all(|(_b, v)| {
|
||||
match f.metadata.value_types.get(v) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
if all_str {
|
||||
insts.push(json!({
|
||||
"op":"phi","dst": dst.as_u32(), "incoming": incoming,
|
||||
"dst_type": {"kind":"handle","box_type":"StringBox"}
|
||||
}));
|
||||
} else {
|
||||
insts.push(json!({"op":"phi","dst": dst.as_u32(), "incoming": incoming}));
|
||||
}
|
||||
}
|
||||
}
|
||||
for inst in &bb.instructions {
|
||||
match inst {
|
||||
I::Const { dst, value } => {
|
||||
match value {
|
||||
crate::mir::ConstValue::Integer(i) => {
|
||||
insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "i64", "value": i}}));
|
||||
}
|
||||
crate::mir::ConstValue::Float(fv) => {
|
||||
insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "f64", "value": fv}}));
|
||||
}
|
||||
crate::mir::ConstValue::Bool(b) => {
|
||||
insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "i64", "value": if *b {1} else {0}}}));
|
||||
}
|
||||
crate::mir::ConstValue::String(s) => {
|
||||
insts.push(json!({
|
||||
"op":"const",
|
||||
"dst": dst.as_u32(),
|
||||
"value": {
|
||||
"type": {"kind":"handle","box_type":"StringBox"},
|
||||
"value": s
|
||||
}
|
||||
}));
|
||||
}
|
||||
crate::mir::ConstValue::Null | crate::mir::ConstValue::Void => {
|
||||
insts.push(json!({"op":"const","dst": dst.as_u32(), "value": {"type": "void", "value": 0}}));
|
||||
}
|
||||
}
|
||||
}
|
||||
I::BinOp { dst, op, lhs, rhs } => {
|
||||
let op_s = match op { B::Add=>"+",B::Sub=>"-",B::Mul=>"*",B::Div=>"/",B::Mod=>"%",B::BitAnd=>"&",B::BitOr=>"|",B::BitXor=>"^",B::Shl=>"<<",B::Shr=>">>",B::And=>"&",B::Or=>"|"};
|
||||
let mut obj = json!({"op":"binop","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
if matches!(op, B::Add) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
let rhs_is_str = match f.metadata.value_types.get(rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => false,
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
obj["dst_type"] = json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::Compare { dst, op, lhs, rhs } => {
|
||||
let op_s = match op { C::Eq=>"==",C::Ne=>"!=",C::Lt=>"<",C::Le=>"<=",C::Gt=>">",C::Ge=>">=" };
|
||||
insts.push(json!({"op":"compare","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()}));
|
||||
}
|
||||
I::ExternCall { dst, iface_name, method_name, args, .. } => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
let mut obj = json!({
|
||||
"op":"externcall","func": format!("{}.{}", iface_name, method_name), "args": args_a,
|
||||
"dst": dst.map(|d| d.as_u32()),
|
||||
});
|
||||
if iface_name == "env.console" { if dst.is_some() { obj["dst_type"] = json!("i64"); } }
|
||||
insts.push(obj);
|
||||
}
|
||||
I::BoxCall { dst, box_val, method, args, .. } => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
let mut obj = json!({
|
||||
"op":"boxcall","box": box_val.as_u32(), "method": method, "args": args_a, "dst": dst.map(|d| d.as_u32())
|
||||
});
|
||||
let m = method.as_str();
|
||||
let dst_ty = if m == "substring" || m == "dirname" || m == "join" || m == "read_all" || m == "read" {
|
||||
Some(json!({"kind":"handle","box_type":"StringBox"}))
|
||||
} else if m == "length" || m == "lastIndexOf" {
|
||||
Some(json!("i64"))
|
||||
} else { None };
|
||||
if let Some(t) = dst_ty { obj["dst_type"] = t; }
|
||||
insts.push(obj);
|
||||
}
|
||||
I::NewBox { dst, box_type, args } => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
insts.push(json!({"op":"newbox","type": box_type, "args": args_a, "dst": dst.as_u32()}));
|
||||
}
|
||||
I::Branch { condition, then_bb, else_bb } => {
|
||||
insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()}));
|
||||
}
|
||||
I::Jump { target } => {
|
||||
insts.push(json!({"op":"jump","target": target.as_u32()}));
|
||||
}
|
||||
I::Return { value } => {
|
||||
insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())}));
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
if let Some(term) = &bb.terminator { match term {
|
||||
I::Return { value } => insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())})),
|
||||
I::Jump { target } => insts.push(json!({"op":"jump","target": target.as_u32()})),
|
||||
I::Branch { condition, then_bb, else_bb } => insts.push(json!({"op":"branch","cond": condition.as_u32(), "then": then_bb.as_u32(), "else": else_bb.as_u32()})),
|
||||
_ => {} } }
|
||||
blocks.push(json!({"id": bid.as_u32(), "instructions": insts}));
|
||||
}
|
||||
}
|
||||
let params: Vec<_> = f.params.iter().map(|v| v.as_u32()).collect();
|
||||
funs.push(json!({"name": name, "params": params, "blocks": blocks}));
|
||||
}
|
||||
let root = json!({"functions": funs});
|
||||
std::fs::write(path, serde_json::to_string_pretty(&root).unwrap())
|
||||
.map_err(|e| format!("write mir json: {}", e))
|
||||
}
|
||||
|
||||
@ -126,6 +126,30 @@ impl NyashRunner {
|
||||
for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } }
|
||||
}
|
||||
if using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); }
|
||||
// nyash.toml: load [modules] and [using.paths]
|
||||
if std::path::Path::new("nyash.toml").exists() {
|
||||
if let Ok(text) = fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
|
||||
for (k, v) in mods.iter() {
|
||||
if let Some(path) = v.as_str() {
|
||||
pending_modules.push((k.to_string(), path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) {
|
||||
if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) {
|
||||
for p in paths_arr {
|
||||
if let Some(s) = p.as_str() {
|
||||
let s = s.trim();
|
||||
if !s.is_empty() { using_paths.push(s.to_string()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Modules mapping from env (e.g., FOO=path)
|
||||
if let Ok(ms) = std::env::var("NYASH_MODULES") {
|
||||
for ent in ms.split(',') {
|
||||
@ -176,7 +200,54 @@ impl NyashRunner {
|
||||
Ok(module) => {
|
||||
// Optional dump via env verbose
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
// Execute via MIR interpreter
|
||||
// Optional: delegate to PyVM when NYASH_PIPE_USE_PYVM=1
|
||||
if std::env::var("NYASH_PIPE_USE_PYVM").ok().as_deref() == Some("1") {
|
||||
let py = which::which("python3").ok();
|
||||
if let Some(py3) = py {
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if runner.exists() {
|
||||
// Emit MIR(JSON) for PyVM
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_pyvm_mir.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Bridge] using PyVM (pipe) → {}", mir_json_path.display());
|
||||
}
|
||||
// Determine entry function hint (prefer Main.main if present)
|
||||
let entry = if module.functions.contains_key("Main.main") { "Main.main" }
|
||||
else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||
let status = std::process::Command::new(py3)
|
||||
.args([
|
||||
runner.to_string_lossy().as_ref(),
|
||||
"--in",
|
||||
&mir_json_path.display().to_string(),
|
||||
"--entry",
|
||||
entry,
|
||||
])
|
||||
.status()
|
||||
.map_err(|e| format!("spawn pyvm: {}", e))
|
||||
.unwrap();
|
||||
let code = status.code().unwrap_or(1);
|
||||
if !status.success() {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("❌ PyVM (pipe) failed (status={})", code);
|
||||
}
|
||||
}
|
||||
std::process::exit(code);
|
||||
} else {
|
||||
eprintln!("❌ PyVM runner not found: {}", runner.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ python3 not found in PATH. Install Python 3 to use PyVM with --ny-parser-pipe.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
// Default: Execute via MIR interpreter
|
||||
self.execute_mir_module(&module);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4,6 +4,10 @@ use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
|
||||
// Use the library crate's plugin init module rather than the bin crate root
|
||||
use nyash_rust::runner_plugin_init;
|
||||
use std::{fs, process};
|
||||
use std::io::Read;
|
||||
use std::process::Stdio;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::thread::sleep;
|
||||
|
||||
// limited directory walk: add matching files ending with .nyash and given leaf name
|
||||
fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
|
||||
@ -157,68 +161,297 @@ impl NyashRunner {
|
||||
Ok(c) => c,
|
||||
Err(e) => { eprintln!("[ny-compiler] read error: {}", e); return false; }
|
||||
};
|
||||
// Write to tmp/ny_parser_input.ny (as expected by Ny parser v0)
|
||||
// Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file)
|
||||
let enable_using = std::env::var("NYASH_ENABLE_USING").ok().as_deref() == Some("1");
|
||||
let mut code_ref: std::borrow::Cow<'_, str> = std::borrow::Cow::Borrowed(&code);
|
||||
if enable_using {
|
||||
let mut out = String::with_capacity(code.len());
|
||||
let mut used_names: Vec<(String, Option<String>)> = Vec::new();
|
||||
for line in code.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("using ") {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] stripped(line→selfhost): {}", line);
|
||||
}
|
||||
let rest0 = t.strip_prefix("using ").unwrap().trim();
|
||||
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
|
||||
let (target, alias) = if let Some(pos) = rest0.find(" as ") {
|
||||
(rest0[..pos].trim().to_string(), Some(rest0[pos+4..].trim().to_string()))
|
||||
} else { (rest0.to_string(), None) };
|
||||
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||
if is_path {
|
||||
let path = target.trim_matches('"').to_string();
|
||||
let name = alias.clone().unwrap_or_else(|| {
|
||||
std::path::Path::new(&path).file_stem().and_then(|s| s.to_str()).unwrap_or("module").to_string()
|
||||
});
|
||||
used_names.push((name, Some(path)));
|
||||
} else {
|
||||
used_names.push((target, alias));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
out.push_str(line);
|
||||
out.push('\n');
|
||||
}
|
||||
// Register modules into minimal registry with best-effort path resolution
|
||||
for (ns_or_alias, alias_or_path) in used_names {
|
||||
if let Some(path) = alias_or_path {
|
||||
let sb = crate::box_trait::StringBox::new(path);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
} else {
|
||||
let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/"));
|
||||
let exists = std::path::Path::new(&rel).exists();
|
||||
let path_or_ns = if exists { rel } else { ns_or_alias.clone() };
|
||||
let sb = crate::box_trait::StringBox::new(path_or_ns);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
}
|
||||
}
|
||||
code_ref = std::borrow::Cow::Owned(out);
|
||||
}
|
||||
|
||||
// Write to tmp/ny_parser_input.ny (as expected by Ny parser v0), unless forced to reuse existing tmp
|
||||
let use_tmp_only = std::env::var("NYASH_NY_COMPILER_USE_TMP_ONLY").ok().as_deref() == Some("1");
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
if let Err(e) = std::fs::create_dir_all(tmp_dir) {
|
||||
eprintln!("[ny-compiler] mkdir tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
let tmp_path = tmp_dir.join("ny_parser_input.ny");
|
||||
match std::fs::File::create(&tmp_path) {
|
||||
Ok(mut f) => {
|
||||
if let Err(e) = f.write_all(code.as_bytes()) {
|
||||
eprintln!("[ny-compiler] write tmp failed: {}", e);
|
||||
if !use_tmp_only {
|
||||
match std::fs::File::create(&tmp_path) {
|
||||
Ok(mut f) => {
|
||||
if let Err(e) = f.write_all(code_ref.as_bytes()) {
|
||||
eprintln!("[ny-compiler] write tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
||||
}
|
||||
}
|
||||
// EXE-first: if requested, try external parser EXE (nyash_compiler)
|
||||
if std::env::var("NYASH_USE_NY_COMPILER_EXE").ok().as_deref() == Some("1") {
|
||||
// Resolve parser EXE path
|
||||
let exe_path = if let Ok(p) = std::env::var("NYASH_NY_COMPILER_EXE_PATH") {
|
||||
std::path::PathBuf::from(p)
|
||||
} else {
|
||||
let mut p = std::path::PathBuf::from("dist/nyash_compiler");
|
||||
#[cfg(windows)]
|
||||
{ p.push("nyash_compiler.exe"); }
|
||||
#[cfg(not(windows))]
|
||||
{ p.push("nyash_compiler"); }
|
||||
if !p.exists() {
|
||||
// Try PATH
|
||||
if let Ok(w) = which::which("nyash_compiler") { w } else { p }
|
||||
} else { p }
|
||||
};
|
||||
if exe_path.exists() {
|
||||
let mut cmd = std::process::Command::new(&exe_path);
|
||||
// Prefer passing the original filename directly (parser EXE accepts positional path)
|
||||
cmd.arg(filename);
|
||||
// Gates
|
||||
if std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().as_deref() == Some("1") { cmd.arg("--min-json"); }
|
||||
if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") { cmd.arg("--read-tmp"); }
|
||||
if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") { for tok in raw.split_whitespace() { cmd.arg(tok); } }
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
||||
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => { eprintln!("[ny-compiler] exe spawn failed: {}", e); return false; } };
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
let start = Instant::now();
|
||||
let mut timed_out = false;
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_status)) => { break; }
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) { let _ = child.kill(); let _ = child.wait(); timed_out = true; break; }
|
||||
sleep(Duration::from_millis(10));
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] exe wait error: {}", e); return false; }
|
||||
}
|
||||
}
|
||||
let mut out_buf = Vec::new();
|
||||
let mut err_buf = Vec::new();
|
||||
if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); }
|
||||
if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); }
|
||||
if timed_out {
|
||||
let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::<String>();
|
||||
eprintln!("[ny-compiler] exe timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||
return false;
|
||||
}
|
||||
let stdout = match String::from_utf8(out_buf) { Ok(s) => s, Err(_) => String::new() };
|
||||
let mut json_line = String::new();
|
||||
for line in stdout.lines() { let t = line.trim(); if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; } }
|
||||
if json_line.is_empty() {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
let head: String = stdout.chars().take(200).collect();
|
||||
let errh: String = String::from_utf8_lossy(&err_buf).chars().take(200).collect();
|
||||
eprintln!("[ny-compiler] exe produced no JSON; stdout(head)='{}' stderr(head)='{}'", head.replace('\n', "\\n"), errh.replace('\n', "\\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Parse JSON v0 → MIR module
|
||||
match json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
println!("🚀 Ny compiler EXE path (ny→json_v0) ON");
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or_else(|_| "1".to_string()) == "1";
|
||||
if emit_only {
|
||||
return false;
|
||||
} else {
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] JSON parse failed (exe): {}", e); return false; }
|
||||
}
|
||||
} else {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[ny-compiler] exe not found at {}", exe_path.display()); }
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
||||
}
|
||||
|
||||
// Locate current exe to invoke Ny VM for the Ny parser program
|
||||
let exe = match std::env::current_exe() {
|
||||
Ok(p) => p,
|
||||
Err(e) => { eprintln!("[ny-compiler] current_exe failed: {}", e); return false; }
|
||||
};
|
||||
// Prefer new selfhost-compiler entry; fallback to legacy ny_parser_v0
|
||||
// Select selfhost compiler entry
|
||||
// NYASH_NY_COMPILER_PREF=legacy|new|auto (default auto: prefer new when exists)
|
||||
let cand_new = std::path::Path::new("apps/selfhost-compiler/compiler.nyash");
|
||||
let cand_old = std::path::Path::new("apps/selfhost/parser/ny_parser_v0/main.nyash");
|
||||
let parser_prog = if cand_new.exists() { cand_new } else { cand_old };
|
||||
let pref = std::env::var("NYASH_NY_COMPILER_PREF").ok();
|
||||
let parser_prog = match pref.as_deref() {
|
||||
Some("legacy") => cand_old,
|
||||
Some("new") => cand_new,
|
||||
_ => if cand_new.exists() { cand_new } else { cand_old },
|
||||
};
|
||||
if !parser_prog.exists() { eprintln!("[ny-compiler] compiler program not found: {}", parser_prog.display()); return false; }
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
cmd.arg("--backend").arg("vm").arg(parser_prog);
|
||||
// Gate: pass script args to child parser program
|
||||
// - NYASH_NY_COMPILER_MIN_JSON=1 → "-- --min-json"
|
||||
// - NYASH_SELFHOST_READ_TMP=1 → "-- --read-tmp"
|
||||
// - NYASH_NY_COMPILER_CHILD_ARGS: additional raw args (split by whitespace)
|
||||
let min_json = std::env::var("NYASH_NY_COMPILER_MIN_JSON").ok().unwrap_or_else(|| "0".to_string());
|
||||
if min_json == "1" { cmd.arg("--").arg("--min-json"); }
|
||||
if std::env::var("NYASH_SELFHOST_READ_TMP").ok().as_deref() == Some("1") {
|
||||
cmd.arg("--").arg("--read-tmp");
|
||||
}
|
||||
if let Ok(raw) = std::env::var("NYASH_NY_COMPILER_CHILD_ARGS") {
|
||||
for tok in raw.split_whitespace() { cmd.arg(tok); }
|
||||
}
|
||||
// Propagate minimal env; disable plugins to reduce noise
|
||||
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||
// Suppress parent runner's result printing in child
|
||||
cmd.env("NYASH_JSON_ONLY", "1");
|
||||
let out = match cmd.output() {
|
||||
Ok(o) => o,
|
||||
// Propagate optional gates to child (if present)
|
||||
if let Ok(v) = std::env::var("NYASH_JSON_INCLUDE_USINGS") { cmd.env("NYASH_JSON_INCLUDE_USINGS", v); }
|
||||
if let Ok(v) = std::env::var("NYASH_ENABLE_USING") { cmd.env("NYASH_ENABLE_USING", v); }
|
||||
// Child timeout guard (Hotfix for potential infinite loop in child Ny parser)
|
||||
// Config: NYASH_NY_COMPILER_TIMEOUT_MS (default 2000ms)
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(2000);
|
||||
let mut cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => { eprintln!("[ny-compiler] spawn failed: {}", e); return false; }
|
||||
};
|
||||
if !out.status.success() {
|
||||
if let Ok(s) = String::from_utf8(out.stderr) { eprintln!("[ny-compiler] parser stderr:\n{}", s); }
|
||||
return false;
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
let start = Instant::now();
|
||||
let mut timed_out = false;
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_status)) => { break; }
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(10));
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] wait error: {}", e); return false; }
|
||||
}
|
||||
}
|
||||
// Collect any available output
|
||||
let mut out_buf = Vec::new();
|
||||
let mut err_buf = Vec::new();
|
||||
if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); }
|
||||
if let Some(mut s) = ch_stderr { let _ = s.read_to_end(&mut err_buf); }
|
||||
if timed_out {
|
||||
let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::<String>();
|
||||
eprintln!("[ny-compiler] child timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||
}
|
||||
let stdout = match String::from_utf8(out_buf) { Ok(s) => s, Err(_) => String::new() };
|
||||
if timed_out {
|
||||
// Fall back path will be taken below when json_line remains empty
|
||||
} else if let Ok(s) = String::from_utf8(err_buf.clone()) {
|
||||
// If the child exited non-zero and printed stderr, surface it and fallback
|
||||
// We cannot easily access ExitStatus here after try_wait loop; rely on JSON detection path.
|
||||
if s.trim().len() > 0 && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[ny-compiler] parser stderr:\n{}", s);
|
||||
}
|
||||
}
|
||||
let stdout = match String::from_utf8(out.stdout) { Ok(s) => s, Err(_) => String::new() };
|
||||
let mut json_line = String::new();
|
||||
for line in stdout.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\":0") { json_line = t.to_string(); break; }
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; }
|
||||
}
|
||||
if json_line.is_empty() {
|
||||
// Fallback: try Python MVP parser to produce JSON v0 from the same tmp source.
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
let head: String = stdout.chars().take(200).collect();
|
||||
eprintln!("[ny-compiler] JSON not found in child stdout (head): {}", head.replace('\n', "\\n"));
|
||||
eprintln!("[ny-compiler] falling back to tools/ny_parser_mvp.py for this input");
|
||||
}
|
||||
return false;
|
||||
let py = which::which("python3").ok();
|
||||
if let Some(py3) = py {
|
||||
let script = std::path::Path::new("tools/ny_parser_mvp.py");
|
||||
if script.exists() {
|
||||
let out2 = std::process::Command::new(py3)
|
||||
.arg(script)
|
||||
.arg(tmp_path.as_os_str())
|
||||
.output();
|
||||
match out2 {
|
||||
Ok(o2) if o2.status.success() => {
|
||||
if let Ok(s2) = String::from_utf8(o2.stdout) {
|
||||
// pick the first JSON-ish line
|
||||
for line in s2.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { json_line = t.to_string(); break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(o2) => {
|
||||
let msg = String::from_utf8_lossy(&o2.stderr);
|
||||
eprintln!("[ny-compiler] python parser failed: {}", msg);
|
||||
}
|
||||
Err(e2) => {
|
||||
eprintln!("[ny-compiler] spawn python3 failed: {}", e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if json_line.is_empty() { return false; }
|
||||
}
|
||||
// Parse JSON v0 → MIR module
|
||||
match json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Ny compiler MVP (ny→json_v0) path ON");
|
||||
}
|
||||
let emit_only_default = "1".to_string();
|
||||
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY").unwrap_or(emit_only_default) == "1";
|
||||
println!("🚀 Ny compiler MVP (ny→json_v0) path ON");
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
self.execute_mir_module(&module);
|
||||
true
|
||||
if emit_only {
|
||||
// Do not execute; fall back to default path to keep final Result unaffected (Stage‑1 policy)
|
||||
false
|
||||
} else {
|
||||
self.execute_mir_module(&module);
|
||||
true
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] JSON parse failed: {}", e);
|
||||
|
||||
@ -6,6 +6,8 @@ use std::sync::Arc;
|
||||
impl NyashRunner {
|
||||
/// Execute VM mode (split)
|
||||
pub(crate) fn execute_vm_mode(&self, filename: &str) {
|
||||
// Quiet mode for child pipelines (e.g., selfhost compiler JSON emit)
|
||||
let quiet_pipe = std::env::var("NYASH_JSON_ONLY").ok().as_deref() == Some("1");
|
||||
// Enforce plugin-first policy for VM on this branch (deterministic):
|
||||
// - Initialize plugin host if not yet loaded
|
||||
// - Prefer plugin implementations for core boxes
|
||||
@ -176,7 +178,7 @@ impl NyashRunner {
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(result) => {
|
||||
println!("✅ VM execution completed successfully!");
|
||||
if !quiet_pipe { println!("✅ VM execution completed successfully!"); }
|
||||
// Pretty-print with coercions for plugin-backed values
|
||||
// Prefer MIR signature when available, but fall back to runtime coercions to keep VM/JIT consistent.
|
||||
let (ety, sval) = if let Some(func) = compile_result.module.functions.get("main") {
|
||||
@ -237,8 +239,10 @@ impl NyashRunner {
|
||||
("String", s)
|
||||
} else { (result.type_name(), result.to_string_box().value) }
|
||||
};
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
if !quiet_pipe {
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
}
|
||||
},
|
||||
Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user