chore(fmt): add legacy stubs and strip trailing whitespace to unblock cargo fmt
This commit is contained in:
@ -6,8 +6,8 @@
|
||||
* to surface aliases defined in nyash.toml/env.
|
||||
*/
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@ -27,16 +27,21 @@ impl BoxIndex {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
|
||||
for (k, v) in alias_tbl.iter() {
|
||||
if let Some(target) = v.as_str() { aliases.insert(k.to_string(), target.to_string()); }
|
||||
if let Some(target) = v.as_str() {
|
||||
aliases.insert(k.to_string(), target.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||
for ent in raw.split(',') {
|
||||
if let Some((k,v)) = ent.split_once('=') {
|
||||
let k = k.trim(); let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() { aliases.insert(k.to_string(), v.to_string()); }
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
let k = k.trim();
|
||||
let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() {
|
||||
aliases.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,14 +63,29 @@ impl BoxIndex {
|
||||
for (k, v) in plugins_tbl.iter() {
|
||||
// Skip non-table entries (string entries are plugin roots)
|
||||
if let Some(t) = v.as_table() {
|
||||
let prefix = t.get("prefix").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||
let require_prefix = t.get("require_prefix").and_then(|x| x.as_bool()).unwrap_or(false);
|
||||
let expose_short_names = t.get("expose_short_names").and_then(|x| x.as_bool()).unwrap_or(true);
|
||||
let meta = PluginMeta { prefix, require_prefix, expose_short_names };
|
||||
let prefix = t
|
||||
.get("prefix")
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let require_prefix = t
|
||||
.get("require_prefix")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(false);
|
||||
let expose_short_names = t
|
||||
.get("expose_short_names")
|
||||
.and_then(|x| x.as_bool())
|
||||
.unwrap_or(true);
|
||||
let meta = PluginMeta {
|
||||
prefix,
|
||||
require_prefix,
|
||||
expose_short_names,
|
||||
};
|
||||
plugin_meta.insert(k.clone(), meta.clone());
|
||||
if let Some(arr) = t.get("boxes").and_then(|x| x.as_array()) {
|
||||
for b in arr {
|
||||
if let Some(name) = b.as_str() { plugin_meta_by_box.insert(name.to_string(), meta.clone()); }
|
||||
if let Some(name) = b.as_str() {
|
||||
plugin_meta_by_box.insert(name.to_string(), meta.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,22 +107,43 @@ impl BoxIndex {
|
||||
}
|
||||
}
|
||||
|
||||
Self { aliases, plugin_boxes, plugin_meta, plugin_meta_by_box, plugins_require_prefix_global }
|
||||
Self {
|
||||
aliases,
|
||||
plugin_boxes,
|
||||
plugin_meta,
|
||||
plugin_meta_by_box,
|
||||
plugins_require_prefix_global,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_known_plugin_short(name: &str) -> bool {
|
||||
// Prefer global index view
|
||||
if GLOBAL.read().ok().map(|g| g.plugin_boxes.contains(name)).unwrap_or(false) {
|
||||
if GLOBAL
|
||||
.read()
|
||||
.ok()
|
||||
.map(|g| g.plugin_boxes.contains(name))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Env override list
|
||||
if let Ok(raw) = std::env::var("NYASH_KNOWN_PLUGIN_SHORTNAMES") {
|
||||
let set: HashSet<String> = raw.split(',').map(|s| s.trim().to_string()).collect();
|
||||
if set.contains(name) { return true; }
|
||||
if set.contains(name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Minimal fallback set
|
||||
const KNOWN: &[&str] = &[
|
||||
"ArrayBox","MapBox","StringBox","ConsoleBox","FileBox","PathBox","MathBox","IntegerBox","TOMLBox"
|
||||
"ArrayBox",
|
||||
"MapBox",
|
||||
"StringBox",
|
||||
"ConsoleBox",
|
||||
"FileBox",
|
||||
"PathBox",
|
||||
"MathBox",
|
||||
"IntegerBox",
|
||||
"TOMLBox",
|
||||
];
|
||||
KNOWN.iter().any(|k| *k == name)
|
||||
}
|
||||
@ -112,11 +153,14 @@ impl BoxIndex {
|
||||
static GLOBAL: Lazy<RwLock<BoxIndex>> = Lazy::new(|| RwLock::new(BoxIndex::default()));
|
||||
|
||||
// Global resolve cache (keyed by tgt|base|strict|paths)
|
||||
static RESOLVE_CACHE: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
static RESOLVE_CACHE: Lazy<RwLock<HashMap<String, String>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub fn refresh_box_index() {
|
||||
let next = BoxIndex::build_current();
|
||||
if let Ok(mut w) = GLOBAL.write() { *w = next; }
|
||||
if let Ok(mut w) = GLOBAL.write() {
|
||||
*w = next;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_box_index() -> BoxIndex {
|
||||
@ -128,11 +172,15 @@ pub fn cache_get(key: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn cache_put(key: &str, value: String) {
|
||||
if let Ok(mut m) = RESOLVE_CACHE.write() { m.insert(key.to_string(), value); }
|
||||
if let Ok(mut m) = RESOLVE_CACHE.write() {
|
||||
m.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache_clear() {
|
||||
if let Ok(mut m) = RESOLVE_CACHE.write() { m.clear(); }
|
||||
if let Ok(mut m) = RESOLVE_CACHE.write() {
|
||||
m.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -143,5 +191,8 @@ pub struct PluginMeta {
|
||||
}
|
||||
|
||||
pub fn get_plugin_meta(plugin: &str) -> Option<PluginMeta> {
|
||||
GLOBAL.read().ok().and_then(|g| g.plugin_meta.get(plugin).cloned())
|
||||
GLOBAL
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|g| g.plugin_meta.get(plugin).cloned())
|
||||
}
|
||||
|
||||
@ -3,33 +3,65 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result<(), String> {
|
||||
let cwd = std::env::current_dir().unwrap_or(PathBuf::from("."));
|
||||
let cfg_abspath = if Path::new(cfg_path).is_absolute() { PathBuf::from(cfg_path) } else { cwd.join(cfg_path) };
|
||||
let cfg_abspath = if Path::new(cfg_path).is_absolute() {
|
||||
PathBuf::from(cfg_path)
|
||||
} else {
|
||||
cwd.join(cfg_path)
|
||||
};
|
||||
// 1) Load nyash.toml
|
||||
let text = std::fs::read_to_string(&cfg_abspath).map_err(|e| format!("read {}: {}", cfg_abspath.display(), e))?;
|
||||
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_abspath.display(), e))?;
|
||||
let text = std::fs::read_to_string(&cfg_abspath)
|
||||
.map_err(|e| format!("read {}: {}", cfg_abspath.display(), e))?;
|
||||
let doc = toml::from_str::<toml::Value>(&text)
|
||||
.map_err(|e| format!("parse {}: {}", cfg_abspath.display(), e))?;
|
||||
// 2) Apply [env]
|
||||
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
||||
for (k, v) in env_tbl.iter() { if let Some(s) = v.as_str() { std::env::set_var(k, s); } }
|
||||
for (k, v) in env_tbl.iter() {
|
||||
if let Some(s) = v.as_str() {
|
||||
std::env::set_var(k, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Derive options
|
||||
let profile = runner.config.build_profile.clone().unwrap_or_else(|| "release".into());
|
||||
let aot = runner.config.build_aot.clone().unwrap_or_else(|| "cranelift".into());
|
||||
let profile = runner
|
||||
.config
|
||||
.build_profile
|
||||
.clone()
|
||||
.unwrap_or_else(|| "release".into());
|
||||
let aot = runner
|
||||
.config
|
||||
.build_aot
|
||||
.clone()
|
||||
.unwrap_or_else(|| "cranelift".into());
|
||||
let out = runner.config.build_out.clone();
|
||||
let target = runner.config.build_target.clone();
|
||||
// 3) Build plugins: read [plugins] values as paths and build each
|
||||
if let Some(pl_tbl) = doc.get("plugins").and_then(|v| v.as_table()) {
|
||||
for (name, v) in pl_tbl.iter() {
|
||||
if let Some(path) = v.as_str() {
|
||||
let p = if Path::new(path).is_absolute() { PathBuf::from(path) } else { cwd.join(path) };
|
||||
let p = if Path::new(path).is_absolute() {
|
||||
PathBuf::from(path)
|
||||
} else {
|
||||
cwd.join(path)
|
||||
};
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
if profile == "release" { cmd.arg("--release"); }
|
||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||
if profile == "release" {
|
||||
cmd.arg("--release");
|
||||
}
|
||||
if let Some(t) = &target {
|
||||
cmd.args(["--target", t]);
|
||||
}
|
||||
cmd.current_dir(&p);
|
||||
println!("[build] plugin {} at {}", name, p.display());
|
||||
let status = cmd.status().map_err(|e| format!("spawn cargo (plugin {}): {}", name, e))?;
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|e| format!("spawn cargo (plugin {}): {}", name, e))?;
|
||||
if !status.success() {
|
||||
return Err(format!("plugin build failed: {} (dir={})", name, p.display()));
|
||||
return Err(format!(
|
||||
"plugin build failed: {} (dir={})",
|
||||
name,
|
||||
p.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,29 +70,63 @@ pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result
|
||||
{
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
if profile == "release" { cmd.arg("--release"); }
|
||||
match aot.as_str() { "llvm" => { cmd.args(["--features","llvm"]); }, _ => { cmd.args(["--features","cranelift-jit"]); } }
|
||||
if let Some(t) = &target { cmd.args(["--target", t]); }
|
||||
println!("[build] nyash core ({}, features={})", profile, if aot=="llvm" {"llvm"} else {"cranelift-jit"});
|
||||
let status = cmd.status().map_err(|e| format!("spawn cargo (core): {}", e))?;
|
||||
if !status.success() { return Err("nyash core build failed".into()); }
|
||||
if profile == "release" {
|
||||
cmd.arg("--release");
|
||||
}
|
||||
match aot.as_str() {
|
||||
"llvm" => {
|
||||
cmd.args(["--features", "llvm"]);
|
||||
}
|
||||
_ => {
|
||||
cmd.args(["--features", "cranelift-jit"]);
|
||||
}
|
||||
}
|
||||
if let Some(t) = &target {
|
||||
cmd.args(["--target", t]);
|
||||
}
|
||||
println!(
|
||||
"[build] nyash core ({}, features={})",
|
||||
profile,
|
||||
if aot == "llvm" {
|
||||
"llvm"
|
||||
} else {
|
||||
"cranelift-jit"
|
||||
}
|
||||
);
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|e| format!("spawn cargo (core): {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("nyash core build failed".into());
|
||||
}
|
||||
}
|
||||
// 5) Determine app entry
|
||||
let app = if let Some(a) = runner.config.build_app.clone() { a } else {
|
||||
let app = if let Some(a) = runner.config.build_app.clone() {
|
||||
a
|
||||
} else {
|
||||
// try [build].app, else suggest
|
||||
if let Some(tbl) = doc.get("build").and_then(|v| v.as_table()) {
|
||||
if let Some(s) = tbl.get("app").and_then(|v| v.as_str()) { s.to_string() } else { String::new() }
|
||||
} else { String::new() }
|
||||
if let Some(s) = tbl.get("app").and_then(|v| v.as_str()) {
|
||||
s.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
let app = if !app.is_empty() { app } else {
|
||||
let app = if !app.is_empty() {
|
||||
app
|
||||
} else {
|
||||
// collect candidates under apps/**/main.nyash
|
||||
let mut cand: Vec<String> = Vec::new();
|
||||
fn walk(dir: &Path, acc: &mut Vec<String>) {
|
||||
if let Ok(rd) = std::fs::read_dir(dir) {
|
||||
for e in rd.flatten() {
|
||||
let p = e.path();
|
||||
if p.is_dir() { walk(&p, acc); }
|
||||
else if p.file_name().map(|n| n=="main.nyash").unwrap_or(false) {
|
||||
if p.is_dir() {
|
||||
walk(&p, acc);
|
||||
} else if p.file_name().map(|n| n == "main.nyash").unwrap_or(false) {
|
||||
acc.push(p.display().to_string());
|
||||
}
|
||||
}
|
||||
@ -70,7 +136,10 @@ pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result
|
||||
let msg = if cand.is_empty() {
|
||||
"no app specified (--app) and no apps/**/main.nyash found".to_string()
|
||||
} else {
|
||||
format!("no app specified (--app). Candidates:\n - {}", cand.join("\n - "))
|
||||
format!(
|
||||
"no app specified (--app). Candidates:\n - {}",
|
||||
cand.join("\n - ")
|
||||
)
|
||||
};
|
||||
return Err(msg);
|
||||
};
|
||||
@ -79,47 +148,101 @@ pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result
|
||||
let _ = std::fs::create_dir_all(&obj_dir);
|
||||
let obj_path = obj_dir.join("main.o");
|
||||
if aot == "llvm" {
|
||||
if std::env::var("LLVM_SYS_180_PREFIX").ok().is_none() && std::env::var("LLVM_SYS_181_PREFIX").ok().is_none() {
|
||||
return Err("LLVM 18 not configured. Set LLVM_SYS_180_PREFIX or install LLVM 18 (llvm-config)".into());
|
||||
if std::env::var("LLVM_SYS_180_PREFIX").ok().is_none()
|
||||
&& std::env::var("LLVM_SYS_181_PREFIX").ok().is_none()
|
||||
{
|
||||
return Err(
|
||||
"LLVM 18 not configured. Set LLVM_SYS_180_PREFIX or install LLVM 18 (llvm-config)"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
std::env::set_var("NYASH_LLVM_OBJ_OUT", &obj_path);
|
||||
println!("[emit] LLVM object → {}", obj_path.display());
|
||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||
.args(["--backend","llvm", &app])
|
||||
.status().map_err(|e| format!("spawn nyash llvm: {}", e))?;
|
||||
if !status.success() { return Err("LLVM emit failed".into()); }
|
||||
let status = std::process::Command::new(
|
||||
cwd.join("target")
|
||||
.join(profile.clone())
|
||||
.join(if cfg!(windows) { "nyash.exe" } else { "nyash" }),
|
||||
)
|
||||
.args(["--backend", "llvm", &app])
|
||||
.status()
|
||||
.map_err(|e| format!("spawn nyash llvm: {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("LLVM emit failed".into());
|
||||
}
|
||||
} else {
|
||||
std::env::set_var("NYASH_AOT_OBJECT_OUT", &obj_dir);
|
||||
println!("[emit] Cranelift object → {} (directory)", obj_dir.display());
|
||||
let status = std::process::Command::new(cwd.join("target").join(profile.clone()).join(if cfg!(windows) {"nyash.exe"} else {"nyash"}))
|
||||
.args(["--backend","vm", &app])
|
||||
.status().map_err(|e| format!("spawn nyash jit-aot: {}", e))?;
|
||||
if !status.success() { return Err("Cranelift emit failed".into()); }
|
||||
println!(
|
||||
"[emit] Cranelift object → {} (directory)",
|
||||
obj_dir.display()
|
||||
);
|
||||
let status = std::process::Command::new(
|
||||
cwd.join("target")
|
||||
.join(profile.clone())
|
||||
.join(if cfg!(windows) { "nyash.exe" } else { "nyash" }),
|
||||
)
|
||||
.args(["--backend", "vm", &app])
|
||||
.status()
|
||||
.map_err(|e| format!("spawn nyash jit-aot: {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("Cranelift emit failed".into());
|
||||
}
|
||||
}
|
||||
if !obj_path.exists() {
|
||||
// In Cranelift path we produce target/aot_objects/<name>.o; fall back to main.o default
|
||||
if !obj_dir.join("main.o").exists() { return Err(format!("object not generated under {}", obj_dir.display())); }
|
||||
if !obj_dir.join("main.o").exists() {
|
||||
return Err(format!("object not generated under {}", obj_dir.display()));
|
||||
}
|
||||
}
|
||||
let out_path = if let Some(o) = out { PathBuf::from(o) } else { if cfg!(windows) { cwd.join("app.exe") } else { cwd.join("app") } };
|
||||
let out_path = if let Some(o) = out {
|
||||
PathBuf::from(o)
|
||||
} else {
|
||||
if cfg!(windows) {
|
||||
cwd.join("app.exe")
|
||||
} else {
|
||||
cwd.join("app")
|
||||
}
|
||||
};
|
||||
// 7) Link
|
||||
println!("[link] → {}", out_path.display());
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Prefer MSVC link.exe, then clang fallback
|
||||
if let Ok(link) = which::which("link") {
|
||||
let status = std::process::Command::new(&link).args(["/NOLOGO", &format!("/OUT:{}", out_path.display().to_string())])
|
||||
let status = std::process::Command::new(&link)
|
||||
.args([
|
||||
"/NOLOGO",
|
||||
&format!("/OUT:{}", out_path.display().to_string()),
|
||||
])
|
||||
.arg(&obj_path)
|
||||
.arg(cwd.join("target").join("release").join("nyrt.lib"))
|
||||
.status().map_err(|e| format!("spawn link.exe: {}", e))?;
|
||||
if status.success() { println!("OK"); return Ok(()); }
|
||||
.status()
|
||||
.map_err(|e| format!("spawn link.exe: {}", e))?;
|
||||
if status.success() {
|
||||
println!("OK");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Ok(clang) = which::which("clang") {
|
||||
let status = std::process::Command::new(&clang)
|
||||
.args(["-o", &out_path.display().to_string(), &obj_path.display().to_string()])
|
||||
.arg(cwd.join("target").join("release").join("nyrt.lib").display().to_string())
|
||||
.args([
|
||||
"-o",
|
||||
&out_path.display().to_string(),
|
||||
&obj_path.display().to_string(),
|
||||
])
|
||||
.arg(
|
||||
cwd.join("target")
|
||||
.join("release")
|
||||
.join("nyrt.lib")
|
||||
.display()
|
||||
.to_string(),
|
||||
)
|
||||
.arg("-lntdll")
|
||||
.status().map_err(|e| format!("spawn clang: {}", e))?;
|
||||
if status.success() { println!("OK"); return Ok(()); }
|
||||
.status()
|
||||
.map_err(|e| format!("spawn clang: {}", e))?;
|
||||
if status.success() {
|
||||
println!("OK");
|
||||
return Ok(());
|
||||
}
|
||||
return Err("link failed on Windows (tried link.exe and clang)".into());
|
||||
}
|
||||
return Err("no linker found (need Visual Studio link.exe or LLVM clang)".into());
|
||||
@ -128,13 +251,25 @@ pub(super) fn run_build_mvp_impl(runner: &NyashRunner, cfg_path: &str) -> Result
|
||||
{
|
||||
let status = std::process::Command::new("cc")
|
||||
.arg(&obj_path)
|
||||
.args(["-L", &cwd.join("target").join("release").display().to_string()])
|
||||
.args(["-Wl,--whole-archive", "-lnyrt", "-Wl,--no-whole-archive", "-lpthread", "-ldl", "-lm"])
|
||||
.args([
|
||||
"-L",
|
||||
&cwd.join("target").join("release").display().to_string(),
|
||||
])
|
||||
.args([
|
||||
"-Wl,--whole-archive",
|
||||
"-lnyrt",
|
||||
"-Wl,--no-whole-archive",
|
||||
"-lpthread",
|
||||
"-ldl",
|
||||
"-lm",
|
||||
])
|
||||
.args(["-o", &out_path.display().to_string()])
|
||||
.status().map_err(|e| format!("spawn cc: {}", e))?;
|
||||
if !status.success() { return Err("link failed (cc)".into()); }
|
||||
.status()
|
||||
.map_err(|e| format!("spawn cc: {}", e))?;
|
||||
if !status.success() {
|
||||
return Err("link failed (cc)".into());
|
||||
}
|
||||
}
|
||||
println!("✅ Success: {}", out_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,9 @@ pub(super) fn apply_cli_directives_from_source(
|
||||
for (i, line) in code.lines().take(128).enumerate() {
|
||||
let l = line.trim();
|
||||
if !(l.starts_with("//") || l.starts_with("#!") || l.is_empty()) {
|
||||
if i > 0 { break; }
|
||||
if i > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(rest) = l.strip_prefix("//") {
|
||||
let rest = rest.trim();
|
||||
@ -28,7 +30,9 @@ pub(super) fn apply_cli_directives_from_source(
|
||||
if let Some((k, v)) = dir.split_once('=') {
|
||||
let key = k.trim();
|
||||
let val = v.trim();
|
||||
if !key.is_empty() { std::env::set_var(key, val); }
|
||||
if !key.is_empty() {
|
||||
std::env::set_var(key, val);
|
||||
}
|
||||
}
|
||||
} else if rest == "@plugin-builtins" {
|
||||
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
|
||||
@ -53,4 +57,3 @@ pub(super) fn apply_cli_directives_from_source(
|
||||
// Lint: enforce fields at top-of-box (delegated)
|
||||
super::pipeline::lint_fields_top(code, strict_fields, verbose)
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
//! Runner demo helpers (moved out of mod.rs to reduce file size)
|
||||
use nyash_rust::box_trait::{StringBox, IntegerBox, BoolBox, VoidBox, AddBox, NyashBox, BoxCore};
|
||||
use nyash_rust::tokenizer::NyashTokenizer;
|
||||
use nyash_rust::ast::ASTNode;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::box_trait::{AddBox, BoolBox, BoxCore, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
use nyash_rust::interpreter::NyashInterpreter;
|
||||
use nyash_rust::parser::NyashParser;
|
||||
use nyash_rust::tokenizer::NyashTokenizer;
|
||||
|
||||
pub(super) fn demo_basic_boxes() {
|
||||
println!("\n📦 1. Basic Box Creation:");
|
||||
@ -33,7 +33,10 @@ pub(super) fn demo_box_operations() {
|
||||
let str1 = StringBox::new("Hello, ".to_string());
|
||||
let str2 = StringBox::new("World!".to_string());
|
||||
let concat_box = AddBox::new(Box::new(str1), Box::new(str2));
|
||||
println!(" \"Hello, \" + \"World!\" = {}", concat_box.to_string_box().value);
|
||||
println!(
|
||||
" \"Hello, \" + \"World!\" = {}",
|
||||
concat_box.to_string_box().value
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn demo_box_collections() {
|
||||
@ -55,7 +58,7 @@ pub(super) fn demo_tokenizer_system() {
|
||||
match tokenizer.tokenize() {
|
||||
Ok(tokens) => {
|
||||
println!(" Tokenized {} tokens successfully", tokens.len());
|
||||
},
|
||||
}
|
||||
Err(e) => println!(" Tokenization error: {}", e),
|
||||
}
|
||||
}
|
||||
@ -82,7 +85,7 @@ pub(super) fn demo_parser_system() {
|
||||
println!(" [{}] {}", i, stmt.info());
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => println!(" Parser error: {}", e),
|
||||
}
|
||||
}
|
||||
@ -103,7 +106,7 @@ pub(super) fn demo_interpreter_system() {
|
||||
match interpreter.execute(ast) {
|
||||
Ok(result) => {
|
||||
println!(" ✅ Result: {}", result.to_string_box().value);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ❌ Execution error: {}", e);
|
||||
}
|
||||
@ -125,7 +128,7 @@ pub(super) fn demo_interpreter_system() {
|
||||
match interpreter.execute(ast) {
|
||||
Ok(result) => {
|
||||
println!(" ✅ Result: {}", result.to_string_box().value);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" ❌ Execution error: {}", e);
|
||||
}
|
||||
|
||||
@ -9,130 +9,164 @@ use std::{fs, process};
|
||||
|
||||
/// Thin file dispatcher: select backend and delegate to mode executors
|
||||
pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
// Selfhost pipeline (Ny -> JSON v0) behind env gate
|
||||
if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") {
|
||||
if runner.try_run_selfhost_pipeline(filename) {
|
||||
// Selfhost pipeline (Ny -> JSON v0) behind env gate
|
||||
if std::env::var("NYASH_USE_NY_COMPILER").ok().as_deref() == Some("1") {
|
||||
if runner.try_run_selfhost_pipeline(filename) {
|
||||
return;
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)");
|
||||
}
|
||||
}
|
||||
|
||||
// Direct v0 bridge when requested via CLI/env
|
||||
let use_ny_parser = runner.config.parser_ny
|
||||
|| std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
|
||||
if use_ny_parser {
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
match json_v0_bridge::parse_source_v0_to_module(&code) {
|
||||
Ok(module) => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀",
|
||||
filename
|
||||
);
|
||||
}
|
||||
runner.execute_mir_module(&module);
|
||||
return;
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[ny-compiler] fallback to default path (MVP unavailable for this input)");
|
||||
}
|
||||
}
|
||||
|
||||
// Direct v0 bridge when requested via CLI/env
|
||||
let use_ny_parser = runner.config.parser_ny || std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
|
||||
if use_ny_parser {
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
match json_v0_bridge::parse_source_v0_to_module(&code) {
|
||||
Ok(module) => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_mir_module(&module);
|
||||
return;
|
||||
}
|
||||
Err(e) => { eprintln!("❌ Direct bridge parse error: {}", e); process::exit(1); }
|
||||
}
|
||||
}
|
||||
|
||||
// AST dump mode
|
||||
if runner.config.dump_ast {
|
||||
println!("🧠 Nyash AST Dump - Processing file: {}", filename);
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
println!("{:#?}", ast);
|
||||
return;
|
||||
}
|
||||
|
||||
// MIR dump/verify
|
||||
if runner.config.dump_mir || runner.config.verify_mir {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_mir_mode(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// WASM / AOT (feature-gated)
|
||||
if runner.config.compile_wasm {
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
{ super::modes::wasm::execute_wasm_mode(runner, filename); return; }
|
||||
#[cfg(not(feature = "wasm-backend"))]
|
||||
{ eprintln!("❌ WASM backend not available. Please rebuild with: cargo build --features wasm-backend"); process::exit(1); }
|
||||
}
|
||||
if runner.config.compile_native {
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
runner.execute_aot_mode(filename);
|
||||
return;
|
||||
}
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{ eprintln!("❌ Native AOT compilation requires Cranelift. Please rebuild: cargo build --features cranelift-jit"); process::exit(1); }
|
||||
}
|
||||
|
||||
// Backend selection
|
||||
match runner.config.backend.as_str() {
|
||||
"mir" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Interpreter - Executing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_mir_mode(filename);
|
||||
}
|
||||
"vm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_vm_mode(filename);
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
"jit-direct" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("⚡ Nyash JIT-Direct Backend - Executing file: {} ⚡", filename);
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
// Use independent JIT-direct runner method (no VM execute loop)
|
||||
runner.run_file_jit_direct(filename);
|
||||
}
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{
|
||||
eprintln!("❌ Cranelift backend not available. Please rebuild with: cargo build --features cranelift-jit");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
"llvm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("⚡ Nyash LLVM Backend - Executing file: {} ⚡", filename);
|
||||
}
|
||||
runner.execute_llvm_mode(filename);
|
||||
}
|
||||
_ => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🦀 Nyash Rust Implementation - Executing file: {} 🦀", filename);
|
||||
if let Some(fuel) = runner.config.debug_fuel {
|
||||
println!("🔥 Debug fuel limit: {} iterations", fuel);
|
||||
} else {
|
||||
println!("🔥 Debug fuel limit: unlimited");
|
||||
}
|
||||
println!("====================================================");
|
||||
}
|
||||
super::modes::interpreter::execute_nyash_file(filename, runner.config.debug_fuel.clone());
|
||||
Err(e) => {
|
||||
eprintln!("❌ Direct bridge parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AST dump mode
|
||||
if runner.config.dump_ast {
|
||||
println!("🧠 Nyash AST Dump - Processing file: {}", filename);
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
println!("{:#?}", ast);
|
||||
return;
|
||||
}
|
||||
|
||||
// MIR dump/verify
|
||||
if runner.config.dump_mir || runner.config.verify_mir {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Compiler - Processing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_mir_mode(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// WASM / AOT (feature-gated)
|
||||
if runner.config.compile_wasm {
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
{
|
||||
super::modes::wasm::execute_wasm_mode(runner, filename);
|
||||
return;
|
||||
}
|
||||
#[cfg(not(feature = "wasm-backend"))]
|
||||
{
|
||||
eprintln!("❌ WASM backend not available. Please rebuild with: cargo build --features wasm-backend");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
if runner.config.compile_native {
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
runner.execute_aot_mode(filename);
|
||||
return;
|
||||
}
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{
|
||||
eprintln!("❌ Native AOT compilation requires Cranelift. Please rebuild: cargo build --features cranelift-jit");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Backend selection
|
||||
match runner.config.backend.as_str() {
|
||||
"mir" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash MIR Interpreter - Executing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_mir_mode(filename);
|
||||
}
|
||||
"vm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
}
|
||||
runner.execute_vm_mode(filename);
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
"jit-direct" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"⚡ Nyash JIT-Direct Backend - Executing file: {} ⚡",
|
||||
filename
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
{
|
||||
// Use independent JIT-direct runner method (no VM execute loop)
|
||||
runner.run_file_jit_direct(filename);
|
||||
}
|
||||
#[cfg(not(feature = "cranelift-jit"))]
|
||||
{
|
||||
eprintln!("❌ Cranelift backend not available. Please rebuild with: cargo build --features cranelift-jit");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
"llvm" => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!("⚡ Nyash LLVM Backend - Executing file: {} ⚡", filename);
|
||||
}
|
||||
runner.execute_llvm_mode(filename);
|
||||
}
|
||||
_ => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
println!(
|
||||
"🦀 Nyash Rust Implementation - Executing file: {} 🦀",
|
||||
filename
|
||||
);
|
||||
if let Some(fuel) = runner.config.debug_fuel {
|
||||
println!("🔥 Debug fuel limit: {} iterations", fuel);
|
||||
} else {
|
||||
println!("🔥 Debug fuel limit: unlimited");
|
||||
}
|
||||
println!("====================================================");
|
||||
}
|
||||
super::modes::interpreter::execute_nyash_file(
|
||||
filename,
|
||||
runner.config.debug_fuel.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
pub(crate) fn execute_mir_module(&self, module: &crate::mir::MirModule) {
|
||||
use crate::backend::MirInterpreter;
|
||||
use crate::box_trait::{IntegerBox, BoolBox, StringBox};
|
||||
use crate::box_trait::{BoolBox, IntegerBox, StringBox};
|
||||
use crate::boxes::FloatBox;
|
||||
use crate::mir::MirType;
|
||||
|
||||
@ -147,26 +181,34 @@ impl NyashRunner {
|
||||
("Float", format!("{}", fb.value))
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Float", format!("{}", ib.value as f64))
|
||||
} else { ("Float", result.to_string_box().value) }
|
||||
} else {
|
||||
("Float", result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
MirType::Integer => {
|
||||
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Integer", ib.value.to_string())
|
||||
} else { ("Integer", result.to_string_box().value) }
|
||||
} else {
|
||||
("Integer", result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
MirType::Bool => {
|
||||
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
|
||||
("Bool", bb.value.to_string())
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Bool", (ib.value != 0).to_string())
|
||||
} else { ("Bool", result.to_string_box().value) }
|
||||
} else {
|
||||
("Bool", result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
MirType::String => {
|
||||
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||
("String", sb.value.clone())
|
||||
} else { ("String", result.to_string_box().value) }
|
||||
} else {
|
||||
("String", result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
_ => { (result.type_name(), result.to_string_box().value) }
|
||||
_ => (result.type_name(), result.to_string_box().value),
|
||||
};
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
|
||||
@ -10,17 +10,31 @@ pub(super) struct ProgramV0 {
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub(super) enum StmtV0 {
|
||||
Return { expr: ExprV0 },
|
||||
Extern { iface: String, method: String, args: Vec<ExprV0> },
|
||||
Expr { expr: ExprV0 },
|
||||
Local { name: String, expr: ExprV0 },
|
||||
Return {
|
||||
expr: ExprV0,
|
||||
},
|
||||
Extern {
|
||||
iface: String,
|
||||
method: String,
|
||||
args: Vec<ExprV0>,
|
||||
},
|
||||
Expr {
|
||||
expr: ExprV0,
|
||||
},
|
||||
Local {
|
||||
name: String,
|
||||
expr: ExprV0,
|
||||
},
|
||||
If {
|
||||
cond: ExprV0,
|
||||
then: Vec<StmtV0>,
|
||||
#[serde(rename = "else", default)]
|
||||
r#else: Option<Vec<StmtV0>>,
|
||||
},
|
||||
Loop { cond: ExprV0, body: Vec<StmtV0> },
|
||||
Loop {
|
||||
cond: ExprV0,
|
||||
body: Vec<StmtV0>,
|
||||
},
|
||||
Break,
|
||||
Continue,
|
||||
Try {
|
||||
@ -46,17 +60,52 @@ pub(super) struct CatchV0 {
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
pub(super) enum ExprV0 {
|
||||
Int { value: serde_json::Value },
|
||||
Str { value: String },
|
||||
Bool { value: bool },
|
||||
Binary { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
|
||||
Extern { iface: String, method: String, args: Vec<ExprV0> },
|
||||
Compare { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
|
||||
Logical { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
|
||||
Call { name: String, args: Vec<ExprV0> },
|
||||
Method { recv: Box<ExprV0>, method: String, args: Vec<ExprV0> },
|
||||
New { class: String, args: Vec<ExprV0> },
|
||||
Var { name: String },
|
||||
Throw { expr: Box<ExprV0> },
|
||||
Int {
|
||||
value: serde_json::Value,
|
||||
},
|
||||
Str {
|
||||
value: String,
|
||||
},
|
||||
Bool {
|
||||
value: bool,
|
||||
},
|
||||
Binary {
|
||||
op: String,
|
||||
lhs: Box<ExprV0>,
|
||||
rhs: Box<ExprV0>,
|
||||
},
|
||||
Extern {
|
||||
iface: String,
|
||||
method: String,
|
||||
args: Vec<ExprV0>,
|
||||
},
|
||||
Compare {
|
||||
op: String,
|
||||
lhs: Box<ExprV0>,
|
||||
rhs: Box<ExprV0>,
|
||||
},
|
||||
Logical {
|
||||
op: String,
|
||||
lhs: Box<ExprV0>,
|
||||
rhs: Box<ExprV0>,
|
||||
},
|
||||
Call {
|
||||
name: String,
|
||||
args: Vec<ExprV0>,
|
||||
},
|
||||
Method {
|
||||
recv: Box<ExprV0>,
|
||||
method: String,
|
||||
args: Vec<ExprV0>,
|
||||
},
|
||||
New {
|
||||
class: String,
|
||||
args: Vec<ExprV0>,
|
||||
},
|
||||
Var {
|
||||
name: String,
|
||||
},
|
||||
Throw {
|
||||
expr: Box<ExprV0>,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -18,25 +18,60 @@ fn lex(input: &str) -> Result<Vec<Tok>, String> {
|
||||
let mut toks = Vec::new();
|
||||
while let Some(&c) = chars.peek() {
|
||||
match c {
|
||||
' ' | '\n' | '\t' | '\r' => { chars.next(); }
|
||||
'+' => { chars.next(); toks.push(Tok::Plus); }
|
||||
'-' => { chars.next(); toks.push(Tok::Minus); }
|
||||
'*' => { chars.next(); toks.push(Tok::Star); }
|
||||
'/' => { chars.next(); toks.push(Tok::Slash); }
|
||||
'(' => { chars.next(); toks.push(Tok::LParen); }
|
||||
')' => { chars.next(); toks.push(Tok::RParen); }
|
||||
' ' | '\n' | '\t' | '\r' => {
|
||||
chars.next();
|
||||
}
|
||||
'+' => {
|
||||
chars.next();
|
||||
toks.push(Tok::Plus);
|
||||
}
|
||||
'-' => {
|
||||
chars.next();
|
||||
toks.push(Tok::Minus);
|
||||
}
|
||||
'*' => {
|
||||
chars.next();
|
||||
toks.push(Tok::Star);
|
||||
}
|
||||
'/' => {
|
||||
chars.next();
|
||||
toks.push(Tok::Slash);
|
||||
}
|
||||
'(' => {
|
||||
chars.next();
|
||||
toks.push(Tok::LParen);
|
||||
}
|
||||
')' => {
|
||||
chars.next();
|
||||
toks.push(Tok::RParen);
|
||||
}
|
||||
'0'..='9' => {
|
||||
let mut n = 0i64;
|
||||
while let Some(&d) = chars.peek() {
|
||||
if d.is_ascii_digit() { n = n * 10 + (d as i64 - '0' as i64); chars.next(); } else { break; }
|
||||
if d.is_ascii_digit() {
|
||||
n = n * 10 + (d as i64 - '0' as i64);
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
toks.push(Tok::Int(n));
|
||||
}
|
||||
'r' => {
|
||||
let kw = "return";
|
||||
let mut it = kw.chars();
|
||||
let mut ok = true; for _ in 0..kw.len() { if Some(chars.next().unwrap_or('\0')) != it.next() { ok=false; break; } }
|
||||
if ok { toks.push(Tok::Return); } else { return Err("unexpected 'r'".into()); }
|
||||
let mut ok = true;
|
||||
for _ in 0..kw.len() {
|
||||
if Some(chars.next().unwrap_or('\0')) != it.next() {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
toks.push(Tok::Return);
|
||||
} else {
|
||||
return Err("unexpected 'r'".into());
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("unexpected char '{}'", c)),
|
||||
}
|
||||
@ -45,39 +80,111 @@ fn lex(input: &str) -> Result<Vec<Tok>, String> {
|
||||
Ok(toks)
|
||||
}
|
||||
|
||||
struct P { toks: Vec<Tok>, pos: usize }
|
||||
struct P {
|
||||
toks: Vec<Tok>,
|
||||
pos: usize,
|
||||
}
|
||||
impl P {
|
||||
fn new(toks: Vec<Tok>) -> Self { Self { toks, pos: 0 } }
|
||||
fn peek(&self) -> &Tok { self.toks.get(self.pos).unwrap() }
|
||||
fn next(&mut self) -> Tok { let t = self.toks.get(self.pos).unwrap().clone(); self.pos += 1; t }
|
||||
fn expect_return(&mut self) -> Result<(), String> { match self.next() { Tok::Return => Ok(()), _ => Err("expected 'return'".into()) } }
|
||||
fn parse_program(&mut self) -> Result<ExprV0, String> { self.expect_return()?; self.parse_expr() }
|
||||
fn new(toks: Vec<Tok>) -> Self {
|
||||
Self { toks, pos: 0 }
|
||||
}
|
||||
fn peek(&self) -> &Tok {
|
||||
self.toks.get(self.pos).unwrap()
|
||||
}
|
||||
fn next(&mut self) -> Tok {
|
||||
let t = self.toks.get(self.pos).unwrap().clone();
|
||||
self.pos += 1;
|
||||
t
|
||||
}
|
||||
fn expect_return(&mut self) -> Result<(), String> {
|
||||
match self.next() {
|
||||
Tok::Return => Ok(()),
|
||||
_ => Err("expected 'return'".into()),
|
||||
}
|
||||
}
|
||||
fn parse_program(&mut self) -> Result<ExprV0, String> {
|
||||
self.expect_return()?;
|
||||
self.parse_expr()
|
||||
}
|
||||
fn parse_expr(&mut self) -> Result<ExprV0, String> {
|
||||
let mut left = self.parse_term()?;
|
||||
loop { match self.peek() { Tok::Plus => { self.next(); let r = self.parse_term()?; left = ExprV0::Binary{op:"+".into(), lhs: Box::new(left), rhs: Box::new(r)}; }
|
||||
Tok::Minus => { self.next(); let r = self.parse_term()?; left = ExprV0::Binary{op:"-".into(), lhs: Box::new(left), rhs: Box::new(r)}; }
|
||||
_ => break } }
|
||||
loop {
|
||||
match self.peek() {
|
||||
Tok::Plus => {
|
||||
self.next();
|
||||
let r = self.parse_term()?;
|
||||
left = ExprV0::Binary {
|
||||
op: "+".into(),
|
||||
lhs: Box::new(left),
|
||||
rhs: Box::new(r),
|
||||
};
|
||||
}
|
||||
Tok::Minus => {
|
||||
self.next();
|
||||
let r = self.parse_term()?;
|
||||
left = ExprV0::Binary {
|
||||
op: "-".into(),
|
||||
lhs: Box::new(left),
|
||||
rhs: Box::new(r),
|
||||
};
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
fn parse_term(&mut self) -> Result<ExprV0, String> {
|
||||
let mut left = self.parse_factor()?;
|
||||
loop { match self.peek() { Tok::Star => { self.next(); let r = self.parse_factor()?; left = ExprV0::Binary{op:"*".into(), lhs: Box::new(left), rhs: Box::new(r)}; }
|
||||
Tok::Slash => { self.next(); let r = self.parse_factor()?; left = ExprV0::Binary{op:"/".into(), lhs: Box::new(left), rhs: Box::new(r)}; }
|
||||
_ => break } }
|
||||
loop {
|
||||
match self.peek() {
|
||||
Tok::Star => {
|
||||
self.next();
|
||||
let r = self.parse_factor()?;
|
||||
left = ExprV0::Binary {
|
||||
op: "*".into(),
|
||||
lhs: Box::new(left),
|
||||
rhs: Box::new(r),
|
||||
};
|
||||
}
|
||||
Tok::Slash => {
|
||||
self.next();
|
||||
let r = self.parse_factor()?;
|
||||
left = ExprV0::Binary {
|
||||
op: "/".into(),
|
||||
lhs: Box::new(left),
|
||||
rhs: Box::new(r),
|
||||
};
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
fn parse_factor(&mut self) -> Result<ExprV0, String> {
|
||||
match self.next() {
|
||||
Tok::Int(v) => Ok(ExprV0::Int { value: serde_json::Value::from(v) }),
|
||||
Tok::LParen => { let e = self.parse_expr()?; match self.next() { Tok::RParen => Ok(e), _ => Err(") expected".into()) } }
|
||||
Tok::Int(v) => Ok(ExprV0::Int {
|
||||
value: serde_json::Value::from(v),
|
||||
}),
|
||||
Tok::LParen => {
|
||||
let e = self.parse_expr()?;
|
||||
match self.next() {
|
||||
Tok::RParen => Ok(e),
|
||||
_ => Err(") expected".into()),
|
||||
}
|
||||
}
|
||||
_ => Err("factor expected".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_source_v0_to_json(input: &str) -> Result<String, String> {
|
||||
let toks = lex(input)?; let mut p = P::new(toks); let expr = p.parse_program()?;
|
||||
let prog = ProgramV0 { version: 0, kind: "Program".into(), body: vec![StmtV0::Return { expr }] };
|
||||
let toks = lex(input)?;
|
||||
let mut p = P::new(toks);
|
||||
let expr = p.parse_program()?;
|
||||
let prog = ProgramV0 {
|
||||
version: 0,
|
||||
kind: "Program".into(),
|
||||
body: vec![StmtV0::Return { expr }],
|
||||
};
|
||||
serde_json::to_string(&prog).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +103,12 @@ fn next_block_id(f: &MirFunction) -> BasicBlockId {
|
||||
BasicBlockId::new(mx)
|
||||
}
|
||||
|
||||
fn lower_throw(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, exception_value: ValueId) -> (ValueId, BasicBlockId) {
|
||||
fn lower_throw(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
exception_value: ValueId,
|
||||
) -> (ValueId, BasicBlockId) {
|
||||
if env.throw_enabled {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Throw {
|
||||
@ -115,7 +120,10 @@ fn lower_throw(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, excep
|
||||
} else {
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) });
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(0),
|
||||
});
|
||||
}
|
||||
(dst, cur_bb)
|
||||
}
|
||||
@ -139,21 +147,30 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
};
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) });
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(ival),
|
||||
});
|
||||
}
|
||||
Ok((dst, cur_bb))
|
||||
}
|
||||
ExprV0::Str { value } => {
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(value.clone()) });
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst,
|
||||
value: ConstValue::String(value.clone()),
|
||||
});
|
||||
}
|
||||
Ok((dst, cur_bb))
|
||||
}
|
||||
ExprV0::Bool { value } => {
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(*value) });
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst,
|
||||
value: ConstValue::Bool(*value),
|
||||
});
|
||||
}
|
||||
Ok((dst, cur_bb))
|
||||
}
|
||||
@ -169,15 +186,30 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
};
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_after_r) {
|
||||
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
|
||||
bb.add_instruction(MirInstruction::BinOp {
|
||||
dst,
|
||||
op: bop,
|
||||
lhs: l,
|
||||
rhs: r,
|
||||
});
|
||||
}
|
||||
Ok((dst, cur_after_r))
|
||||
}
|
||||
ExprV0::Extern { iface, method, args } => {
|
||||
ExprV0::Extern {
|
||||
iface,
|
||||
method,
|
||||
args,
|
||||
} => {
|
||||
let (arg_ids, cur2) = lower_args_with_scope(env, 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: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
|
||||
bb.add_instruction(MirInstruction::ExternCall {
|
||||
dst: Some(dst),
|
||||
iface_name: iface.clone(),
|
||||
method_name: method.clone(),
|
||||
args: arg_ids,
|
||||
effects: EffectMask::IO,
|
||||
});
|
||||
}
|
||||
Ok((dst, cur2))
|
||||
}
|
||||
@ -195,7 +227,12 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
};
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_after_r) {
|
||||
bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r });
|
||||
bb.add_instruction(MirInstruction::Compare {
|
||||
dst,
|
||||
op: cop,
|
||||
lhs: l,
|
||||
rhs: r,
|
||||
});
|
||||
}
|
||||
Ok((dst, cur_after_r))
|
||||
}
|
||||
@ -210,9 +247,17 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
let is_and = matches!(op.as_str(), "&&" | "and");
|
||||
if let Some(bb) = f.get_block_mut(cur_after_l) {
|
||||
if is_and {
|
||||
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb });
|
||||
bb.set_terminator(MirInstruction::Branch {
|
||||
condition: l,
|
||||
then_bb: rhs_bb,
|
||||
else_bb: fall_bb,
|
||||
});
|
||||
} else {
|
||||
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb });
|
||||
bb.set_terminator(MirInstruction::Branch {
|
||||
condition: l,
|
||||
then_bb: fall_bb,
|
||||
else_bb: rhs_bb,
|
||||
});
|
||||
}
|
||||
}
|
||||
crate::jit::events::emit_lower(
|
||||
@ -222,19 +267,44 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
);
|
||||
let cdst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(fall_bb) {
|
||||
let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) };
|
||||
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
|
||||
let cval = if is_and {
|
||||
ConstValue::Bool(false)
|
||||
} else {
|
||||
ConstValue::Bool(true)
|
||||
};
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst: cdst,
|
||||
value: cval,
|
||||
});
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
let (rval, rhs_end) = lower_expr_with_scope(env, 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 }); } }
|
||||
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 env.mir_no_phi {
|
||||
if let Some(bb) = f.get_block_mut(fall_bb) { bb.add_instruction(MirInstruction::Copy { dst: out, src: cdst }); }
|
||||
if let Some(bb) = f.get_block_mut(rhs_end) { bb.add_instruction(MirInstruction::Copy { dst: out, src: rval }); }
|
||||
if let Some(bb) = f.get_block_mut(fall_bb) {
|
||||
bb.add_instruction(MirInstruction::Copy {
|
||||
dst: out,
|
||||
src: cdst,
|
||||
});
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(rhs_end) {
|
||||
bb.add_instruction(MirInstruction::Copy {
|
||||
dst: out,
|
||||
src: rval,
|
||||
});
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
let mut inputs: Vec<(BasicBlockId, ValueId)> = vec![(fall_bb, cdst)];
|
||||
if rhs_end != fall_bb { inputs.push((rhs_end, rval)); } else { inputs.push((fall_bb, rval)); }
|
||||
if rhs_end != fall_bb {
|
||||
inputs.push((rhs_end, rval));
|
||||
} else {
|
||||
inputs.push((fall_bb, rval));
|
||||
}
|
||||
inputs.sort_by_key(|(bbid, _)| bbid.0);
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs });
|
||||
}
|
||||
@ -243,62 +313,133 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
ExprV0::Call { name, args } => {
|
||||
if name == "array.of" {
|
||||
let arr = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] }); }
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox {
|
||||
dst: arr,
|
||||
box_type: "ArrayBox".into(),
|
||||
args: vec![],
|
||||
});
|
||||
}
|
||||
let mut cur = cur_bb;
|
||||
for e in args {
|
||||
let (v, c) = lower_expr_with_scope(env, f, cur, e, vars)?; cur = c;
|
||||
let (v, c) = lower_expr_with_scope(env, f, cur, e, vars)?;
|
||||
cur = c;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(tmp),
|
||||
box_val: arr,
|
||||
method: "push".into(),
|
||||
method_id: None,
|
||||
args: vec![v],
|
||||
effects: EffectMask::READ,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Ok((arr, cur));
|
||||
}
|
||||
if name == "map.of" {
|
||||
let mapv = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] }); }
|
||||
let mut cur = cur_bb; let mut it = args.iter();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::NewBox {
|
||||
dst: mapv,
|
||||
box_type: "MapBox".into(),
|
||||
args: vec![],
|
||||
});
|
||||
}
|
||||
let mut cur = cur_bb;
|
||||
let mut it = args.iter();
|
||||
while let Some(k) = it.next() {
|
||||
if let Some(v) = it.next() {
|
||||
let (kv, cur2) = lower_expr_with_scope(env, f, cur, k, vars)?; cur = cur2;
|
||||
let (vv, cur3) = lower_expr_with_scope(env, f, cur, v, vars)?; cur = cur3;
|
||||
let (kv, cur2) = lower_expr_with_scope(env, f, cur, k, vars)?;
|
||||
cur = cur2;
|
||||
let (vv, cur3) = lower_expr_with_scope(env, f, cur, v, vars)?;
|
||||
cur = cur3;
|
||||
let tmp = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ }); }
|
||||
} else { break; }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(tmp),
|
||||
box_val: mapv,
|
||||
method: "set".into(),
|
||||
method_id: None,
|
||||
args: vec![kv, vv],
|
||||
effects: EffectMask::READ,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Ok((mapv, cur));
|
||||
}
|
||||
let (arg_ids, cur) = lower_args_with_scope(env, f, cur_bb, args, vars)?;
|
||||
let fun_val = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst: fun_val,
|
||||
value: ConstValue::String(name.clone()),
|
||||
});
|
||||
}
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::Call { dst: Some(dst), func: fun_val, args: arg_ids, effects: EffectMask::READ }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::Call {
|
||||
dst: Some(dst),
|
||||
func: fun_val,
|
||||
args: arg_ids,
|
||||
effects: EffectMask::READ,
|
||||
});
|
||||
}
|
||||
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 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_scope(env, 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 }); }
|
||||
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_scope(env, f, cur_bb, recv, vars)?;
|
||||
let (arg_ids, cur2) = lower_args_with_scope(env, f, cur, args, vars)?;
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur2) { bb.add_instruction(MirInstruction::BoxCall { dst: Some(dst), box_val: recv_v, method: method.clone(), method_id: None, args: arg_ids, effects: EffectMask::READ }); }
|
||||
if let Some(bb) = f.get_block_mut(cur2) {
|
||||
bb.add_instruction(MirInstruction::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val: recv_v,
|
||||
method: method.clone(),
|
||||
method_id: None,
|
||||
args: arg_ids,
|
||||
effects: EffectMask::READ,
|
||||
});
|
||||
}
|
||||
Ok((dst, cur2))
|
||||
}
|
||||
ExprV0::New { class, args } => {
|
||||
let (arg_ids, cur) = lower_args_with_scope(env, f, cur_bb, args, vars)?;
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::NewBox {
|
||||
dst,
|
||||
box_type: class.clone(),
|
||||
args: arg_ids,
|
||||
});
|
||||
}
|
||||
Ok((dst, cur))
|
||||
}
|
||||
ExprV0::Var { name } => {
|
||||
match vars.resolve(env, f, cur_bb, name)? {
|
||||
Some(v) => Ok((v, cur_bb)),
|
||||
None => Err(format!("undefined variable: {}", name)),
|
||||
}
|
||||
}
|
||||
ExprV0::Var { name } => match vars.resolve(env, f, cur_bb, name)? {
|
||||
Some(v) => Ok((v, cur_bb)),
|
||||
None => Err(format!("undefined variable: {}", name)),
|
||||
},
|
||||
ExprV0::Throw { expr } => {
|
||||
let (exc, cur) = lower_expr_with_scope(env, f, cur_bb, expr, vars)?;
|
||||
Ok(lower_throw(env, f, cur, exc))
|
||||
@ -306,7 +447,13 @@ fn lower_expr_with_scope<S: VarScope>(
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_args_with_scope<S: VarScope>(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0], scope: &mut S) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
fn lower_args_with_scope<S: VarScope>(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
args: &[ExprV0],
|
||||
scope: &mut S,
|
||||
) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
let mut out = Vec::with_capacity(args.len());
|
||||
let mut cur = cur_bb;
|
||||
for a in args {
|
||||
@ -317,22 +464,44 @@ fn lower_args_with_scope<S: VarScope>(env: &BridgeEnv, f: &mut MirFunction, cur_
|
||||
Ok((out, cur))
|
||||
}
|
||||
|
||||
fn lower_expr(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(ValueId, BasicBlockId), String> {
|
||||
fn lower_expr(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
e: &ExprV0,
|
||||
) -> Result<(ValueId, BasicBlockId), String> {
|
||||
let mut scope = NoVars;
|
||||
lower_expr_with_scope(env, f, cur_bb, e, &mut scope)
|
||||
}
|
||||
|
||||
fn lower_expr_with_vars(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0, vars: &mut HashMap<String, ValueId>) -> Result<(ValueId, BasicBlockId), String> {
|
||||
fn lower_expr_with_vars(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
e: &ExprV0,
|
||||
vars: &mut HashMap<String, ValueId>,
|
||||
) -> Result<(ValueId, BasicBlockId), String> {
|
||||
let mut scope = MapVars::new(vars);
|
||||
lower_expr_with_scope(env, f, cur_bb, e, &mut scope)
|
||||
}
|
||||
|
||||
fn lower_args(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0]) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
fn lower_args(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
args: &[ExprV0],
|
||||
) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
let mut scope = NoVars;
|
||||
lower_args_with_scope(env, f, cur_bb, args, &mut scope)
|
||||
}
|
||||
|
||||
fn lower_args_with_vars(env: &BridgeEnv, f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0], vars: &mut HashMap<String, ValueId>) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
fn lower_args_with_vars(
|
||||
env: &BridgeEnv,
|
||||
f: &mut MirFunction,
|
||||
cur_bb: BasicBlockId,
|
||||
args: &[ExprV0],
|
||||
vars: &mut HashMap<String, ValueId>,
|
||||
) -> Result<(Vec<ValueId>, BasicBlockId), String> {
|
||||
let mut scope = MapVars::new(vars);
|
||||
lower_args_with_scope(env, f, cur_bb, args, &mut scope)
|
||||
}
|
||||
@ -348,12 +517,26 @@ fn lower_stmt_with_vars(
|
||||
match s {
|
||||
StmtV0::Return { expr } => {
|
||||
let (v, cur) = lower_expr_with_vars(env, f, cur_bb, expr, vars)?;
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.set_terminator(MirInstruction::Return { value: Some(v) }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.set_terminator(MirInstruction::Return { value: Some(v) });
|
||||
}
|
||||
Ok(cur)
|
||||
}
|
||||
StmtV0::Extern { iface, method, args } => {
|
||||
StmtV0::Extern {
|
||||
iface,
|
||||
method,
|
||||
args,
|
||||
} => {
|
||||
let (arg_ids, cur) = lower_args_with_vars(env, f, cur_bb, args, vars)?;
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO }); }
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.add_instruction(MirInstruction::ExternCall {
|
||||
dst: None,
|
||||
iface_name: iface.clone(),
|
||||
method_name: method.clone(),
|
||||
args: arg_ids,
|
||||
effects: EffectMask::IO,
|
||||
});
|
||||
}
|
||||
Ok(cur)
|
||||
}
|
||||
StmtV0::Expr { expr } => {
|
||||
@ -367,7 +550,11 @@ fn lower_stmt_with_vars(
|
||||
}
|
||||
StmtV0::Break => {
|
||||
if let Some(ctx) = loop_stack.last().copied() {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { bb.set_terminator(MirInstruction::Jump { target: ctx.exit_bb }); }
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: ctx.exit_bb,
|
||||
});
|
||||
}
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_break","exit_bb": ctx.exit_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
@ -378,7 +565,11 @@ fn lower_stmt_with_vars(
|
||||
}
|
||||
StmtV0::Continue => {
|
||||
if let Some(ctx) = loop_stack.last().copied() {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { bb.set_terminator(MirInstruction::Jump { target: ctx.cond_bb }); }
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: ctx.cond_bb,
|
||||
});
|
||||
}
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({ "id": "loop_continue","cond_bb": ctx.cond_bb.0,"decision": "lower" }),
|
||||
"loop",
|
||||
@ -387,40 +578,89 @@ fn lower_stmt_with_vars(
|
||||
}
|
||||
Ok(cur_bb)
|
||||
}
|
||||
StmtV0::Try { try_body, catches, finally } => {
|
||||
StmtV0::Try {
|
||||
try_body,
|
||||
catches,
|
||||
finally,
|
||||
} => {
|
||||
let try_enabled = std::env::var("NYASH_BRIDGE_TRY_ENABLE").ok().as_deref() == Some("1");
|
||||
if !try_enabled || catches.is_empty() || catches.len() > 1 {
|
||||
let mut tmp_vars = vars.clone();
|
||||
let mut next_bb = lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack, env)?;
|
||||
let mut next_bb =
|
||||
lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars, loop_stack, env)?;
|
||||
if !finally.is_empty() {
|
||||
next_bb = lower_stmt_list_with_vars(f, next_bb, finally, &mut tmp_vars, loop_stack, env)?;
|
||||
next_bb = lower_stmt_list_with_vars(
|
||||
f,
|
||||
next_bb,
|
||||
finally,
|
||||
&mut tmp_vars,
|
||||
loop_stack,
|
||||
env,
|
||||
)?;
|
||||
}
|
||||
*vars = tmp_vars;
|
||||
return Ok(next_bb);
|
||||
}
|
||||
|
||||
let base_vars = vars.clone();
|
||||
let try_bb = next_block_id(f); f.add_block(BasicBlock::new(try_bb));
|
||||
let try_bb = next_block_id(f);
|
||||
f.add_block(BasicBlock::new(try_bb));
|
||||
let catch_clause = &catches[0];
|
||||
let catch_bb = next_block_id(f); f.add_block(BasicBlock::new(catch_bb));
|
||||
let finally_bb = if !finally.is_empty() { let id = next_block_id(f); f.add_block(BasicBlock::new(id)); Some(id) } else { None };
|
||||
let exit_bb = next_block_id(f); f.add_block(BasicBlock::new(exit_bb));
|
||||
let catch_bb = next_block_id(f);
|
||||
f.add_block(BasicBlock::new(catch_bb));
|
||||
let finally_bb = if !finally.is_empty() {
|
||||
let id = next_block_id(f);
|
||||
f.add_block(BasicBlock::new(id));
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let exit_bb = next_block_id(f);
|
||||
f.add_block(BasicBlock::new(exit_bb));
|
||||
let handler_target = finally_bb.unwrap_or(exit_bb);
|
||||
let exception_value = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Catch { exception_type: catch_clause.type_hint.clone(), exception_value, handler_bb: catch_bb });
|
||||
bb.add_instruction(MirInstruction::Catch {
|
||||
exception_type: catch_clause.type_hint.clone(),
|
||||
exception_value,
|
||||
handler_bb: catch_bb,
|
||||
});
|
||||
bb.set_terminator(MirInstruction::Jump { target: try_bb });
|
||||
}
|
||||
let mut try_vars = vars.clone();
|
||||
let try_end = lower_stmt_list_with_vars(f, try_bb, try_body, &mut try_vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(try_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: handler_target }); } }
|
||||
let try_end =
|
||||
lower_stmt_list_with_vars(f, try_bb, try_body, &mut try_vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(try_end) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: handler_target,
|
||||
});
|
||||
}
|
||||
}
|
||||
let try_branch_vars = try_vars.clone();
|
||||
|
||||
let mut catch_vars = base_vars.clone();
|
||||
if let Some(param) = &catch_clause.param { catch_vars.insert(param.clone(), exception_value); }
|
||||
let catch_end = lower_stmt_list_with_vars(f, catch_bb, &catch_clause.body, &mut catch_vars, loop_stack, env)?;
|
||||
if let Some(param) = &catch_clause.param { catch_vars.remove(param); }
|
||||
if let Some(bb) = f.get_block_mut(catch_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: handler_target }); } }
|
||||
if let Some(param) = &catch_clause.param {
|
||||
catch_vars.insert(param.clone(), exception_value);
|
||||
}
|
||||
let catch_end = lower_stmt_list_with_vars(
|
||||
f,
|
||||
catch_bb,
|
||||
&catch_clause.body,
|
||||
&mut catch_vars,
|
||||
loop_stack,
|
||||
env,
|
||||
)?;
|
||||
if let Some(param) = &catch_clause.param {
|
||||
catch_vars.remove(param);
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(catch_end) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump {
|
||||
target: handler_target,
|
||||
});
|
||||
}
|
||||
}
|
||||
let catch_branch_vars = catch_vars.clone();
|
||||
|
||||
use std::collections::HashSet;
|
||||
@ -428,78 +668,220 @@ fn lower_stmt_with_vars(
|
||||
if let Some(finally_block) = finally_bb {
|
||||
let names: HashSet<String> = {
|
||||
let mut set: HashSet<String> = base_vars.keys().cloned().collect();
|
||||
for (_, map) in &branch_vars { set.extend(map.keys().cloned()); }
|
||||
for (_, map) in &branch_vars {
|
||||
set.extend(map.keys().cloned());
|
||||
}
|
||||
set
|
||||
};
|
||||
let mut merged_vars = base_vars.clone();
|
||||
let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new();
|
||||
for name in names {
|
||||
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
||||
for (bbid, map) in &branch_vars { if let Some(&val) = map.get(&name) { inputs.push((*bbid, val)); } }
|
||||
if inputs.is_empty() { if let Some(&base_val) = base_vars.get(&name) { merged_vars.insert(name.clone(), base_val); } continue; }
|
||||
for (bbid, map) in &branch_vars {
|
||||
if let Some(&val) = map.get(&name) {
|
||||
inputs.push((*bbid, val));
|
||||
}
|
||||
}
|
||||
if inputs.is_empty() {
|
||||
if let Some(&base_val) = base_vars.get(&name) {
|
||||
merged_vars.insert(name.clone(), base_val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let unique: HashSet<ValueId> = inputs.iter().map(|(_, v)| *v).collect();
|
||||
if unique.len() == 1 { merged_vars.insert(name.clone(), inputs[0].1); continue; }
|
||||
let dst = f.next_value_id(); inputs.sort_by_key(|(bbid, _)| bbid.0); phi_entries.push((dst, inputs)); merged_vars.insert(name.clone(), dst);
|
||||
if unique.len() == 1 {
|
||||
merged_vars.insert(name.clone(), inputs[0].1);
|
||||
continue;
|
||||
}
|
||||
let dst = f.next_value_id();
|
||||
inputs.sort_by_key(|(bbid, _)| bbid.0);
|
||||
phi_entries.push((dst, inputs));
|
||||
merged_vars.insert(name.clone(), dst);
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(finally_block) {
|
||||
for (dst, inputs) in phi_entries {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs });
|
||||
}
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(finally_block) { for (dst, inputs) in phi_entries { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs }); } }
|
||||
let mut finally_vars = merged_vars.clone();
|
||||
let final_end = lower_stmt_list_with_vars(f, finally_block, finally, &mut finally_vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(final_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: exit_bb }); } }
|
||||
*vars = finally_vars; Ok(exit_bb)
|
||||
let final_end = lower_stmt_list_with_vars(
|
||||
f,
|
||||
finally_block,
|
||||
finally,
|
||||
&mut finally_vars,
|
||||
loop_stack,
|
||||
env,
|
||||
)?;
|
||||
if let Some(bb) = f.get_block_mut(final_end) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump { target: exit_bb });
|
||||
}
|
||||
}
|
||||
*vars = finally_vars;
|
||||
Ok(exit_bb)
|
||||
} else {
|
||||
let names: HashSet<String> = {
|
||||
let mut set: HashSet<String> = base_vars.keys().cloned().collect();
|
||||
for (_, map) in &branch_vars { set.extend(map.keys().cloned()); }
|
||||
for (_, map) in &branch_vars {
|
||||
set.extend(map.keys().cloned());
|
||||
}
|
||||
set
|
||||
};
|
||||
let mut merged_vars = base_vars.clone();
|
||||
let mut phi_entries: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)> = Vec::new();
|
||||
for name in names {
|
||||
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
||||
for (bbid, map) in &branch_vars { if let Some(&val) = map.get(&name) { inputs.push((*bbid, val)); } }
|
||||
if inputs.is_empty() { if let Some(&base_val) = base_vars.get(&name) { merged_vars.insert(name.clone(), base_val); } continue; }
|
||||
for (bbid, map) in &branch_vars {
|
||||
if let Some(&val) = map.get(&name) {
|
||||
inputs.push((*bbid, val));
|
||||
}
|
||||
}
|
||||
if inputs.is_empty() {
|
||||
if let Some(&base_val) = base_vars.get(&name) {
|
||||
merged_vars.insert(name.clone(), base_val);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let unique: HashSet<ValueId> = inputs.iter().map(|(_, v)| *v).collect();
|
||||
if unique.len() == 1 { merged_vars.insert(name.clone(), inputs[0].1); continue; }
|
||||
let dst = f.next_value_id(); inputs.sort_by_key(|(bbid, _)| bbid.0); phi_entries.push((dst, inputs)); merged_vars.insert(name.clone(), dst);
|
||||
if unique.len() == 1 {
|
||||
merged_vars.insert(name.clone(), inputs[0].1);
|
||||
continue;
|
||||
}
|
||||
let dst = f.next_value_id();
|
||||
inputs.sort_by_key(|(bbid, _)| bbid.0);
|
||||
phi_entries.push((dst, inputs));
|
||||
merged_vars.insert(name.clone(), dst);
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(exit_bb) { for (dst, inputs) in phi_entries { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs }); } }
|
||||
*vars = merged_vars; Ok(exit_bb)
|
||||
if let Some(bb) = f.get_block_mut(exit_bb) {
|
||||
for (dst, inputs) in phi_entries {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs });
|
||||
}
|
||||
}
|
||||
*vars = merged_vars;
|
||||
Ok(exit_bb)
|
||||
}
|
||||
}
|
||||
StmtV0::If { cond, then, r#else } => {
|
||||
let (cval, cur) = lower_expr_with_vars(env, f, cur_bb, cond, vars)?;
|
||||
let then_bb = next_block_id(f); let else_bb = BasicBlockId::new(then_bb.0 + 1); let merge_bb = BasicBlockId::new(then_bb.0 + 2);
|
||||
f.add_block(BasicBlock::new(then_bb)); f.add_block(BasicBlock::new(else_bb)); f.add_block(BasicBlock::new(merge_bb));
|
||||
if let Some(bb) = f.get_block_mut(cur) { bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb, else_bb }); }
|
||||
let base_vars = vars.clone(); let mut then_vars = base_vars.clone();
|
||||
let tend = lower_stmt_list_with_vars(f, then_bb, then, &mut then_vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(tend) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } }
|
||||
let then_bb = next_block_id(f);
|
||||
let else_bb = BasicBlockId::new(then_bb.0 + 1);
|
||||
let merge_bb = BasicBlockId::new(then_bb.0 + 2);
|
||||
f.add_block(BasicBlock::new(then_bb));
|
||||
f.add_block(BasicBlock::new(else_bb));
|
||||
f.add_block(BasicBlock::new(merge_bb));
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.set_terminator(MirInstruction::Branch {
|
||||
condition: cval,
|
||||
then_bb,
|
||||
else_bb,
|
||||
});
|
||||
}
|
||||
let base_vars = vars.clone();
|
||||
let mut then_vars = base_vars.clone();
|
||||
let tend =
|
||||
lower_stmt_list_with_vars(f, then_bb, then, &mut then_vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(tend) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
}
|
||||
let (else_end_pred, else_vars) = if let Some(elses) = r#else {
|
||||
let mut ev = base_vars.clone();
|
||||
let eend = lower_stmt_list_with_vars(f, else_bb, elses, &mut ev, loop_stack, env)?;
|
||||
if let Some(bb) = f.get_block_mut(eend) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } }
|
||||
if let Some(bb) = f.get_block_mut(eend) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
}
|
||||
(eend, ev)
|
||||
} else {
|
||||
if let Some(bb) = f.get_block_mut(else_bb) { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
|
||||
if let Some(bb) = f.get_block_mut(else_bb) {
|
||||
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
|
||||
}
|
||||
(else_bb, base_vars.clone())
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
let no_phi = env.mir_no_phi;
|
||||
let mut names: HashSet<String> = base_vars.keys().cloned().collect(); for k in then_vars.keys() { names.insert(k.clone()); } for k in else_vars.keys() { names.insert(k.clone()); }
|
||||
let mut names: HashSet<String> = base_vars.keys().cloned().collect();
|
||||
for k in then_vars.keys() {
|
||||
names.insert(k.clone());
|
||||
}
|
||||
for k in else_vars.keys() {
|
||||
names.insert(k.clone());
|
||||
}
|
||||
for name in names {
|
||||
let tv = then_vars.get(&name).copied(); let ev = else_vars.get(&name).copied(); let exists_base = base_vars.contains_key(&name);
|
||||
let tv = then_vars.get(&name).copied();
|
||||
let ev = else_vars.get(&name).copied();
|
||||
let exists_base = base_vars.contains_key(&name);
|
||||
match (tv, ev, exists_base) {
|
||||
(Some(tval), Some(eval), _) => {
|
||||
let merged = if tval == eval { tval } else { let dst = f.next_value_id(); if no_phi { if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: tval }); } if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: eval }); } } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, eval)] }); } dst }; vars.insert(name, merged);
|
||||
let merged = if tval == eval {
|
||||
tval
|
||||
} else {
|
||||
let dst = f.next_value_id();
|
||||
if no_phi {
|
||||
if let Some(bb) = f.get_block_mut(tend) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: tval });
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(else_end_pred) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: eval });
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: vec![(tend, tval), (else_end_pred, eval)],
|
||||
});
|
||||
}
|
||||
dst
|
||||
};
|
||||
vars.insert(name, merged);
|
||||
}
|
||||
(Some(tval), None, true) => {
|
||||
if let Some(&bval) = base_vars.get(&name) {
|
||||
let merged = if tval == bval { tval } else { let dst = f.next_value_id(); if no_phi { if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: tval }); } if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: bval }); } } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, bval)] }); } dst }; vars.insert(name, merged);
|
||||
let merged = if tval == bval {
|
||||
tval
|
||||
} else {
|
||||
let dst = f.next_value_id();
|
||||
if no_phi {
|
||||
if let Some(bb) = f.get_block_mut(tend) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: tval });
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(else_end_pred) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: bval });
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: vec![(tend, tval), (else_end_pred, bval)],
|
||||
});
|
||||
}
|
||||
dst
|
||||
};
|
||||
vars.insert(name, merged);
|
||||
}
|
||||
}
|
||||
(None, Some(eval), true) => {
|
||||
if let Some(&bval) = base_vars.get(&name) {
|
||||
let merged = if eval == bval { eval } else { let dst = f.next_value_id(); if no_phi { if let Some(bb) = f.get_block_mut(tend) { bb.add_instruction(MirInstruction::Copy { dst, src: bval }); } if let Some(bb) = f.get_block_mut(else_end_pred) { bb.add_instruction(MirInstruction::Copy { dst, src: eval }); } } else if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, bval), (else_end_pred, eval)] }); } dst }; vars.insert(name, merged);
|
||||
let merged = if eval == bval {
|
||||
eval
|
||||
} else {
|
||||
let dst = f.next_value_id();
|
||||
if no_phi {
|
||||
if let Some(bb) = f.get_block_mut(tend) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: bval });
|
||||
}
|
||||
if let Some(bb) = f.get_block_mut(else_end_pred) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: eval });
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: vec![(tend, bval), (else_end_pred, eval)],
|
||||
});
|
||||
}
|
||||
dst
|
||||
};
|
||||
vars.insert(name, merged);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -508,37 +890,90 @@ fn lower_stmt_with_vars(
|
||||
Ok(merge_bb)
|
||||
}
|
||||
StmtV0::Loop { cond, body } => {
|
||||
let cond_bb = next_block_id(f); let body_bb = BasicBlockId::new(cond_bb.0 + 1); let exit_bb = BasicBlockId::new(cond_bb.0 + 2);
|
||||
f.add_block(BasicBlock::new(cond_bb)); f.add_block(BasicBlock::new(body_bb)); f.add_block(BasicBlock::new(exit_bb));
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { if !bb.is_terminated() { bb.add_instruction(MirInstruction::Jump { target: cond_bb }); } }
|
||||
let cond_bb = next_block_id(f);
|
||||
let body_bb = BasicBlockId::new(cond_bb.0 + 1);
|
||||
let exit_bb = BasicBlockId::new(cond_bb.0 + 2);
|
||||
f.add_block(BasicBlock::new(cond_bb));
|
||||
f.add_block(BasicBlock::new(body_bb));
|
||||
f.add_block(BasicBlock::new(exit_bb));
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
if !bb.is_terminated() {
|
||||
bb.add_instruction(MirInstruction::Jump { target: cond_bb });
|
||||
}
|
||||
}
|
||||
let no_phi = env.mir_no_phi;
|
||||
let base_vars = vars.clone(); let orig_names: Vec<String> = base_vars.keys().cloned().collect();
|
||||
let base_vars = vars.clone();
|
||||
let orig_names: Vec<String> = base_vars.keys().cloned().collect();
|
||||
let mut phi_map: HashMap<String, ValueId> = HashMap::new();
|
||||
for name in &orig_names {
|
||||
if let Some(&bval) = base_vars.get(name) {
|
||||
let dst = f.next_value_id();
|
||||
if no_phi {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) { bb.add_instruction(MirInstruction::Copy { dst, src: bval }); }
|
||||
} else if let Some(bb) = f.get_block_mut(cond_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(cur_bb, bval)] }); }
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Copy { dst, src: bval });
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(cond_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: vec![(cur_bb, bval)],
|
||||
});
|
||||
}
|
||||
phi_map.insert(name.clone(), dst);
|
||||
}
|
||||
}
|
||||
for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); }
|
||||
for (name, &phi) in &phi_map {
|
||||
vars.insert(name.clone(), phi);
|
||||
}
|
||||
let (cval, _cend) = lower_expr_with_vars(env, f, cond_bb, cond, vars)?;
|
||||
if let Some(bb) = f.get_block_mut(cond_bb) { bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb: body_bb, else_bb: exit_bb }); }
|
||||
let mut body_vars = vars.clone(); loop_stack.push(LoopContext { cond_bb, exit_bb });
|
||||
let bend_res = lower_stmt_list_with_vars(f, body_bb, body, &mut body_vars, loop_stack, env); loop_stack.pop();
|
||||
if let Some(bb) = f.get_block_mut(cond_bb) {
|
||||
bb.set_terminator(MirInstruction::Branch {
|
||||
condition: cval,
|
||||
then_bb: body_bb,
|
||||
else_bb: exit_bb,
|
||||
});
|
||||
}
|
||||
let mut body_vars = vars.clone();
|
||||
loop_stack.push(LoopContext { cond_bb, exit_bb });
|
||||
let bend_res =
|
||||
lower_stmt_list_with_vars(f, body_bb, body, &mut body_vars, loop_stack, env);
|
||||
loop_stack.pop();
|
||||
let bend = bend_res?;
|
||||
if let Some(bb) = f.get_block_mut(bend) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: cond_bb }); } }
|
||||
if let Some(bb) = f.get_block_mut(bend) {
|
||||
if !bb.is_terminated() {
|
||||
bb.set_terminator(MirInstruction::Jump { target: cond_bb });
|
||||
}
|
||||
}
|
||||
let backedge_to_cond = matches!(f.blocks.get(&bend).and_then(|bb| bb.terminator.as_ref()), Some(MirInstruction::Jump { target, .. }) if *target == cond_bb);
|
||||
if backedge_to_cond {
|
||||
if no_phi {
|
||||
for (name, &phi_dst) in &phi_map { if let Some(&latch_val) = body_vars.get(name) { if let Some(bb) = f.get_block_mut(bend) { bb.add_instruction(MirInstruction::Copy { dst: phi_dst, src: latch_val }); } } }
|
||||
for (name, &phi_dst) in &phi_map {
|
||||
if let Some(&latch_val) = body_vars.get(name) {
|
||||
if let Some(bb) = f.get_block_mut(bend) {
|
||||
bb.add_instruction(MirInstruction::Copy {
|
||||
dst: phi_dst,
|
||||
src: latch_val,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(bb) = f.get_block_mut(cond_bb) {
|
||||
for (name, &phi_dst) in &phi_map { if let Some(&latch_val) = body_vars.get(name) { for inst in &mut bb.instructions { if let MirInstruction::Phi { dst, inputs } = inst { if *dst == phi_dst { inputs.push((bend, latch_val)); break; } } } } }
|
||||
for (name, &phi_dst) in &phi_map {
|
||||
if let Some(&latch_val) = body_vars.get(name) {
|
||||
for inst in &mut bb.instructions {
|
||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||
if *dst == phi_dst {
|
||||
inputs.push((bend, latch_val));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); }
|
||||
for (name, &phi) in &phi_map {
|
||||
vars.insert(name.clone(), phi);
|
||||
}
|
||||
Ok(exit_bb)
|
||||
}
|
||||
}
|
||||
@ -555,29 +990,53 @@ fn lower_stmt_list_with_vars(
|
||||
let mut cur = start_bb;
|
||||
for s in stmts {
|
||||
cur = lower_stmt_with_vars(f, cur, s, vars, loop_stack, env)?;
|
||||
if let Some(bb) = f.blocks.get(&cur) { if bb.is_terminated() { break; } }
|
||||
if let Some(bb) = f.blocks.get(&cur) {
|
||||
if bb.is_terminated() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(cur)
|
||||
}
|
||||
|
||||
pub(super) fn lower_program(prog: ProgramV0) -> Result<MirModule, String> {
|
||||
if prog.body.is_empty() { return Err("empty body".into()); }
|
||||
if prog.body.is_empty() {
|
||||
return Err("empty body".into());
|
||||
}
|
||||
let env = BridgeEnv::load();
|
||||
let mut module = MirModule::new("ny_json_v0".into());
|
||||
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
|
||||
let sig = FunctionSignature {
|
||||
name: "main".into(),
|
||||
params: vec![],
|
||||
return_type: MirType::Integer,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let entry = BasicBlockId::new(0);
|
||||
let mut f = MirFunction::new(sig, entry);
|
||||
let mut var_map: HashMap<String, ValueId> = HashMap::new();
|
||||
let mut loop_stack: Vec<LoopContext> = Vec::new();
|
||||
let start_bb = f.entry_block;
|
||||
let end_bb = lower_stmt_list_with_vars(&mut f, start_bb, &prog.body, &mut var_map, &mut loop_stack, &env)?;
|
||||
let end_bb = lower_stmt_list_with_vars(
|
||||
&mut f,
|
||||
start_bb,
|
||||
&prog.body,
|
||||
&mut var_map,
|
||||
&mut loop_stack,
|
||||
&env,
|
||||
)?;
|
||||
let need_default_ret = f.blocks.iter().any(|(_k, b)| !b.is_terminated());
|
||||
if need_default_ret {
|
||||
let target_bb = end_bb; let dst_id = f.next_value_id();
|
||||
let target_bb = end_bb;
|
||||
let dst_id = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(target_bb) {
|
||||
if !bb.is_terminated() {
|
||||
bb.add_instruction(MirInstruction::Const { dst: dst_id, value: ConstValue::Integer(0) });
|
||||
bb.set_terminator(MirInstruction::Return { value: Some(dst_id) });
|
||||
bb.add_instruction(MirInstruction::Const {
|
||||
dst: dst_id,
|
||||
value: ConstValue::Integer(0),
|
||||
});
|
||||
bb.set_terminator(MirInstruction::Return {
|
||||
value: Some(dst_id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -592,4 +1051,3 @@ pub(super) fn maybe_dump_mir(module: &MirModule) {
|
||||
println!("{}", p.print_module(module));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
mod ast;
|
||||
mod lowering;
|
||||
mod lexer;
|
||||
mod lowering;
|
||||
|
||||
use ast::ProgramV0;
|
||||
use lowering::lower_program;
|
||||
|
||||
pub fn parse_json_v0_to_module(json: &str) -> Result<crate::mir::MirModule, String> {
|
||||
let prog: ProgramV0 = serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?;
|
||||
let prog: ProgramV0 =
|
||||
serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?;
|
||||
if prog.version != 0 || prog.kind != "Program" {
|
||||
return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into());
|
||||
}
|
||||
@ -21,4 +22,6 @@ pub fn parse_source_v0_to_module(input: &str) -> Result<crate::mir::MirModule, S
|
||||
parse_json_v0_to_module(&json)
|
||||
}
|
||||
|
||||
pub fn maybe_dump_mir(module: &crate::mir::MirModule) { lowering::maybe_dump_mir(module) }
|
||||
pub fn maybe_dump_mir(module: &crate::mir::MirModule) {
|
||||
lowering::maybe_dump_mir(module)
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ pub fn emit_mir_json_for_harness(
|
||||
module: &nyash_rust::mir::MirModule,
|
||||
path: &std::path::Path,
|
||||
) -> Result<(), String> {
|
||||
use nyash_rust::mir::{MirInstruction as I, BinaryOp as B, CompareOp as C, MirType};
|
||||
use nyash_rust::mir::{BinaryOp as B, CompareOp as C, MirInstruction as I, MirType};
|
||||
let mut funs = Vec::new();
|
||||
for (name, f) in &module.functions {
|
||||
let mut blocks = Vec::new();
|
||||
@ -24,20 +24,23 @@ pub fn emit_mir_json_for_harness(
|
||||
.map(|(b, v)| json!([v.as_u32(), b.as_u32()]))
|
||||
.collect();
|
||||
// dst_type hint: if all incoming values are String-ish, annotate result as String handle
|
||||
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,
|
||||
}
|
||||
});
|
||||
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}));
|
||||
insts.push(
|
||||
json!({"op":"phi","dst": dst.as_u32(), "incoming": incoming}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +48,11 @@ pub fn emit_mir_json_for_harness(
|
||||
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" };
|
||||
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 } => {
|
||||
@ -70,13 +77,27 @@ pub fn emit_mir_json_for_harness(
|
||||
}
|
||||
}));
|
||||
}
|
||||
nyash_rust::mir::ConstValue::Null | nyash_rust::mir::ConstValue::Void => {
|
||||
nyash_rust::mir::ConstValue::Null
|
||||
| nyash_rust::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 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()});
|
||||
// dst_type hint for string concatenation: if either side is String-ish and op is '+', mark result as String handle
|
||||
if matches!(op, B::Add) {
|
||||
@ -91,16 +112,24 @@ pub fn emit_mir_json_for_harness(
|
||||
_ => false,
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
obj["dst_type"] = json!({"kind":"handle","box_type":"StringBox"});
|
||||
obj["dst_type"] =
|
||||
json!({"kind":"handle","box_type":"StringBox"});
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::Compare { dst, op, lhs, rhs } => {
|
||||
let op_s = match op { C::Lt=>"<", C::Le=>"<=", C::Gt=>">", C::Ge=>">=", C::Eq=>"==", C::Ne=>"!=" };
|
||||
let op_s = match op {
|
||||
C::Lt => "<",
|
||||
C::Le => "<=",
|
||||
C::Gt => ">",
|
||||
C::Ge => ">=",
|
||||
C::Eq => "==",
|
||||
C::Ne => "!=",
|
||||
};
|
||||
let mut obj = json!({"op":"compare","operation": op_s, "lhs": lhs.as_u32(), "rhs": rhs.as_u32(), "dst": dst.as_u32()});
|
||||
// cmp_kind hint for string equality
|
||||
if matches!(op, C::Eq|C::Ne) {
|
||||
if matches!(op, C::Eq | C::Ne) {
|
||||
let lhs_is_str = match f.metadata.value_types.get(lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
@ -117,15 +146,25 @@ pub fn emit_mir_json_for_harness(
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::Call { dst, func, args, .. } => {
|
||||
I::Call {
|
||||
dst, func, args, ..
|
||||
} => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
insts.push(json!({"op":"call","func": func.as_u32(), "args": args_a, "dst": dst.map(|d| d.as_u32())}));
|
||||
}
|
||||
I::ExternCall { dst, iface_name, method_name, args, .. } => {
|
||||
I::ExternCall {
|
||||
dst,
|
||||
iface_name,
|
||||
method_name,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
let func_name = if iface_name == "env.console" {
|
||||
format!("nyash.console.{}", method_name)
|
||||
} else { format!("{}.{}", iface_name, method_name) };
|
||||
} else {
|
||||
format!("{}.{}", iface_name, method_name)
|
||||
};
|
||||
let mut obj = json!({
|
||||
"op": "externcall",
|
||||
"func": func_name,
|
||||
@ -135,30 +174,55 @@ pub fn emit_mir_json_for_harness(
|
||||
// Minimal dst_type hints for known externs
|
||||
if iface_name == "env.console" {
|
||||
// console.* returns i64 status (ignored by user code)
|
||||
if dst.is_some() { obj["dst_type"] = json!("i64"); }
|
||||
if dst.is_some() {
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::BoxCall { dst, box_val, method, args, .. } => {
|
||||
I::BoxCall {
|
||||
dst,
|
||||
box_val,
|
||||
method,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
let args_a: Vec<_> = args.iter().map(|v| json!(v.as_u32())).collect();
|
||||
// Minimal dst_type hints
|
||||
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" {
|
||||
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; }
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(t) = dst_ty {
|
||||
obj["dst_type"] = t;
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::NewBox { dst, box_type, args } => {
|
||||
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 } => {
|
||||
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 } => {
|
||||
@ -195,7 +259,7 @@ 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};
|
||||
use crate::mir::{BinaryOp as B, CompareOp as C, MirInstruction as I, MirType};
|
||||
let mut funs = Vec::new();
|
||||
for (name, f) in &module.functions {
|
||||
let mut blocks = Vec::new();
|
||||
@ -210,53 +274,67 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
.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,
|
||||
}
|
||||
});
|
||||
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}));
|
||||
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::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 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) {
|
||||
@ -270,43 +348,84 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
_ => false,
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
obj["dst_type"] = json!({"kind":"handle","box_type":"StringBox"});
|
||||
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=>">=" };
|
||||
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, .. } => {
|
||||
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"); } }
|
||||
if iface_name == "env.console" {
|
||||
if dst.is_some() {
|
||||
obj["dst_type"] = json!("i64");
|
||||
}
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::BoxCall { dst, box_val, method, args, .. } => {
|
||||
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" {
|
||||
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; }
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(t) = dst_ty {
|
||||
obj["dst_type"] = t;
|
||||
}
|
||||
insts.push(obj);
|
||||
}
|
||||
I::NewBox { dst, box_type, args } => {
|
||||
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 } => {
|
||||
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 } => {
|
||||
@ -315,14 +434,16 @@ pub fn emit_mir_json_for_harness_bin(
|
||||
I::Return { value } => {
|
||||
insts.push(json!({"op":"ret","value": value.map(|v| v.as_u32())}));
|
||||
}
|
||||
_ => { }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(term) = &bb.terminator { match term {
|
||||
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}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*!
|
||||
* Execution Runner Module - Nyash File and Mode Execution Coordinator
|
||||
*
|
||||
*
|
||||
* This module handles all execution logic, backend selection, and mode coordination,
|
||||
* separated from CLI parsing and the main entry point.
|
||||
*/
|
||||
@ -10,28 +10,28 @@ use nyash_rust::cli::CliConfig;
|
||||
// pruned unused runtime imports in this module
|
||||
|
||||
#[cfg(feature = "wasm-backend")]
|
||||
use nyash_rust::backend::{wasm::WasmBackend, aot::AotBackend};
|
||||
use nyash_rust::backend::{aot::AotBackend, wasm::WasmBackend};
|
||||
|
||||
#[cfg(feature = "llvm-inkwell-legacy")]
|
||||
use nyash_rust::backend::{llvm_compile_and_execute};
|
||||
use nyash_rust::backend::llvm_compile_and_execute;
|
||||
use std::{fs, process};
|
||||
mod modes;
|
||||
mod box_index;
|
||||
mod build;
|
||||
mod cli_directives;
|
||||
mod demos;
|
||||
mod dispatch;
|
||||
mod json_v0_bridge;
|
||||
mod mir_json_emit;
|
||||
mod modes;
|
||||
mod pipe_io;
|
||||
mod pipeline;
|
||||
mod cli_directives;
|
||||
mod trace;
|
||||
mod box_index;
|
||||
mod tasks;
|
||||
mod build;
|
||||
mod dispatch;
|
||||
mod selfhost;
|
||||
mod tasks;
|
||||
mod trace;
|
||||
|
||||
// v2 plugin system imports
|
||||
use nyash_rust::runtime;
|
||||
use nyash_rust::runner_plugin_init;
|
||||
use nyash_rust::runtime;
|
||||
// use std::path::PathBuf; // not used in current runner
|
||||
|
||||
/// Resolve a using target according to priority: modules > relative > using-paths
|
||||
@ -68,16 +68,30 @@ impl NyashRunner {
|
||||
// CLI --using SPEC entries (SPEC: 'ns', 'ns as Alias', '"path" as Alias')
|
||||
for spec in &self.config.cli_usings {
|
||||
let s = spec.trim();
|
||||
if s.is_empty() { continue; }
|
||||
if s.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (target, alias) = if let Some(pos) = s.find(" as ") {
|
||||
(s[..pos].trim().to_string(), Some(s[pos+4..].trim().to_string()))
|
||||
} else { (s.to_string(), None) };
|
||||
(
|
||||
s[..pos].trim().to_string(),
|
||||
Some(s[pos + 4..].trim().to_string()),
|
||||
)
|
||||
} else {
|
||||
(s.to_string(), None)
|
||||
};
|
||||
// Normalize quotes for path
|
||||
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||
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()
|
||||
std::path::Path::new(&path)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or("module")
|
||||
.to_string()
|
||||
});
|
||||
pending_using.push((name, Some(path)));
|
||||
} else {
|
||||
@ -96,9 +110,16 @@ impl NyashRunner {
|
||||
// Try to extract quick hints without failing
|
||||
let mut root_info = String::new();
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&s) {
|
||||
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) { root_info = format!(" root='{}'", r); }
|
||||
if let Some(r) = v.get("root_path").and_then(|x| x.as_str()) {
|
||||
root_info = format!(" root='{}'", r);
|
||||
}
|
||||
}
|
||||
eprintln!("[deps] loaded {} bytes from{} {}", bytes, if root_info.is_empty(){""} else {":"}, root_info);
|
||||
eprintln!(
|
||||
"[deps] loaded {} bytes from{} {}",
|
||||
bytes,
|
||||
if root_info.is_empty() { "" } else { ":" },
|
||||
root_info
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[deps] read error: {}", e);
|
||||
@ -107,7 +128,9 @@ impl NyashRunner {
|
||||
}
|
||||
|
||||
// Phase-15: JSON IR v0 bridge (stdin/file)
|
||||
if self.try_run_json_v0_pipe() { return; }
|
||||
if self.try_run_json_v0_pipe() {
|
||||
return;
|
||||
}
|
||||
// Run named task from nyash.toml (MVP)
|
||||
if let Some(task) = self.config.run_task.clone() {
|
||||
if let Err(e) = run_named_task(&task) {
|
||||
@ -117,7 +140,9 @@ impl NyashRunner {
|
||||
return;
|
||||
}
|
||||
// Verbose CLI flag maps to env for downstream helpers/scripts
|
||||
if self.config.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); }
|
||||
if self.config.cli_verbose {
|
||||
std::env::set_var("NYASH_CLI_VERBOSE", "1");
|
||||
}
|
||||
// Script-level env directives (special comments) — parse early
|
||||
// Supported:
|
||||
// // @env KEY=VALUE
|
||||
@ -126,8 +151,13 @@ impl NyashRunner {
|
||||
if let Some(ref filename) = self.config.file {
|
||||
if let Ok(code) = fs::read_to_string(filename) {
|
||||
// Apply script-level directives and lint
|
||||
let strict_fields = std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
||||
if let Err(e) = cli_directives::apply_cli_directives_from_source(&code, strict_fields, self.config.cli_verbose) {
|
||||
let strict_fields =
|
||||
std::env::var("NYASH_FIELDS_TOP_STRICT").ok().as_deref() == Some("1");
|
||||
if let Err(e) = cli_directives::apply_cli_directives_from_source(
|
||||
&code,
|
||||
strict_fields,
|
||||
self.config.cli_verbose,
|
||||
) {
|
||||
eprintln!("❌ Lint/Directive error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
@ -135,13 +165,23 @@ impl NyashRunner {
|
||||
// Env overrides for using rules
|
||||
// Merge late env overrides (if any)
|
||||
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { using_ctx.using_paths.push(p.to_string()); } }
|
||||
for p in paths.split(':') {
|
||||
let p = p.trim();
|
||||
if !p.is_empty() {
|
||||
using_ctx.using_paths.push(p.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(mods) = std::env::var("NYASH_MODULES") {
|
||||
for ent in mods.split(',') {
|
||||
if let Some((k,v)) = ent.split_once('=') {
|
||||
let k = k.trim(); let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() { using_ctx.pending_modules.push((k.to_string(), v.to_string())); }
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
let k = k.trim();
|
||||
let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() {
|
||||
using_ctx
|
||||
.pending_modules
|
||||
.push((k.to_string(), v.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,9 +196,21 @@ impl NyashRunner {
|
||||
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||
let ctx = std::path::Path::new(filename).parent();
|
||||
for (ns, alias) in pending_using.iter() {
|
||||
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx, strict, verbose) {
|
||||
let value = match resolve_using_target(
|
||||
ns,
|
||||
false,
|
||||
&using_ctx.pending_modules,
|
||||
&using_ctx.using_paths,
|
||||
&using_ctx.aliases,
|
||||
ctx,
|
||||
strict,
|
||||
verbose,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ using: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
@ -181,10 +233,10 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// 🏭 Phase 9.78b: Initialize unified registry
|
||||
runtime::init_global_unified_registry();
|
||||
|
||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||
// 🏭 Phase 9.78b: Initialize unified registry
|
||||
runtime::init_global_unified_registry();
|
||||
|
||||
// Try to initialize BID plugins from nyash.toml (best-effort)
|
||||
// Allow disabling during snapshot/CI via NYASH_DISABLE_PLUGINS=1
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
runner_plugin_init::init_bid_plugins();
|
||||
@ -197,29 +249,53 @@ impl NyashRunner {
|
||||
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
|
||||
}
|
||||
// Merge FileBox,TOMLBox with defaults if present
|
||||
let mut override_types: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
|
||||
} else {
|
||||
vec!["ArrayBox".into(), "MapBox".into()]
|
||||
};
|
||||
for t in ["FileBox", "TOMLBox"] { if !override_types.iter().any(|x| x==t) { override_types.push(t.into()); } }
|
||||
let mut override_types: Vec<String> =
|
||||
if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
vec!["ArrayBox".into(), "MapBox".into()]
|
||||
};
|
||||
for t in ["FileBox", "TOMLBox"] {
|
||||
if !override_types.iter().any(|x| x == t) {
|
||||
override_types.push(t.into());
|
||||
}
|
||||
}
|
||||
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
|
||||
|
||||
// Opt-in: load Ny script plugins listed in nyash.toml [ny_plugins]
|
||||
if self.config.load_ny_plugins || std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1") {
|
||||
if self.config.load_ny_plugins
|
||||
|| std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1")
|
||||
{
|
||||
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(np) = doc.get("ny_plugins") {
|
||||
let mut list: Vec<String> = Vec::new();
|
||||
if let Some(arr) = np.as_array() {
|
||||
for v in arr { if let Some(s) = v.as_str() { list.push(s.to_string()); } }
|
||||
for v in arr {
|
||||
if let Some(s) = v.as_str() {
|
||||
list.push(s.to_string());
|
||||
}
|
||||
}
|
||||
} else if let Some(tbl) = np.as_table() {
|
||||
for (_k, v) in tbl { if let Some(s) = v.as_str() { list.push(s.to_string()); }
|
||||
else if let Some(arr) = v.as_array() { for e in arr { if let Some(s) = e.as_str() { list.push(s.to_string()); } } }
|
||||
for (_k, v) in tbl {
|
||||
if let Some(s) = v.as_str() {
|
||||
list.push(s.to_string());
|
||||
} else if let Some(arr) = v.as_array() {
|
||||
for e in arr {
|
||||
if let Some(s) = e.as_str() {
|
||||
list.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !list.is_empty() {
|
||||
let list_only = std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref() == Some("1");
|
||||
let list_only =
|
||||
std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref()
|
||||
== Some("1");
|
||||
println!("🧩 Ny script plugins ({}):", list.len());
|
||||
for p in list {
|
||||
if list_only {
|
||||
@ -229,13 +305,20 @@ impl NyashRunner {
|
||||
// Execute each script best-effort via interpreter
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(code) => {
|
||||
match nyash_rust::parser::NyashParser::parse_from_string(&code) {
|
||||
match nyash_rust::parser::NyashParser::parse_from_string(
|
||||
&code,
|
||||
) {
|
||||
Ok(ast) => {
|
||||
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
|
||||
let mut interpreter =
|
||||
nyash_rust::interpreter::NyashInterpreter::new(
|
||||
);
|
||||
match interpreter.execute(ast) {
|
||||
Ok(_) => println!("[ny_plugins] {}: OK", p),
|
||||
Err(e) => {
|
||||
println!("[ny_plugins] {}: FAIL ({})", p, e);
|
||||
println!(
|
||||
"[ny_plugins] {}: FAIL ({})",
|
||||
p, e
|
||||
);
|
||||
// continue to next
|
||||
}
|
||||
}
|
||||
@ -267,16 +350,26 @@ impl NyashRunner {
|
||||
// Optional: JIT controls via CLI flags (centralized)
|
||||
{
|
||||
// CLI opt-in for JSONL events
|
||||
if self.config.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
|
||||
if self.config.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
|
||||
if self.config.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
|
||||
if let Some(ref p) = self.config.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
|
||||
if self.config.jit_events {
|
||||
std::env::set_var("NYASH_JIT_EVENTS", "1");
|
||||
}
|
||||
if self.config.jit_events_compile {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1");
|
||||
}
|
||||
if self.config.jit_events_runtime {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1");
|
||||
}
|
||||
if let Some(ref p) = self.config.jit_events_path {
|
||||
std::env::set_var("NYASH_JIT_EVENTS_PATH", p);
|
||||
}
|
||||
let mut jc = nyash_rust::jit::config::JitConfig::from_env();
|
||||
jc.exec |= self.config.jit_exec;
|
||||
jc.stats |= self.config.jit_stats;
|
||||
jc.stats_json |= self.config.jit_stats_json;
|
||||
jc.dump |= self.config.jit_dump;
|
||||
if self.config.jit_threshold.is_some() { jc.threshold = self.config.jit_threshold; }
|
||||
if self.config.jit_threshold.is_some() {
|
||||
jc.threshold = self.config.jit_threshold;
|
||||
}
|
||||
jc.phi_min |= self.config.jit_phi_min;
|
||||
jc.hostcall |= self.config.jit_hostcall;
|
||||
jc.handle_debug |= self.config.jit_handle_debug;
|
||||
@ -286,8 +379,12 @@ impl NyashRunner {
|
||||
let events_on = std::env::var("NYASH_JIT_EVENTS").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_COMPILE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_EVENTS_RUNTIME").ok().as_deref() == Some("1");
|
||||
if events_on && jc.threshold.is_none() { jc.threshold = Some(1); }
|
||||
if self.config.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
|
||||
if events_on && jc.threshold.is_none() {
|
||||
jc.threshold = Some(1);
|
||||
}
|
||||
if self.config.jit_only {
|
||||
std::env::set_var("NYASH_JIT_ONLY", "1");
|
||||
}
|
||||
// Apply runtime capability probe (e.g., disable b1 ABI if unsupported)
|
||||
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||
jc = nyash_rust::jit::config::apply_runtime_caps(jc, caps);
|
||||
@ -316,7 +413,7 @@ impl NyashRunner {
|
||||
println!("====================================");
|
||||
println!("Running {} iterations per test...", self.config.iterations);
|
||||
println!();
|
||||
|
||||
|
||||
self.execute_benchmark_mode();
|
||||
return;
|
||||
}
|
||||
@ -350,12 +447,13 @@ impl NyashRunner {
|
||||
impl NyashRunner {
|
||||
/// Run a file through independent JIT engine (no VM execute loop)
|
||||
fn run_file_jit_direct(&self, filename: &str) {
|
||||
use nyash_rust::{mir::MirCompiler, parser::NyashParser};
|
||||
use std::fs;
|
||||
use nyash_rust::{parser::NyashParser, mir::MirCompiler};
|
||||
// Small helper for unified error output (text or JSON)
|
||||
let emit_err = |phase: &str, code: &str, msg: &str| {
|
||||
if std::env::var("NYASH_JIT_STATS_JSON").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_JIT_ERROR_JSON").ok().as_deref() == Some("1") {
|
||||
|| std::env::var("NYASH_JIT_ERROR_JSON").ok().as_deref() == Some("1")
|
||||
{
|
||||
let payload = serde_json::json!({
|
||||
"kind": "jit_direct_error",
|
||||
"phase": phase,
|
||||
@ -371,14 +469,33 @@ impl NyashRunner {
|
||||
// Require cranelift feature at runtime by attempting compile; if unavailable compile_function returns None
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(s) => s,
|
||||
Err(e) => { emit_err("read_file", "IO", &format!("{}", e)); std::process::exit(1); }
|
||||
Err(e) => {
|
||||
emit_err("read_file", "IO", &format!("{}", e));
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(a) => a, Err(e) => { emit_err("parse", "SYNTAX", &format!("{}", e)); std::process::exit(1); }
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
emit_err("parse", "SYNTAX", &format!("{}", e));
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = match mc.compile(ast) { Ok(m) => m, Err(e) => { emit_err("mir", "MIR_COMPILE", &format!("{}", e)); std::process::exit(1); } };
|
||||
let func = match cr.module.functions.get("main") { Some(f) => f, None => { emit_err("mir", "NO_MAIN", "No main function found"); std::process::exit(1); } };
|
||||
let cr = match mc.compile(ast) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
emit_err("mir", "MIR_COMPILE", &format!("{}", e));
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let func = match cr.module.functions.get("main") {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
emit_err("mir", "NO_MAIN", "No main function found");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Guard: refuse write-effects in jit-direct when policy.read_only
|
||||
{
|
||||
@ -393,11 +510,20 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
if let Some(term) = &bb.terminator {
|
||||
if term.effects().contains(Effect::WriteHeap) { writes += 1; }
|
||||
if term.effects().contains(Effect::WriteHeap) {
|
||||
writes += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy.read_only && writes > 0 {
|
||||
emit_err("policy", "WRITE_EFFECTS", &format!("write-effects detected ({} ops). jit-direct is read-only at this stage.", writes));
|
||||
emit_err(
|
||||
"policy",
|
||||
"WRITE_EFFECTS",
|
||||
&format!(
|
||||
"write-effects detected ({} ops). jit-direct is read-only at this stage.",
|
||||
writes
|
||||
),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@ -417,63 +543,141 @@ impl NyashRunner {
|
||||
match engine.compile_function("main", func) {
|
||||
Some(h) => {
|
||||
// Optional event: compile
|
||||
nyash_rust::jit::events::emit("compile", &func.signature.name, Some(h), None, serde_json::json!({}));
|
||||
nyash_rust::jit::events::emit(
|
||||
"compile",
|
||||
&func.signature.name,
|
||||
Some(h),
|
||||
None,
|
||||
serde_json::json!({}),
|
||||
);
|
||||
// Parse JIT args from env: NYASH_JIT_ARGS (comma-separated), with optional type prefixes
|
||||
// Formats per arg: i:123, f:3.14, b:true/false, h:42 (handle), or bare numbers (int), true/false (bool)
|
||||
let mut jit_args: Vec<nyash_rust::jit::abi::JitValue> = Vec::new();
|
||||
if let Ok(s) = std::env::var("NYASH_JIT_ARGS") {
|
||||
for raw in s.split(',') {
|
||||
let t = raw.trim();
|
||||
if t.is_empty() { continue; }
|
||||
if t.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let v = if let Some(rest) = t.strip_prefix("i:") {
|
||||
rest.parse::<i64>().ok().map(nyash_rust::jit::abi::JitValue::I64)
|
||||
rest.parse::<i64>()
|
||||
.ok()
|
||||
.map(nyash_rust::jit::abi::JitValue::I64)
|
||||
} else if let Some(rest) = t.strip_prefix("f:") {
|
||||
rest.parse::<f64>().ok().map(nyash_rust::jit::abi::JitValue::F64)
|
||||
rest.parse::<f64>()
|
||||
.ok()
|
||||
.map(nyash_rust::jit::abi::JitValue::F64)
|
||||
} else if let Some(rest) = t.strip_prefix("b:") {
|
||||
let b = matches!(rest, "1"|"true"|"True"|"TRUE");
|
||||
let b = matches!(rest, "1" | "true" | "True" | "TRUE");
|
||||
Some(nyash_rust::jit::abi::JitValue::Bool(b))
|
||||
} else if let Some(rest) = t.strip_prefix("h:") {
|
||||
rest.parse::<u64>().ok().map(nyash_rust::jit::abi::JitValue::Handle)
|
||||
} else if t.eq_ignore_ascii_case("true") || t == "1" { Some(nyash_rust::jit::abi::JitValue::Bool(true)) }
|
||||
else if t.eq_ignore_ascii_case("false") || t == "0" { Some(nyash_rust::jit::abi::JitValue::Bool(false)) }
|
||||
else if let Ok(iv) = t.parse::<i64>() { Some(nyash_rust::jit::abi::JitValue::I64(iv)) }
|
||||
else if let Ok(fv) = t.parse::<f64>() { Some(nyash_rust::jit::abi::JitValue::F64(fv)) }
|
||||
else { None };
|
||||
if let Some(jv) = v { jit_args.push(jv); }
|
||||
rest.parse::<u64>()
|
||||
.ok()
|
||||
.map(nyash_rust::jit::abi::JitValue::Handle)
|
||||
} else if t.eq_ignore_ascii_case("true") || t == "1" {
|
||||
Some(nyash_rust::jit::abi::JitValue::Bool(true))
|
||||
} else if t.eq_ignore_ascii_case("false") || t == "0" {
|
||||
Some(nyash_rust::jit::abi::JitValue::Bool(false))
|
||||
} else if let Ok(iv) = t.parse::<i64>() {
|
||||
Some(nyash_rust::jit::abi::JitValue::I64(iv))
|
||||
} else if let Ok(fv) = t.parse::<f64>() {
|
||||
Some(nyash_rust::jit::abi::JitValue::F64(fv))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(jv) = v {
|
||||
jit_args.push(jv);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Coerce args to expected MIR types
|
||||
use nyash_rust::mir::MirType;
|
||||
let expected = &func.signature.params;
|
||||
if expected.len() != jit_args.len() {
|
||||
emit_err("args", "COUNT_MISMATCH", &format!("expected={}, passed={}", expected.len(), jit_args.len()));
|
||||
emit_err(
|
||||
"args",
|
||||
"COUNT_MISMATCH",
|
||||
&format!("expected={}, passed={}", expected.len(), jit_args.len()),
|
||||
);
|
||||
eprintln!("Hint: set NYASH_JIT_ARGS as comma-separated values, e.g., i:42,f:3.14,b:true");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let mut coerced: Vec<nyash_rust::jit::abi::JitValue> = Vec::with_capacity(jit_args.len());
|
||||
let mut coerced: Vec<nyash_rust::jit::abi::JitValue> =
|
||||
Vec::with_capacity(jit_args.len());
|
||||
for (i, (exp, got)) in expected.iter().zip(jit_args.iter()).enumerate() {
|
||||
let cv = match exp {
|
||||
MirType::Integer => match got {
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::I64(*v),
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::I64(*f as i64),
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::I64(if *b {1} else {0}),
|
||||
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Integer", i)); std::process::exit(1); }
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => {
|
||||
nyash_rust::jit::abi::JitValue::I64(*v)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => {
|
||||
nyash_rust::jit::abi::JitValue::I64(*f as i64)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
||||
nyash_rust::jit::abi::JitValue::I64(if *b { 1 } else { 0 })
|
||||
}
|
||||
_ => {
|
||||
emit_err(
|
||||
"args",
|
||||
"TYPE_MISMATCH",
|
||||
&format!("param#{} expects Integer", i),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
MirType::Float => match got {
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::F64(*f),
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::F64(*v as f64),
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::F64(if *b {1.0} else {0.0}),
|
||||
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Float", i)); std::process::exit(1); }
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => {
|
||||
nyash_rust::jit::abi::JitValue::F64(*f)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => {
|
||||
nyash_rust::jit::abi::JitValue::F64(*v as f64)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
||||
nyash_rust::jit::abi::JitValue::F64(if *b { 1.0 } else { 0.0 })
|
||||
}
|
||||
_ => {
|
||||
emit_err(
|
||||
"args",
|
||||
"TYPE_MISMATCH",
|
||||
&format!("param#{} expects Float", i),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
MirType::Bool => match got {
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => nyash_rust::jit::abi::JitValue::Bool(*b),
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => nyash_rust::jit::abi::JitValue::Bool(*v != 0),
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => nyash_rust::jit::abi::JitValue::Bool(*f != 0.0),
|
||||
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects Bool", i)); std::process::exit(1); }
|
||||
nyash_rust::jit::abi::JitValue::Bool(b) => {
|
||||
nyash_rust::jit::abi::JitValue::Bool(*b)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::I64(v) => {
|
||||
nyash_rust::jit::abi::JitValue::Bool(*v != 0)
|
||||
}
|
||||
nyash_rust::jit::abi::JitValue::F64(f) => {
|
||||
nyash_rust::jit::abi::JitValue::Bool(*f != 0.0)
|
||||
}
|
||||
_ => {
|
||||
emit_err(
|
||||
"args",
|
||||
"TYPE_MISMATCH",
|
||||
&format!("param#{} expects Bool", i),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
MirType::String | MirType::Box(_) | MirType::Array(_) | MirType::Future(_) => match got {
|
||||
nyash_rust::jit::abi::JitValue::Handle(h) => nyash_rust::jit::abi::JitValue::Handle(*h),
|
||||
_ => { emit_err("args", "TYPE_MISMATCH", &format!("param#{} expects handle (h:<id>)", i)); std::process::exit(1); }
|
||||
MirType::String
|
||||
| MirType::Box(_)
|
||||
| MirType::Array(_)
|
||||
| MirType::Future(_) => match got {
|
||||
nyash_rust::jit::abi::JitValue::Handle(h) => {
|
||||
nyash_rust::jit::abi::JitValue::Handle(*h)
|
||||
}
|
||||
_ => {
|
||||
emit_err(
|
||||
"args",
|
||||
"TYPE_MISMATCH",
|
||||
&format!("param#{} expects handle (h:<id>)", i),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
MirType::Void | MirType::Unknown => {
|
||||
// Keep as-is
|
||||
@ -488,32 +692,66 @@ impl NyashRunner {
|
||||
match out {
|
||||
Some(v) => {
|
||||
let ms = t0.elapsed().as_millis();
|
||||
nyash_rust::jit::events::emit("execute", &func.signature.name, Some(h), Some(ms), serde_json::json!({}));
|
||||
nyash_rust::jit::events::emit(
|
||||
"execute",
|
||||
&func.signature.name,
|
||||
Some(h),
|
||||
Some(ms),
|
||||
serde_json::json!({}),
|
||||
);
|
||||
// Normalize result according to MIR return type for friendly output
|
||||
use nyash_rust::mir::MirType;
|
||||
let ret_ty = &func.signature.return_type;
|
||||
let vmv = match (ret_ty, v) {
|
||||
(MirType::Bool, nyash_rust::jit::abi::JitValue::I64(i)) => nyash_rust::backend::vm::VMValue::Bool(i != 0),
|
||||
(MirType::Bool, nyash_rust::jit::abi::JitValue::Bool(b)) => nyash_rust::backend::vm::VMValue::Bool(b),
|
||||
(MirType::Float, nyash_rust::jit::abi::JitValue::F64(f)) => nyash_rust::backend::vm::VMValue::Float(f),
|
||||
(MirType::Float, nyash_rust::jit::abi::JitValue::I64(i)) => nyash_rust::backend::vm::VMValue::Float(i as f64),
|
||||
(MirType::Bool, nyash_rust::jit::abi::JitValue::I64(i)) => {
|
||||
nyash_rust::backend::vm::VMValue::Bool(i != 0)
|
||||
}
|
||||
(MirType::Bool, nyash_rust::jit::abi::JitValue::Bool(b)) => {
|
||||
nyash_rust::backend::vm::VMValue::Bool(b)
|
||||
}
|
||||
(MirType::Float, nyash_rust::jit::abi::JitValue::F64(f)) => {
|
||||
nyash_rust::backend::vm::VMValue::Float(f)
|
||||
}
|
||||
(MirType::Float, nyash_rust::jit::abi::JitValue::I64(i)) => {
|
||||
nyash_rust::backend::vm::VMValue::Float(i as f64)
|
||||
}
|
||||
// Default adapter for other combos
|
||||
_ => nyash_rust::jit::abi::adapter::from_jit_value(v),
|
||||
};
|
||||
println!("✅ JIT-direct execution completed successfully!");
|
||||
// Pretty print with expected type tag
|
||||
let (ety, sval) = match (ret_ty, &vmv) {
|
||||
(MirType::Bool, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
|
||||
(MirType::Float, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
|
||||
(MirType::Integer, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
|
||||
(MirType::Bool, nyash_rust::backend::vm::VMValue::Bool(b)) => {
|
||||
("Bool", b.to_string())
|
||||
}
|
||||
(MirType::Float, nyash_rust::backend::vm::VMValue::Float(f)) => {
|
||||
("Float", format!("{}", f))
|
||||
}
|
||||
(MirType::Integer, nyash_rust::backend::vm::VMValue::Integer(i)) => {
|
||||
("Integer", i.to_string())
|
||||
}
|
||||
// Fallbacks
|
||||
(_, nyash_rust::backend::vm::VMValue::Integer(i)) => ("Integer", i.to_string()),
|
||||
(_, nyash_rust::backend::vm::VMValue::Float(f)) => ("Float", format!("{}", f)),
|
||||
(_, nyash_rust::backend::vm::VMValue::Bool(b)) => ("Bool", b.to_string()),
|
||||
(_, nyash_rust::backend::vm::VMValue::String(s)) => ("String", s.clone()),
|
||||
(_, nyash_rust::backend::vm::VMValue::BoxRef(arc)) => ("BoxRef", arc.type_name().to_string()),
|
||||
(_, nyash_rust::backend::vm::VMValue::Future(_)) => ("Future", "<future>".to_string()),
|
||||
(_, nyash_rust::backend::vm::VMValue::Void) => ("Void", "void".to_string()),
|
||||
(_, nyash_rust::backend::vm::VMValue::Integer(i)) => {
|
||||
("Integer", i.to_string())
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::Float(f)) => {
|
||||
("Float", format!("{}", f))
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::Bool(b)) => {
|
||||
("Bool", b.to_string())
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::String(s)) => {
|
||||
("String", s.clone())
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::BoxRef(arc)) => {
|
||||
("BoxRef", arc.type_name().to_string())
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::Future(_)) => {
|
||||
("Future", "<future>".to_string())
|
||||
}
|
||||
(_, nyash_rust::backend::vm::VMValue::Void) => {
|
||||
("Void", "void".to_string())
|
||||
}
|
||||
};
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
@ -522,7 +760,11 @@ impl NyashRunner {
|
||||
let cfg = nyash_rust::jit::config::current();
|
||||
let caps = nyash_rust::jit::config::probe_capabilities();
|
||||
let (phi_t, phi_b1, ret_b) = engine.last_lower_stats();
|
||||
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig { "b1_bool" } else { "i64_bool" };
|
||||
let abi_mode = if cfg.native_bool_abi && caps.supports_b1_sig {
|
||||
"b1_bool"
|
||||
} else {
|
||||
"i64_bool"
|
||||
};
|
||||
let payload = serde_json::json!({
|
||||
"version": 1,
|
||||
"function": func.signature.name,
|
||||
@ -539,14 +781,28 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
nyash_rust::jit::events::emit("fallback", &func.signature.name, Some(h), None, serde_json::json!({"reason":"trap_or_missing"}));
|
||||
emit_err("execute", "TRAP_OR_MISSING", "execution failed (trap or missing handle)");
|
||||
nyash_rust::jit::events::emit(
|
||||
"fallback",
|
||||
&func.signature.name,
|
||||
Some(h),
|
||||
None,
|
||||
serde_json::json!({"reason":"trap_or_missing"}),
|
||||
);
|
||||
emit_err(
|
||||
"execute",
|
||||
"TRAP_OR_MISSING",
|
||||
"execution failed (trap or missing handle)",
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
emit_err("compile", "UNAVAILABLE", "Build with --features cranelift-jit");
|
||||
emit_err(
|
||||
"compile",
|
||||
"UNAVAILABLE",
|
||||
"Build with --features cranelift-jit",
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use super::super::NyashRunner;
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
use std::{process::Command, process};
|
||||
use std::{process, process::Command};
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute AOT compilation mode (split)
|
||||
@ -11,7 +11,16 @@ impl NyashRunner {
|
||||
let status = if cfg!(target_os = "windows") {
|
||||
// Use PowerShell helper; falls back to bash if available inside the script
|
||||
Command::new("powershell")
|
||||
.args(["-ExecutionPolicy","Bypass","-File","tools/build_aot.ps1","-Input", filename, "-Out", &format!("{}.exe", output)])
|
||||
.args([
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
"tools/build_aot.ps1",
|
||||
"-Input",
|
||||
filename,
|
||||
"-Out",
|
||||
&format!("{}.exe", output),
|
||||
])
|
||||
.status()
|
||||
} else {
|
||||
Command::new("bash")
|
||||
@ -20,15 +29,24 @@ impl NyashRunner {
|
||||
};
|
||||
match status {
|
||||
Ok(s) if s.success() => {
|
||||
println!("✅ AOT compilation successful!\nExecutable written to: {}", output);
|
||||
println!(
|
||||
"✅ AOT compilation successful!\nExecutable written to: {}",
|
||||
output
|
||||
);
|
||||
}
|
||||
Ok(s) => {
|
||||
eprintln!("❌ AOT compilation failed (exit={} ). See logs above.", s.code().unwrap_or(-1));
|
||||
eprintln!(
|
||||
"❌ AOT compilation failed (exit={} ). See logs above.",
|
||||
s.code().unwrap_or(-1)
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to invoke build_aot.sh: {}", e);
|
||||
eprintln!("Hint: ensure bash is available, or run: bash tools/build_aot.sh {} -o {}", filename, output);
|
||||
eprintln!(
|
||||
"Hint: ensure bash is available, or run: bash tools/build_aot.sh {} -o {}",
|
||||
filename, output
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
use super::super::NyashRunner;
|
||||
use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter, mir::MirCompiler, backend::VM};
|
||||
use nyash_rust::{
|
||||
backend::VM, interpreter::NyashInterpreter, mir::MirCompiler, parser::NyashParser,
|
||||
};
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute benchmark mode (split)
|
||||
pub(crate) fn execute_benchmark_mode(&self) {
|
||||
println!("🏁 Running benchmark mode with {} iterations", self.config.iterations);
|
||||
println!(
|
||||
"🏁 Running benchmark mode with {} iterations",
|
||||
self.config.iterations
|
||||
);
|
||||
// Tests: some run on all backends, some are JIT+f64 only
|
||||
// Third element indicates JIT+f64 only (skip VM/Interpreter)
|
||||
let tests: Vec<(&str, &str, bool)> = vec![
|
||||
@ -63,14 +68,21 @@ impl NyashRunner {
|
||||
println!("\n====================================");
|
||||
println!("🧪 Test: {}", name);
|
||||
if jit_f64_only {
|
||||
println!("(JIT+f64 only) Skipping VM/Interpreter; requires --features cranelift-jit");
|
||||
println!(
|
||||
"(JIT+f64 only) Skipping VM/Interpreter; requires --features cranelift-jit"
|
||||
);
|
||||
// Warmup JIT
|
||||
let warmup = (self.config.iterations / 10).max(1);
|
||||
self.bench_jit(code, warmup);
|
||||
// Measured
|
||||
let jit_time = self.bench_jit(code, self.config.iterations);
|
||||
println!("\n📊 Performance Summary [{}]:", name);
|
||||
println!(" JIT f64 ops: {} iters in {:?} ({:.2} ops/sec)", self.config.iterations, jit_time, self.config.iterations as f64 / jit_time.as_secs_f64());
|
||||
println!(
|
||||
" JIT f64 ops: {} iters in {:?} ({:.2} ops/sec)",
|
||||
self.config.iterations,
|
||||
jit_time,
|
||||
self.config.iterations as f64 / jit_time.as_secs_f64()
|
||||
);
|
||||
} else {
|
||||
// Quick correctness check across modes (golden): Interpreter vs VM vs VM+JIT
|
||||
if let Err(e) = self.verify_outputs_match(code) {
|
||||
@ -93,8 +105,28 @@ impl NyashRunner {
|
||||
let vm_vs_interp = interpreter_time.as_secs_f64() / vm_time.as_secs_f64();
|
||||
let jit_vs_vm = vm_time.as_secs_f64() / jit_time.as_secs_f64();
|
||||
println!("\n📊 Performance Summary [{}]:", name);
|
||||
println!(" VM is {:.2}x {} than Interpreter", if vm_vs_interp > 1.0 { vm_vs_interp } else { 1.0 / vm_vs_interp }, if vm_vs_interp > 1.0 { "faster" } else { "slower" });
|
||||
println!(" JIT is {:.2}x {} than VM (note: compile cost included)", if jit_vs_vm > 1.0 { jit_vs_vm } else { 1.0 / jit_vs_vm }, if jit_vs_vm > 1.0 { "faster" } else { "slower" });
|
||||
println!(
|
||||
" VM is {:.2}x {} than Interpreter",
|
||||
if vm_vs_interp > 1.0 {
|
||||
vm_vs_interp
|
||||
} else {
|
||||
1.0 / vm_vs_interp
|
||||
},
|
||||
if vm_vs_interp > 1.0 {
|
||||
"faster"
|
||||
} else {
|
||||
"slower"
|
||||
}
|
||||
);
|
||||
println!(
|
||||
" JIT is {:.2}x {} than VM (note: compile cost included)",
|
||||
if jit_vs_vm > 1.0 {
|
||||
jit_vs_vm
|
||||
} else {
|
||||
1.0 / jit_vs_vm
|
||||
},
|
||||
if jit_vs_vm > 1.0 { "faster" } else { "slower" }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,7 +142,12 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
println!(" ⚡ Interpreter: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
println!(
|
||||
" ⚡ Interpreter: {} iters in {:?} ({:.2} ops/sec)",
|
||||
iters,
|
||||
elapsed,
|
||||
iters as f64 / elapsed.as_secs_f64()
|
||||
);
|
||||
elapsed
|
||||
}
|
||||
|
||||
@ -126,7 +163,12 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
println!(" 🚀 VM: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
println!(
|
||||
" 🚀 VM: {} iters in {:?} ({:.2} ops/sec)",
|
||||
iters,
|
||||
elapsed,
|
||||
iters as f64 / elapsed.as_secs_f64()
|
||||
);
|
||||
elapsed
|
||||
}
|
||||
|
||||
@ -134,8 +176,12 @@ impl NyashRunner {
|
||||
// Force JIT mode for this run
|
||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||
if self.config.jit_stats { std::env::set_var("NYASH_JIT_STATS", "1"); }
|
||||
if self.config.jit_stats_json { std::env::set_var("NYASH_JIT_STATS_JSON", "1"); }
|
||||
if self.config.jit_stats {
|
||||
std::env::set_var("NYASH_JIT_STATS", "1");
|
||||
}
|
||||
if self.config.jit_stats_json {
|
||||
std::env::set_var("NYASH_JIT_STATS_JSON", "1");
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
for _ in 0..iters {
|
||||
if let Ok(ast) = NyashParser::parse_from_string(code) {
|
||||
@ -147,7 +193,12 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
let elapsed = start.elapsed();
|
||||
println!(" 🔥 JIT: {} iters in {:?} ({:.2} ops/sec)", iters, elapsed, iters as f64 / elapsed.as_secs_f64());
|
||||
println!(
|
||||
" 🔥 JIT: {} iters in {:?} ({:.2} ops/sec)",
|
||||
iters,
|
||||
elapsed,
|
||||
iters as f64 / elapsed.as_secs_f64()
|
||||
);
|
||||
elapsed
|
||||
}
|
||||
|
||||
@ -155,22 +206,28 @@ impl NyashRunner {
|
||||
fn verify_outputs_match(&self, code: &str) -> Result<(), String> {
|
||||
// VM
|
||||
let vm_out = {
|
||||
let ast = NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||
let ast =
|
||||
NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = mc.compile(ast).map_err(|e| format!("vm compile: {}", e))?;
|
||||
let mut vm = VM::new();
|
||||
let out = vm.execute_module(&cr.module).map_err(|e| format!("vm exec: {}", e))?;
|
||||
let out = vm
|
||||
.execute_module(&cr.module)
|
||||
.map_err(|e| format!("vm exec: {}", e))?;
|
||||
out.to_string_box().value
|
||||
};
|
||||
// VM+JIT
|
||||
let jit_out = {
|
||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||
let ast = NyashParser::parse_from_string(code).map_err(|e| format!("jit parse: {}", e))?;
|
||||
let ast =
|
||||
NyashParser::parse_from_string(code).map_err(|e| format!("jit parse: {}", e))?;
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = mc.compile(ast).map_err(|e| format!("jit compile: {}", e))?;
|
||||
let mut vm = VM::new();
|
||||
let out = vm.execute_module(&cr.module).map_err(|e| format!("jit exec: {}", e))?;
|
||||
let out = vm
|
||||
.execute_module(&cr.module)
|
||||
.map_err(|e| format!("jit exec: {}", e))?;
|
||||
out.to_string_box().value
|
||||
};
|
||||
if vm_out != jit_out {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::parser::NyashParser;
|
||||
use crate::interpreter::NyashInterpreter;
|
||||
use crate::runner_plugin_init;
|
||||
use crate::parser::NyashParser;
|
||||
use crate::runner::pipeline::{resolve_using_target, UsingContext};
|
||||
use crate::runner_plugin_init;
|
||||
use std::{fs, process};
|
||||
|
||||
/// Execute Nyash file with interpreter
|
||||
@ -22,11 +22,11 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
|
||||
println!("📝 File contents:\n{}", code);
|
||||
println!("\n🚀 Parsing and executing...\n");
|
||||
|
||||
|
||||
// Test: immediate file creation (use relative path to avoid sandbox issues)
|
||||
std::fs::create_dir_all("development/debug_hang_issue").ok();
|
||||
std::fs::write("development/debug_hang_issue/test.txt", "START").ok();
|
||||
|
||||
|
||||
// Optional: using pre-processing (strip lines and register modules)
|
||||
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);
|
||||
@ -39,13 +39,25 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
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");
|
||||
(
|
||||
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()
|
||||
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 {
|
||||
@ -57,7 +69,11 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
out.push('\n');
|
||||
}
|
||||
// Resolve and register
|
||||
let using_ctx = UsingContext { using_paths: vec!["apps".into(), "lib".into(), ".".into()], pending_modules: vec![], aliases: std::collections::HashMap::new() };
|
||||
let using_ctx = UsingContext {
|
||||
using_paths: vec!["apps".into(), "lib".into(), ".".into()],
|
||||
pending_modules: vec![],
|
||||
aliases: std::collections::HashMap::new(),
|
||||
};
|
||||
let strict = std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1");
|
||||
let verbose = std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1");
|
||||
let ctx_dir = std::path::Path::new(filename).parent();
|
||||
@ -66,12 +82,24 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
let sb = crate::box_trait::StringBox::new(path);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
} else {
|
||||
match resolve_using_target(&ns_or_alias, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx_dir, strict, verbose) {
|
||||
match resolve_using_target(
|
||||
&ns_or_alias,
|
||||
false,
|
||||
&using_ctx.pending_modules,
|
||||
&using_ctx.using_paths,
|
||||
&using_ctx.aliases,
|
||||
ctx_dir,
|
||||
strict,
|
||||
verbose,
|
||||
) {
|
||||
Ok(value) => {
|
||||
let sb = crate::box_trait::StringBox::new(value);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
}
|
||||
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ using: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,30 +112,30 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
Ok(ast) => {
|
||||
eprintln!("🔍 DEBUG: Parse completed, AST created");
|
||||
ast
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
eprintln!("🔍 DEBUG: About to print parse success message...");
|
||||
println!("✅ Parse successful!");
|
||||
eprintln!("🔍 DEBUG: Parse success message printed");
|
||||
|
||||
|
||||
// Debug log file write
|
||||
if let Ok(mut file) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("development/debug_hang_issue/debug_trace.log")
|
||||
.open("development/debug_hang_issue/debug_trace.log")
|
||||
{
|
||||
use std::io::Write;
|
||||
let _ = writeln!(file, "=== MAIN: Parse successful ===");
|
||||
let _ = file.flush();
|
||||
}
|
||||
|
||||
|
||||
eprintln!("🔍 DEBUG: Creating interpreter...");
|
||||
|
||||
|
||||
// Execute the AST
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
eprintln!("🔍 DEBUG: Starting execution...");
|
||||
@ -116,9 +144,12 @@ pub fn execute_nyash_file(filename: &str, debug_fuel: Option<usize>) {
|
||||
println!("✅ Execution completed successfully!");
|
||||
println!("Result: {}", result.to_string_box().value);
|
||||
// Structured concurrency: best-effort join of spawned tasks at program end
|
||||
let join_ms: u64 = std::env::var("NYASH_JOIN_ALL_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
||||
let join_ms: u64 = std::env::var("NYASH_JOIN_ALL_MS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(2000);
|
||||
crate::runtime::global_hooks::join_all_registered_futures(join_ms);
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
// Use enhanced error reporting with source context
|
||||
eprintln!("❌ Runtime error:\n{}", e.detailed_message(Some(&code)));
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
use super::super::NyashRunner;
|
||||
use nyash_rust::{parser::NyashParser, mir::{MirCompiler, MirInstruction}};
|
||||
use nyash_rust::mir::passes::method_id_inject::inject_method_ids;
|
||||
use nyash_rust::{
|
||||
mir::{MirCompiler, MirInstruction},
|
||||
parser::NyashParser,
|
||||
};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
@ -12,20 +15,29 @@ impl NyashRunner {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
println!("📊 MIR Module compiled successfully!");
|
||||
@ -46,7 +58,9 @@ impl NyashRunner {
|
||||
// Harness path (optional): if NYASH_LLVM_USE_HARNESS=1, try Python/llvmlite first.
|
||||
let use_harness = crate::config::env::llvm_use_harness();
|
||||
if use_harness {
|
||||
if let Some(parent) = std::path::Path::new(&_out_path).parent() { let _ = std::fs::create_dir_all(parent); }
|
||||
if let Some(parent) = std::path::Path::new(&_out_path).parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
let py = which::which("python3").ok();
|
||||
if let Some(py3) = py {
|
||||
let harness = std::path::Path::new("tools/llvmlite_harness.py");
|
||||
@ -55,34 +69,61 @@ impl NyashRunner {
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let mir_json_path = tmp_dir.join("nyash_harness_mir.json");
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness(&module, &mir_json_path) {
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness(
|
||||
&module,
|
||||
&mir_json_path,
|
||||
) {
|
||||
eprintln!("❌ MIR JSON emit error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Runner/LLVM] using llvmlite harness → {} (mir={})", _out_path, mir_json_path.display());
|
||||
eprintln!(
|
||||
"[Runner/LLVM] using llvmlite harness → {} (mir={})",
|
||||
_out_path,
|
||||
mir_json_path.display()
|
||||
);
|
||||
}
|
||||
// 2) Run harness with --in/--out(失敗時は即エラー)
|
||||
let status = std::process::Command::new(py3)
|
||||
.args([harness.to_string_lossy().as_ref(), "--in", &mir_json_path.display().to_string(), "--out", &_out_path])
|
||||
.status().map_err(|e| format!("spawn harness: {}", e)).unwrap();
|
||||
.args([
|
||||
harness.to_string_lossy().as_ref(),
|
||||
"--in",
|
||||
&mir_json_path.display().to_string(),
|
||||
"--out",
|
||||
&_out_path,
|
||||
])
|
||||
.status()
|
||||
.map_err(|e| format!("spawn harness: {}", e))
|
||||
.unwrap();
|
||||
if !status.success() {
|
||||
eprintln!("❌ llvmlite harness failed (status={})", status.code().unwrap_or(-1));
|
||||
eprintln!(
|
||||
"❌ llvmlite harness failed (status={})",
|
||||
status.code().unwrap_or(-1)
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
// Verify
|
||||
match std::fs::metadata(&_out_path) {
|
||||
Ok(meta) if meta.len() > 0 => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[LLVM] object emitted by harness: {} ({} bytes)", _out_path, meta.len());
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
eprintln!("❌ harness output not found or empty: {}", _out_path);
|
||||
process::exit(1);
|
||||
match std::fs::metadata(&_out_path) {
|
||||
Ok(meta) if meta.len() > 0 => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[LLVM] object emitted by harness: {} ({} bytes)",
|
||||
_out_path,
|
||||
meta.len()
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"❌ harness output not found or empty: {}",
|
||||
_out_path
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("❌ harness script not found: {}", harness.display());
|
||||
process::exit(1);
|
||||
@ -99,11 +140,18 @@ impl NyashRunner {
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[LLVM] object emitted: {} ({} bytes)", _out_path, meta.len());
|
||||
eprintln!(
|
||||
"[LLVM] object emitted: {} ({} bytes)",
|
||||
_out_path,
|
||||
meta.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ harness output not found after emit: {} ({})", _out_path, e);
|
||||
eprintln!(
|
||||
"❌ harness output not found after emit: {} ({})",
|
||||
_out_path, e
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@ -117,7 +165,13 @@ impl NyashRunner {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Runner/LLVM] emitting object to {} (cwd={})", _out_path, std::env::current_dir().map(|p| p.display().to_string()).unwrap_or_default());
|
||||
eprintln!(
|
||||
"[Runner/LLVM] emitting object to {} (cwd={})",
|
||||
_out_path,
|
||||
std::env::current_dir()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
if let Err(e) = llvm_compile_to_object(&module, &_out_path) {
|
||||
eprintln!("❌ LLVM object emit error: {}", e);
|
||||
@ -126,10 +180,17 @@ impl NyashRunner {
|
||||
match std::fs::metadata(&_out_path) {
|
||||
Ok(meta) if meta.len() > 0 => {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[LLVM] object emitted: {} ({} bytes)", _out_path, meta.len());
|
||||
eprintln!(
|
||||
"[LLVM] object emitted: {} ({} bytes)",
|
||||
_out_path,
|
||||
meta.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => { eprintln!("❌ LLVM object not found or empty: {}", _out_path); process::exit(1); }
|
||||
_ => {
|
||||
eprintln!("❌ LLVM object not found or empty: {}", _out_path);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -156,8 +217,11 @@ impl NyashRunner {
|
||||
println!("✅ LLVM execution completed (non-integer result)!");
|
||||
println!("📊 Result: {}", result.to_string_box().value);
|
||||
}
|
||||
},
|
||||
Err(e) => { eprintln!("❌ LLVM execution error: {}", e); process::exit(1); }
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ LLVM execution error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(all(not(feature = "llvm-inkwell-legacy")))]
|
||||
@ -168,8 +232,14 @@ impl NyashRunner {
|
||||
for (_bid, block) in &main_func.blocks {
|
||||
for inst in &block.instructions {
|
||||
match inst {
|
||||
MirInstruction::Return { value: Some(_) } => { println!("✅ Mock exit code: 42"); process::exit(42); }
|
||||
MirInstruction::Return { value: None } => { println!("✅ Mock exit code: 0"); process::exit(0); }
|
||||
MirInstruction::Return { value: Some(_) } => {
|
||||
println!("✅ Mock exit code: 42");
|
||||
process::exit(42);
|
||||
}
|
||||
MirInstruction::Return { value: None } => {
|
||||
println!("✅ Mock exit code: 0");
|
||||
process::exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
use super::super::NyashRunner;
|
||||
use nyash_rust::{parser::NyashParser, mir::{MirCompiler, MirPrinter}};
|
||||
use nyash_rust::{
|
||||
mir::{MirCompiler, MirPrinter},
|
||||
parser::NyashParser,
|
||||
};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
@ -8,20 +11,29 @@ impl NyashRunner {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Verify MIR if requested
|
||||
@ -31,7 +43,9 @@ impl NyashRunner {
|
||||
Ok(()) => println!("✅ MIR verification passed!"),
|
||||
Err(errors) => {
|
||||
eprintln!("❌ MIR verification failed:");
|
||||
for error in errors { eprintln!(" • {}", error); }
|
||||
for error in errors {
|
||||
eprintln!(" • {}", error);
|
||||
}
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@ -39,11 +53,16 @@ impl NyashRunner {
|
||||
|
||||
// Dump MIR if requested
|
||||
if self.config.dump_mir {
|
||||
let mut printer = if self.config.mir_verbose { MirPrinter::verbose() } else { MirPrinter::new() };
|
||||
if self.config.mir_verbose_effects { printer.set_show_effects_inline(true); }
|
||||
let mut printer = if self.config.mir_verbose {
|
||||
MirPrinter::verbose()
|
||||
} else {
|
||||
MirPrinter::new()
|
||||
};
|
||||
if self.config.mir_verbose_effects {
|
||||
printer.set_show_effects_inline(true);
|
||||
}
|
||||
println!("🚀 MIR Output for {}:", filename);
|
||||
println!("{}", printer.print_module(&compile_result.module));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
pub mod bench;
|
||||
pub mod interpreter;
|
||||
pub mod llvm;
|
||||
pub mod mir;
|
||||
pub mod vm;
|
||||
pub mod llvm;
|
||||
pub mod bench;
|
||||
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
pub mod aot;
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
use super::super::NyashRunner;
|
||||
use nyash_rust::{parser::NyashParser, mir::MirCompiler, backend::VM, runtime::{NyashRuntime, NyashRuntimeBuilder}, ast::ASTNode, core::model::BoxDeclaration as CoreBoxDecl, interpreter::SharedState, box_factory::user_defined::UserDefinedBoxFactory};
|
||||
use std::{fs, process};
|
||||
use nyash_rust::{
|
||||
ast::ASTNode,
|
||||
backend::VM,
|
||||
box_factory::user_defined::UserDefinedBoxFactory,
|
||||
core::model::BoxDeclaration as CoreBoxDecl,
|
||||
interpreter::SharedState,
|
||||
mir::MirCompiler,
|
||||
parser::NyashParser,
|
||||
runtime::{NyashRuntime, NyashRuntimeBuilder},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute VM mode (split)
|
||||
@ -18,7 +27,9 @@ impl NyashRunner {
|
||||
// Init plugin host from nyash.toml if not yet loaded
|
||||
let need_init = {
|
||||
let host = nyash_rust::runtime::get_global_plugin_host();
|
||||
host.read().map(|h| h.config_ref().is_none()).unwrap_or(true)
|
||||
host.read()
|
||||
.map(|h| h.config_ref().is_none())
|
||||
.unwrap_or(true)
|
||||
};
|
||||
if need_init {
|
||||
let _ = nyash_rust::runtime::init_global_plugin_host("nyash.toml");
|
||||
@ -29,16 +40,29 @@ impl NyashRunner {
|
||||
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
|
||||
}
|
||||
// Build stable override list
|
||||
let mut override_types: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect()
|
||||
} else { vec![] };
|
||||
let mut override_types: Vec<String> =
|
||||
if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
|
||||
list.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
for t in [
|
||||
"FileBox", "TOMLBox", // IO/config
|
||||
"ConsoleBox", "StringBox", "IntegerBox", // core value-ish
|
||||
"ArrayBox", "MapBox", // collections
|
||||
"MathBox", "TimeBox" // math/time helpers
|
||||
"FileBox",
|
||||
"TOMLBox", // IO/config
|
||||
"ConsoleBox",
|
||||
"StringBox",
|
||||
"IntegerBox", // core value-ish
|
||||
"ArrayBox",
|
||||
"MapBox", // collections
|
||||
"MathBox",
|
||||
"TimeBox", // math/time helpers
|
||||
] {
|
||||
if !override_types.iter().any(|x| x == t) { override_types.push(t.to_string()); }
|
||||
if !override_types.iter().any(|x| x == t) {
|
||||
override_types.push(t.to_string());
|
||||
}
|
||||
}
|
||||
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
|
||||
|
||||
@ -46,11 +70,23 @@ impl NyashRunner {
|
||||
if std::env::var("NYASH_VM_PLUGIN_STRICT").ok().as_deref() == Some("1") {
|
||||
let v2 = nyash_rust::runtime::get_global_registry();
|
||||
let mut missing: Vec<String> = Vec::new();
|
||||
for t in ["FileBox","ConsoleBox","ArrayBox","MapBox","StringBox","IntegerBox"] {
|
||||
if v2.get_provider(t).is_none() { missing.push(t.to_string()); }
|
||||
for t in [
|
||||
"FileBox",
|
||||
"ConsoleBox",
|
||||
"ArrayBox",
|
||||
"MapBox",
|
||||
"StringBox",
|
||||
"IntegerBox",
|
||||
] {
|
||||
if v2.get_provider(t).is_none() {
|
||||
missing.push(t.to_string());
|
||||
}
|
||||
}
|
||||
if !missing.is_empty() {
|
||||
eprintln!("❌ VM plugin-first strict: missing providers for: {:?}", missing);
|
||||
eprintln!(
|
||||
"❌ VM plugin-first strict: missing providers for: {:?}",
|
||||
missing
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@ -59,13 +95,19 @@ impl NyashRunner {
|
||||
// Read the file
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse to AST
|
||||
let ast = match NyashParser::parse_from_string(&code) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ Parse error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Prepare runtime and collect Box declarations for VM user-defined types
|
||||
@ -80,7 +122,9 @@ impl NyashRunner {
|
||||
let mut shared = SharedState::new();
|
||||
shared.box_declarations = rt.box_declarations.clone();
|
||||
let udf = Arc::new(UserDefinedBoxFactory::new(shared));
|
||||
if let Ok(mut reg) = rt.box_registry.lock() { reg.register(udf); }
|
||||
if let Ok(mut reg) = rt.box_registry.lock() {
|
||||
reg.register(udf);
|
||||
}
|
||||
rt
|
||||
};
|
||||
|
||||
@ -88,20 +132,30 @@ impl NyashRunner {
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
Ok(result) => result,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Optional: demo scheduling hook
|
||||
if std::env::var("NYASH_SCHED_DEMO").ok().as_deref() == Some("1") {
|
||||
if let Some(s) = &runtime.scheduler {
|
||||
// Immediate task
|
||||
s.spawn("demo-immediate", Box::new(|| {
|
||||
println!("[SCHED] immediate task ran at safepoint");
|
||||
}));
|
||||
s.spawn(
|
||||
"demo-immediate",
|
||||
Box::new(|| {
|
||||
println!("[SCHED] immediate task ran at safepoint");
|
||||
}),
|
||||
);
|
||||
// Delayed task
|
||||
s.spawn_after(0, "demo-delayed", Box::new(|| {
|
||||
println!("[SCHED] delayed task ran at safepoint");
|
||||
}));
|
||||
s.spawn_after(
|
||||
0,
|
||||
"demo-delayed",
|
||||
Box::new(|| {
|
||||
println!("[SCHED] delayed task ran at safepoint");
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,17 +184,28 @@ impl NyashRunner {
|
||||
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(&module_vm, &mir_json_path) {
|
||||
if let Err(e) = crate::runner::mir_json_emit::emit_mir_json_for_harness(
|
||||
&module_vm,
|
||||
&mir_json_path,
|
||||
) {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Runner/VM] using PyVM → {} (mir={})", filename, mir_json_path.display());
|
||||
eprintln!(
|
||||
"[Runner/VM] using PyVM → {} (mir={})",
|
||||
filename,
|
||||
mir_json_path.display()
|
||||
);
|
||||
}
|
||||
// Determine entry function hint (prefer Main.main if present)
|
||||
let entry = if module_vm.functions.contains_key("Main.main") {
|
||||
"Main.main"
|
||||
} else if module_vm.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||
} else if module_vm.functions.contains_key("main") {
|
||||
"main"
|
||||
} else {
|
||||
"Main.main"
|
||||
};
|
||||
// Spawn runner
|
||||
let status = std::process::Command::new(py3)
|
||||
.args([
|
||||
@ -178,20 +243,24 @@ impl NyashRunner {
|
||||
let mut vm = VM::with_runtime(runtime);
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(result) => {
|
||||
if !quiet_pipe { 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") {
|
||||
use nyash_rust::mir::MirType;
|
||||
use nyash_rust::box_trait::{IntegerBox, BoolBox, StringBox};
|
||||
use nyash_rust::box_trait::{BoolBox, IntegerBox, StringBox};
|
||||
use nyash_rust::boxes::FloatBox;
|
||||
use nyash_rust::mir::MirType;
|
||||
match &func.signature.return_type {
|
||||
MirType::Float => {
|
||||
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
|
||||
("Float", format!("{}", fb.value))
|
||||
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Float", format!("{}", ib.value as f64))
|
||||
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
|
||||
} else if let Some(s) =
|
||||
nyash_rust::runtime::semantics::coerce_to_string(result.as_ref())
|
||||
{
|
||||
("String", s)
|
||||
} else {
|
||||
(result.type_name(), result.to_string_box().value)
|
||||
@ -200,7 +269,9 @@ impl NyashRunner {
|
||||
MirType::Integer => {
|
||||
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||||
("Integer", ib.value.to_string())
|
||||
} else if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
|
||||
} else if let Some(i) =
|
||||
nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
|
||||
{
|
||||
("Integer", i.to_string())
|
||||
} else {
|
||||
(result.type_name(), result.to_string_box().value)
|
||||
@ -218,50 +289,72 @@ impl NyashRunner {
|
||||
MirType::String => {
|
||||
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||
("String", sb.value.clone())
|
||||
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
|
||||
} else if let Some(s) =
|
||||
nyash_rust::runtime::semantics::coerce_to_string(result.as_ref())
|
||||
{
|
||||
("String", s)
|
||||
} else {
|
||||
(result.type_name(), result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
|
||||
if let Some(i) =
|
||||
nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
|
||||
{
|
||||
("Integer", i.to_string())
|
||||
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
|
||||
} else if let Some(s) =
|
||||
nyash_rust::runtime::semantics::coerce_to_string(result.as_ref())
|
||||
{
|
||||
("String", s)
|
||||
} else { (result.type_name(), result.to_string_box().value) }
|
||||
} else {
|
||||
(result.type_name(), result.to_string_box().value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref()) {
|
||||
if let Some(i) = nyash_rust::runtime::semantics::coerce_to_i64(result.as_ref())
|
||||
{
|
||||
("Integer", i.to_string())
|
||||
} else if let Some(s) = nyash_rust::runtime::semantics::coerce_to_string(result.as_ref()) {
|
||||
} else if let Some(s) =
|
||||
nyash_rust::runtime::semantics::coerce_to_string(result.as_ref())
|
||||
{
|
||||
("String", s)
|
||||
} else { (result.type_name(), result.to_string_box().value) }
|
||||
} else {
|
||||
(result.type_name(), result.to_string_box().value)
|
||||
}
|
||||
};
|
||||
if !quiet_pipe {
|
||||
println!("ResultType(MIR): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
}
|
||||
},
|
||||
Err(e) => { eprintln!("❌ VM execution error: {}", e); process::exit(1); }
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ VM execution error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect Box declarations from AST and register into runtime
|
||||
pub(crate) fn collect_box_declarations(&self, ast: &ASTNode, runtime: &NyashRuntime) {
|
||||
fn resolve_include_path(filename: &str) -> String {
|
||||
if filename.starts_with("./") || filename.starts_with("../") { return filename.to_string(); }
|
||||
if filename.starts_with("./") || filename.starts_with("../") {
|
||||
return filename.to_string();
|
||||
}
|
||||
let parts: Vec<&str> = filename.splitn(2, '/').collect();
|
||||
if parts.len() == 2 {
|
||||
let root = parts[0]; let rest = parts[1];
|
||||
let root = parts[0];
|
||||
let rest = parts[1];
|
||||
let cfg_path = "nyash.toml";
|
||||
if let Ok(toml_str) = std::fs::read_to_string(cfg_path) {
|
||||
if let Ok(toml_val) = toml::from_str::<toml::Value>(&toml_str) {
|
||||
if let Some(include) = toml_val.get("include") {
|
||||
if let Some(roots) = include.get("roots").and_then(|v| v.as_table()) {
|
||||
if let Some(base) = roots.get(root).and_then(|v| v.as_str()) {
|
||||
let mut b = base.to_string(); if !b.ends_with('/') && !b.ends_with('\\') { b.push('/'); }
|
||||
let mut b = base.to_string();
|
||||
if !b.ends_with('/') && !b.ends_with('\\') {
|
||||
b.push('/');
|
||||
}
|
||||
return format!("{}{}", b, rest);
|
||||
}
|
||||
}
|
||||
@ -274,10 +367,23 @@ impl NyashRunner {
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn walk_with_state(node: &ASTNode, runtime: &NyashRuntime, stack: &mut Vec<String>, visited: &mut HashSet<String>) {
|
||||
fn walk_with_state(
|
||||
node: &ASTNode,
|
||||
runtime: &NyashRuntime,
|
||||
stack: &mut Vec<String>,
|
||||
visited: &mut HashSet<String>,
|
||||
) {
|
||||
match node {
|
||||
ASTNode::Program { statements, .. } => { for st in statements { walk_with_state(st, runtime, stack, visited); } }
|
||||
ASTNode::FunctionDeclaration { body, .. } => { for st in body { walk_with_state(st, runtime, stack, visited); } }
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for st in statements {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => {
|
||||
for st in body {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::Include { filename, .. } => {
|
||||
let mut path = resolve_include_path(filename);
|
||||
if std::path::Path::new(&path).is_dir() {
|
||||
@ -305,42 +411,139 @@ impl NyashRunner {
|
||||
stack.pop();
|
||||
}
|
||||
ASTNode::Assignment { target, value, .. } => {
|
||||
walk_with_state(target, runtime, stack, visited); walk_with_state(value, runtime, stack, visited);
|
||||
walk_with_state(target, runtime, stack, visited);
|
||||
walk_with_state(value, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::Return { value, .. } => { if let Some(v) = value { walk_with_state(v, runtime, stack, visited); } }
|
||||
ASTNode::Print { expression, .. } => { walk_with_state(expression, runtime, stack, visited); }
|
||||
ASTNode::If { condition, then_body, else_body, .. } => {
|
||||
ASTNode::Return { value, .. } => {
|
||||
if let Some(v) = value {
|
||||
walk_with_state(v, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::Print { expression, .. } => {
|
||||
walk_with_state(expression, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
walk_with_state(condition, runtime, stack, visited);
|
||||
for st in then_body { walk_with_state(st, runtime, stack, visited); }
|
||||
if let Some(eb) = else_body { for st in eb { walk_with_state(st, runtime, stack, visited); } }
|
||||
for st in then_body {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
if let Some(eb) = else_body {
|
||||
for st in eb {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
walk_with_state(condition, runtime, stack, visited); for st in body { walk_with_state(st, runtime, stack, visited); }
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => {
|
||||
walk_with_state(condition, runtime, stack, visited);
|
||||
for st in body {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
for st in try_body { walk_with_state(st, runtime, stack, visited); }
|
||||
for cc in catch_clauses { for st in &cc.body { walk_with_state(st, runtime, stack, visited); } }
|
||||
if let Some(fb) = finally_body { for st in fb { walk_with_state(st, runtime, stack, visited); } }
|
||||
ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
..
|
||||
} => {
|
||||
for st in try_body {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
for cc in catch_clauses {
|
||||
for st in &cc.body {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
if let Some(fb) = finally_body {
|
||||
for st in fb {
|
||||
walk_with_state(st, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Throw { expression, .. } => {
|
||||
walk_with_state(expression, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::Throw { expression, .. } => { walk_with_state(expression, runtime, stack, visited); }
|
||||
ASTNode::Local { initial_values, .. } => {
|
||||
for iv in initial_values { if let Some(v) = iv { walk_with_state(v, runtime, stack, visited); } }
|
||||
for iv in initial_values {
|
||||
if let Some(v) = iv {
|
||||
walk_with_state(v, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Outbox { initial_values, .. } => {
|
||||
for iv in initial_values { if let Some(v) = iv { walk_with_state(v, runtime, stack, visited); } }
|
||||
for iv in initial_values {
|
||||
if let Some(v) = iv {
|
||||
walk_with_state(v, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::FunctionCall { arguments, .. } => { for a in arguments { walk_with_state(a, runtime, stack, visited); } }
|
||||
ASTNode::MethodCall { object, arguments, .. } => { walk_with_state(object, runtime, stack, visited); for a in arguments { walk_with_state(a, runtime, stack, visited); } }
|
||||
ASTNode::FieldAccess { object, .. } => { walk_with_state(object, runtime, stack, visited); }
|
||||
ASTNode::New { arguments, .. } => { for a in arguments { walk_with_state(a, runtime, stack, visited); } }
|
||||
ASTNode::BinaryOp { left, right, .. } => { walk_with_state(left, runtime, stack, visited); walk_with_state(right, runtime, stack, visited); }
|
||||
ASTNode::UnaryOp { operand, .. } => { walk_with_state(operand, runtime, stack, visited); }
|
||||
ASTNode::AwaitExpression { expression, .. } => { walk_with_state(expression, runtime, stack, visited); }
|
||||
ASTNode::Arrow { sender, receiver, .. } => { walk_with_state(sender, runtime, stack, visited); walk_with_state(receiver, runtime, stack, visited); }
|
||||
ASTNode::Nowait { expression, .. } => { walk_with_state(expression, runtime, stack, visited); }
|
||||
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, .. } => {
|
||||
for (_mname, mnode) in methods { walk_with_state(mnode, runtime, stack, visited); }
|
||||
for (_ckey, cnode) in constructors { walk_with_state(cnode, runtime, stack, visited); }
|
||||
ASTNode::FunctionCall { arguments, .. } => {
|
||||
for a in arguments {
|
||||
walk_with_state(a, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
walk_with_state(object, runtime, stack, visited);
|
||||
for a in arguments {
|
||||
walk_with_state(a, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::FieldAccess { object, .. } => {
|
||||
walk_with_state(object, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::New { arguments, .. } => {
|
||||
for a in arguments {
|
||||
walk_with_state(a, runtime, stack, visited);
|
||||
}
|
||||
}
|
||||
ASTNode::BinaryOp { left, right, .. } => {
|
||||
walk_with_state(left, runtime, stack, visited);
|
||||
walk_with_state(right, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::UnaryOp { operand, .. } => {
|
||||
walk_with_state(operand, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::AwaitExpression { expression, .. } => {
|
||||
walk_with_state(expression, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::Arrow {
|
||||
sender, receiver, ..
|
||||
} => {
|
||||
walk_with_state(sender, runtime, stack, visited);
|
||||
walk_with_state(receiver, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::Nowait { expression, .. } => {
|
||||
walk_with_state(expression, runtime, stack, visited);
|
||||
}
|
||||
ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
weak_fields,
|
||||
is_interface,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters,
|
||||
..
|
||||
} => {
|
||||
for (_mname, mnode) in methods {
|
||||
walk_with_state(mnode, runtime, stack, visited);
|
||||
}
|
||||
for (_ckey, cnode) in constructors {
|
||||
walk_with_state(cnode, runtime, stack, visited);
|
||||
}
|
||||
let decl = CoreBoxDecl {
|
||||
name: name.clone(),
|
||||
fields: fields.clone(),
|
||||
@ -355,7 +558,9 @@ impl NyashRunner {
|
||||
implements: implements.clone(),
|
||||
type_parameters: type_parameters.clone(),
|
||||
};
|
||||
if let Ok(mut map) = runtime.box_declarations.write() { map.insert(name.clone(), decl); }
|
||||
if let Ok(mut map) = runtime.box_declarations.write() {
|
||||
map.insert(name.clone(), decl);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -21,13 +21,17 @@ impl NyashRunner {
|
||||
let json = if let Some(path) = &self.config.json_file {
|
||||
match std::fs::read_to_string(path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => { eprintln!("❌ json-file read error: {}", e); std::process::exit(1); }
|
||||
Err(e) => {
|
||||
eprintln!("❌ json-file read error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
use std::io::Read;
|
||||
let mut buf = String::new();
|
||||
if let Err(e) = std::io::stdin().read_to_string(&mut buf) {
|
||||
eprintln!("❌ stdin read error: {}", e); std::process::exit(1);
|
||||
eprintln!("❌ stdin read error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
buf
|
||||
};
|
||||
@ -45,16 +49,27 @@ impl NyashRunner {
|
||||
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) = super::mir_json_emit::emit_mir_json_for_harness_bin(&module, &mir_json_path) {
|
||||
if let Err(e) = super::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());
|
||||
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 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(),
|
||||
|
||||
@ -22,7 +22,8 @@ impl NyashRunner {
|
||||
pub(super) fn init_using_context(&self) -> UsingContext {
|
||||
let mut using_paths: Vec<String> = Vec::new();
|
||||
let mut pending_modules: Vec<(String, String)> = Vec::new();
|
||||
let mut aliases: std::collections::HashMap<String, String> = std::collections::HashMap::new();
|
||||
let mut aliases: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
// Defaults
|
||||
using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string()));
|
||||
@ -43,7 +44,9 @@ impl NyashRunner {
|
||||
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()); }
|
||||
if !s.is_empty() {
|
||||
using_paths.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,20 +78,29 @@ impl NyashRunner {
|
||||
if let Ok(p) = std::env::var("NYASH_USING_PATH") {
|
||||
for s in p.split(':') {
|
||||
let s = s.trim();
|
||||
if !s.is_empty() { using_paths.push(s.to_string()); }
|
||||
if !s.is_empty() {
|
||||
using_paths.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Env aliases: comma-separated k=v pairs
|
||||
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||
for ent in raw.split(',') {
|
||||
if let Some((k,v)) = ent.split_once('=') {
|
||||
let k = k.trim(); let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() { aliases.insert(k.to_string(), v.to_string()); }
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
let k = k.trim();
|
||||
let v = v.trim();
|
||||
if !k.is_empty() && !v.is_empty() {
|
||||
aliases.insert(k.to_string(), v.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UsingContext { using_paths, pending_modules, aliases }
|
||||
UsingContext {
|
||||
using_paths,
|
||||
pending_modules,
|
||||
aliases,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,19 +108,25 @@ impl NyashRunner {
|
||||
pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
|
||||
use std::fs;
|
||||
fn walk(dir: &std::path::Path, leaf: &str, out: &mut Vec<String>, depth: usize) {
|
||||
if depth == 0 || out.len() >= 5 { return; }
|
||||
if depth == 0 || out.len() >= 5 {
|
||||
return;
|
||||
}
|
||||
if let Ok(entries) = fs::read_dir(dir) {
|
||||
for e in entries.flatten() {
|
||||
let path = e.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, leaf, out, depth - 1);
|
||||
if out.len() >= 5 { return; }
|
||||
if out.len() >= 5 {
|
||||
return;
|
||||
}
|
||||
} else if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
|
||||
if ext == "nyash" {
|
||||
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
if stem == leaf {
|
||||
out.push(path.to_string_lossy().to_string());
|
||||
if out.len() >= 5 { return; }
|
||||
if out.len() >= 5 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,17 +150,29 @@ pub(super) fn resolve_using_target(
|
||||
strict: bool,
|
||||
verbose: bool,
|
||||
) -> Result<String, String> {
|
||||
if is_path { return Ok(tgt.to_string()); }
|
||||
if is_path {
|
||||
return Ok(tgt.to_string());
|
||||
}
|
||||
let trace = verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1");
|
||||
let idx = super::box_index::get_box_index();
|
||||
let mut strict_effective = strict || idx.plugins_require_prefix_global;
|
||||
if std::env::var("NYASH_PLUGIN_REQUIRE_PREFIX").ok().as_deref() == Some("1") { strict_effective = true; }
|
||||
if std::env::var("NYASH_PLUGIN_REQUIRE_PREFIX").ok().as_deref() == Some("1") {
|
||||
strict_effective = true;
|
||||
}
|
||||
let meta_for_target = idx.plugin_meta_by_box.get(tgt).cloned();
|
||||
let mut require_prefix_target = meta_for_target.as_ref().map(|m| m.require_prefix).unwrap_or(false);
|
||||
if let Some(m) = &meta_for_target { if !m.expose_short_names { require_prefix_target = true; } }
|
||||
let mut require_prefix_target = meta_for_target
|
||||
.as_ref()
|
||||
.map(|m| m.require_prefix)
|
||||
.unwrap_or(false);
|
||||
if let Some(m) = &meta_for_target {
|
||||
if !m.expose_short_names {
|
||||
require_prefix_target = true;
|
||||
}
|
||||
}
|
||||
let mut is_plugin_short = meta_for_target.is_some();
|
||||
if !is_plugin_short {
|
||||
is_plugin_short = idx.plugin_boxes.contains(tgt) || super::box_index::BoxIndex::is_known_plugin_short(tgt);
|
||||
is_plugin_short = idx.plugin_boxes.contains(tgt)
|
||||
|| super::box_index::BoxIndex::is_known_plugin_short(tgt);
|
||||
}
|
||||
if (strict_effective || require_prefix_target) && is_plugin_short && !tgt.contains('.') {
|
||||
let mut msg = format!("plugin short name '{}' requires prefix", tgt);
|
||||
@ -155,25 +185,40 @@ pub(super) fn resolve_using_target(
|
||||
}
|
||||
let key = {
|
||||
let base = context_dir.and_then(|p| p.to_str()).unwrap_or("");
|
||||
format!("{}|{}|{}|{}", tgt, base, strict as i32, using_paths.join(":"))
|
||||
format!(
|
||||
"{}|{}|{}|{}",
|
||||
tgt,
|
||||
base,
|
||||
strict as i32,
|
||||
using_paths.join(":")
|
||||
)
|
||||
};
|
||||
if let Some(hit) = crate::runner::box_index::cache_get(&key) {
|
||||
if trace { crate::runner::trace::log(format!("[using/cache] '{}' -> '{}'", tgt, hit)); }
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/cache] '{}' -> '{}'", tgt, hit));
|
||||
}
|
||||
return Ok(hit);
|
||||
}
|
||||
// Resolve aliases early (provided map)
|
||||
if let Some(v) = aliases.get(tgt) {
|
||||
if trace { crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v)); }
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, v.clone());
|
||||
return Ok(v.clone());
|
||||
}
|
||||
// Also consult env aliases
|
||||
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||
for ent in raw.split(',') {
|
||||
if let Some((k,v)) = ent.split_once('=') {
|
||||
if let Some((k, v)) = ent.split_once('=') {
|
||||
if k.trim() == tgt {
|
||||
let out = v.trim().to_string();
|
||||
if trace { crate::runner::trace::log(format!("[using/resolve] env-alias '{}' -> '{}'", tgt, out)); }
|
||||
if trace {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using/resolve] env-alias '{}' -> '{}'",
|
||||
tgt, out
|
||||
));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, out.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
@ -183,17 +228,26 @@ pub(super) fn resolve_using_target(
|
||||
// 1) modules mapping
|
||||
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) {
|
||||
let out = p.clone();
|
||||
if trace { crate::runner::trace::log(format!("[using/resolve] modules '{}' -> '{}'", tgt, out)); }
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] modules '{}' -> '{}'", tgt, out));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, out.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
// 2) build candidate list: relative then using-paths
|
||||
let rel = tgt.replace('.', "/") + ".nyash";
|
||||
let mut cand: Vec<String> = Vec::new();
|
||||
if let Some(dir) = context_dir { let c = dir.join(&rel); if c.exists() { cand.push(c.to_string_lossy().to_string()); } }
|
||||
if let Some(dir) = context_dir {
|
||||
let c = dir.join(&rel);
|
||||
if c.exists() {
|
||||
cand.push(c.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
for base in using_paths {
|
||||
let c = std::path::Path::new(base).join(&rel);
|
||||
if c.exists() { cand.push(c.to_string_lossy().to_string()); }
|
||||
if c.exists() {
|
||||
cand.push(c.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
if cand.is_empty() {
|
||||
if trace {
|
||||
@ -201,10 +255,17 @@ pub(super) fn resolve_using_target(
|
||||
let leaf = tgt.split('.').last().unwrap_or(tgt);
|
||||
let mut cands: Vec<String> = Vec::new();
|
||||
suggest_in_base("apps", leaf, &mut cands);
|
||||
if cands.len() < 5 { suggest_in_base("lib", leaf, &mut cands); }
|
||||
if cands.len() < 5 { suggest_in_base(".", leaf, &mut cands); }
|
||||
if cands.len() < 5 {
|
||||
suggest_in_base("lib", leaf, &mut cands);
|
||||
}
|
||||
if cands.len() < 5 {
|
||||
suggest_in_base(".", leaf, &mut cands);
|
||||
}
|
||||
if cands.is_empty() {
|
||||
crate::runner::trace::log(format!("[using] unresolved '{}' (searched: rel+paths)", tgt));
|
||||
crate::runner::trace::log(format!(
|
||||
"[using] unresolved '{}' (searched: rel+paths)",
|
||||
tgt
|
||||
));
|
||||
} else {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using] unresolved '{}' (searched: rel+paths) candidates: {}",
|
||||
@ -219,7 +280,9 @@ pub(super) fn resolve_using_target(
|
||||
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
||||
}
|
||||
let out = cand.remove(0);
|
||||
if trace { crate::runner::trace::log(format!("[using/resolve] '{}' -> '{}'", tgt, out)); }
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] '{}' -> '{}'", tgt, out));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, out.clone());
|
||||
Ok(out)
|
||||
}
|
||||
@ -246,9 +309,17 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result
|
||||
if !in_box && trimmed.starts_with("box ") || trimmed.starts_with("static box ") {
|
||||
// capture name
|
||||
let mut name = String::new();
|
||||
let after = if let Some(rest) = trimmed.strip_prefix("static box ") { rest } else { trimmed.strip_prefix("box ").unwrap_or("") };
|
||||
let after = if let Some(rest) = trimmed.strip_prefix("static box ") {
|
||||
rest
|
||||
} else {
|
||||
trimmed.strip_prefix("box ").unwrap_or("")
|
||||
};
|
||||
for ch in after.chars() {
|
||||
if ch.is_alphanumeric() || ch == '_' { name.push(ch); } else { break; }
|
||||
if ch.is_alphanumeric() || ch == '_' {
|
||||
name.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// require K&R brace on same line to start tracking
|
||||
if opens > 0 {
|
||||
@ -269,11 +340,29 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result
|
||||
// starts with identifier then '(' and later '{'
|
||||
let mut it = trimmed.chars();
|
||||
let mut ident = String::new();
|
||||
while let Some(c) = it.next() { if c.is_whitespace() { continue; } if c.is_alphabetic() || c=='_' { ident.push(c); break; } else { break; } }
|
||||
while let Some(c) = it.next() { if c.is_alphanumeric() || c=='_' { ident.push(c); } else { break; } }
|
||||
while let Some(c) = it.next() {
|
||||
if c.is_whitespace() {
|
||||
continue;
|
||||
}
|
||||
if c.is_alphabetic() || c == '_' {
|
||||
ident.push(c);
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some(c) = it.next() {
|
||||
if c.is_alphanumeric() || c == '_' {
|
||||
ident.push(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
trimmed.contains('(') && trimmed.ends_with('{') && !ident.is_empty()
|
||||
};
|
||||
if is_method { seen_method = true; }
|
||||
if is_method {
|
||||
seen_method = true;
|
||||
}
|
||||
|
||||
// Detect field: ident ':' Type (rough heuristic)
|
||||
let is_field = {
|
||||
@ -281,10 +370,22 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result
|
||||
if parts.len() == 2 {
|
||||
let lhs = parts[0].trim();
|
||||
let rhs = parts[1].trim();
|
||||
let lhs_ok = !lhs.is_empty() && lhs.chars().next().map(|c| c.is_alphabetic() || c=='_').unwrap_or(false);
|
||||
let rhs_ok = !rhs.is_empty() && rhs.chars().next().map(|c| c.is_alphabetic() || c=='_').unwrap_or(false);
|
||||
let lhs_ok = !lhs.is_empty()
|
||||
&& lhs
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_alphabetic() || c == '_')
|
||||
.unwrap_or(false);
|
||||
let rhs_ok = !rhs.is_empty()
|
||||
&& rhs
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_alphabetic() || c == '_')
|
||||
.unwrap_or(false);
|
||||
lhs_ok && rhs_ok && !trimmed.contains('(') && !trimmed.contains(')')
|
||||
} else { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if is_field && seen_method {
|
||||
violations.push((lno, trimmed.to_string(), cur_box.clone()));
|
||||
@ -293,7 +394,10 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result
|
||||
}
|
||||
// Exit box when closing brace reduces depth below box_depth
|
||||
let post_brace = pre_brace + opens - closes;
|
||||
if post_brace < box_depth { in_box = false; cur_box.clear(); }
|
||||
if post_brace < box_depth {
|
||||
in_box = false;
|
||||
cur_box.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Update brace after processing
|
||||
@ -305,17 +409,30 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result
|
||||
}
|
||||
if strict {
|
||||
// Compose error message
|
||||
let mut msg = String::from("Field declarations must appear at the top of box. Violations:\n");
|
||||
let mut msg =
|
||||
String::from("Field declarations must appear at the top of box. Violations:\n");
|
||||
for (lno, fld, bx) in violations.iter().take(10) {
|
||||
msg.push_str(&format!(" line {} in box {}: '{}" , lno, if bx.is_empty(){"<unknown>"} else {bx}, fld));
|
||||
msg.push_str(&format!(
|
||||
" line {} in box {}: '{}",
|
||||
lno,
|
||||
if bx.is_empty() { "<unknown>" } else { bx },
|
||||
fld
|
||||
));
|
||||
msg.push_str("'\n");
|
||||
}
|
||||
if violations.len() > 10 { msg.push_str(&format!(" ... and {} more\n", violations.len()-10)); }
|
||||
if violations.len() > 10 {
|
||||
msg.push_str(&format!(" ... and {} more\n", violations.len() - 10));
|
||||
}
|
||||
return Err(msg);
|
||||
}
|
||||
if verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1") {
|
||||
for (lno, fld, bx) in violations {
|
||||
eprintln!("[lint] fields-top: line {} in box {} -> {}", lno, if bx.is_empty(){"<unknown>"} else {&bx}, fld);
|
||||
eprintln!(
|
||||
"[lint] fields-top: line {} in box {} -> {}",
|
||||
lno,
|
||||
if bx.is_empty() { "<unknown>" } else { &bx },
|
||||
fld
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@ -8,12 +8,12 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
|
||||
use std::{fs, process};
|
||||
use nyash_rust::{interpreter::NyashInterpreter, parser::NyashParser};
|
||||
use std::io::Read;
|
||||
use std::process::Stdio;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
/// Selfhost (Ny -> JSON v0) pipeline: EXE/VM/Python フォールバック含む
|
||||
@ -22,7 +22,10 @@ impl NyashRunner {
|
||||
// Read input source
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(c) => c,
|
||||
Err(e) => { eprintln!("[ny-compiler] read error: {}", e); return false; }
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] read error: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// Optional Phase-15: strip `using` lines and register modules (same policy as execute_nyash_file)
|
||||
let enable_using = crate::config::env::enable_using();
|
||||
@ -39,13 +42,25 @@ impl NyashRunner {
|
||||
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");
|
||||
(
|
||||
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()
|
||||
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 {
|
||||
@ -88,13 +103,17 @@ impl NyashRunner {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] open tmp failed: {}", e); return false; }
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] open tmp failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Preferred: run Ny selfhost compiler program (apps/selfhost-compiler/compiler.nyash)
|
||||
// This avoids inline embedding pitfalls and supports Stage-3 gating via args.
|
||||
{
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let exe = std::env::current_exe()
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let parser_prog = std::path::Path::new("apps/selfhost-compiler/compiler.nyash");
|
||||
if parser_prog.exists() {
|
||||
let mut cmd = std::process::Command::new(&exe);
|
||||
@ -113,7 +132,9 @@ impl NyashRunner {
|
||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||
cmd.env("NYASH_JSON_ONLY", "1");
|
||||
let timeout_ms: u64 = crate::config::env::ny_compiler_timeout_ms();
|
||||
let mut cmd = cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped());
|
||||
let mut cmd = cmd
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped());
|
||||
if let Ok(mut child) = cmd.spawn() {
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
@ -124,7 +145,10 @@ impl NyashRunner {
|
||||
Ok(Some(_)) => break,
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= std::time::Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill(); let _ = child.wait(); timed_out = true; break;
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
@ -133,21 +157,41 @@ impl NyashRunner {
|
||||
}
|
||||
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 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 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 = String::from_utf8_lossy(&out_buf).to_string();
|
||||
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; } }
|
||||
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() {
|
||||
match super::json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||
let emit_only = crate::config::env::ny_compiler_emit_only();
|
||||
if emit_only { return false; }
|
||||
if emit_only {
|
||||
return false;
|
||||
}
|
||||
// Prefer PyVM path when requested
|
||||
if crate::config::env::vm_use_py() {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
@ -160,10 +204,25 @@ impl NyashRunner {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||
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(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry])
|
||||
.status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap();
|
||||
.args([
|
||||
"tools/pyvm_runner.py",
|
||||
"--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);
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
@ -173,7 +232,9 @@ impl NyashRunner {
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] json parse error (child): {}", e); }
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] json parse error (child): {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,56 +243,102 @@ impl NyashRunner {
|
||||
|
||||
// Python MVP-first: prefer the lightweight harness to produce JSON v0 (unless skipped)
|
||||
if std::env::var("NYASH_NY_COMPILER_SKIP_PY").ok().as_deref() != Some("1") {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let py = std::path::Path::new("tools/ny_parser_mvp.py");
|
||||
if py.exists() {
|
||||
let mut cmd = std::process::Command::new(&py3);
|
||||
cmd.arg(py).arg(&tmp_path);
|
||||
let out = match cmd.output() { Ok(o) => o, Err(e) => { eprintln!("[ny-compiler] python harness failed to spawn: {}", e); return false; } };
|
||||
if out.status.success() {
|
||||
if let Ok(line) = String::from_utf8(out.stdout).map(|s| s.lines().next().unwrap_or("").to_string()) {
|
||||
if line.contains("\"version\"") && line.contains("\"kind\"") {
|
||||
match super::json_v0_bridge::parse_json_v0_to_module(&line) {
|
||||
Ok(module) => {
|
||||
super::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; }
|
||||
// Prefer PyVM for selfhost pipeline (parity reference)
|
||||
if std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") {
|
||||
// Reuse the common PyVM runner path
|
||||
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) {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let py = std::path::Path::new("tools/ny_parser_mvp.py");
|
||||
if py.exists() {
|
||||
let mut cmd = std::process::Command::new(&py3);
|
||||
cmd.arg(py).arg(&tmp_path);
|
||||
let out = match cmd.output() {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] python harness failed to spawn: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if out.status.success() {
|
||||
if let Ok(line) = String::from_utf8(out.stdout)
|
||||
.map(|s| s.lines().next().unwrap_or("").to_string())
|
||||
{
|
||||
if line.contains("\"version\"") && line.contains("\"kind\"") {
|
||||
match super::json_v0_bridge::parse_json_v0_to_module(&line) {
|
||||
Ok(module) => {
|
||||
super::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;
|
||||
}
|
||||
// Prefer PyVM for selfhost pipeline (parity reference)
|
||||
if std::env::var("NYASH_VM_USE_PY").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
// Reuse the common PyVM runner path
|
||||
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);
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Bridge] using PyVM (selfhost-py) → {}", mir_json_path.display());
|
||||
}
|
||||
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(["tools/pyvm_runner.py", "--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 (selfhost-py) failed (status={})", code);
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[Bridge] using PyVM (selfhost-py) → {}",
|
||||
mir_json_path.display()
|
||||
);
|
||||
}
|
||||
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([
|
||||
"tools/pyvm_runner.py",
|
||||
"--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 (selfhost-py) failed (status={})",
|
||||
code
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
}
|
||||
println!("Result: {}", code);
|
||||
std::process::exit(code);
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] json parse error: {}", e);
|
||||
return false;
|
||||
}
|
||||
self.execute_mir_module(&module);
|
||||
return true;
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] json parse error: {}", 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
|
||||
@ -240,56 +347,118 @@ impl NyashRunner {
|
||||
} else {
|
||||
let mut p = std::path::PathBuf::from("dist/nyash_compiler");
|
||||
#[cfg(windows)]
|
||||
{ p.push("nyash_compiler.exe"); }
|
||||
{
|
||||
p.push("nyash_compiler.exe");
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{ p.push("nyash_compiler"); }
|
||||
{
|
||||
p.push("nyash_compiler");
|
||||
}
|
||||
if !p.exists() {
|
||||
// Try PATH
|
||||
if let Ok(w) = which::which("nyash_compiler") { w } else { p }
|
||||
} else { p }
|
||||
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 std::env::var("NYASH_NY_COMPILER_STAGE3").ok().as_deref() == Some("1") { cmd.arg("--stage3"); }
|
||||
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);
|
||||
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 std::env::var("NYASH_NY_COMPILER_STAGE3").ok().as_deref() == Some("1") {
|
||||
cmd.arg("--stage3");
|
||||
}
|
||||
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 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(Some(_status)) => {
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) { let _ = child.kill(); let _ = child.wait(); timed_out = true; break; }
|
||||
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; }
|
||||
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 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"));
|
||||
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 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; } }
|
||||
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();
|
||||
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;
|
||||
@ -299,12 +468,15 @@ impl NyashRunner {
|
||||
Ok(module) => {
|
||||
println!("🚀 Ny compiler EXE path (ny→json_v0) ON");
|
||||
super::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";
|
||||
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY")
|
||||
.unwrap_or_else(|_| "1".to_string())
|
||||
== "1";
|
||||
if emit_only {
|
||||
return false;
|
||||
} else {
|
||||
// Prefer PyVM when requested (reference semantics), regardless of BoxCall presence
|
||||
let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1");
|
||||
let prefer_pyvm =
|
||||
std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1");
|
||||
if prefer_pyvm {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
@ -316,17 +488,41 @@ impl NyashRunner {
|
||||
eprintln!("❌ PyVM MIR JSON emit error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[Bridge] using PyVM (selfhost) → {}", mir_json_path.display());
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[Bridge] using PyVM (selfhost) → {}",
|
||||
mir_json_path.display()
|
||||
);
|
||||
}
|
||||
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||
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(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry])
|
||||
.status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap();
|
||||
.args([
|
||||
"tools/pyvm_runner.py",
|
||||
"--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 (selfhost) failed (status={})", code);
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"❌ PyVM (selfhost) failed (status={})",
|
||||
code
|
||||
);
|
||||
}
|
||||
}
|
||||
// Harmonize with interpreter path for smokes: print Result then exit code
|
||||
@ -339,7 +535,10 @@ impl NyashRunner {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] json parse error: {}", e); return false; }
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] json parse error: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,15 +567,25 @@ impl NyashRunner {
|
||||
eprintln!("[ny-compiler] write inline failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
let exe = std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let exe = std::env::current_exe()
|
||||
.unwrap_or_else(|_| std::path::PathBuf::from("target/release/nyash"));
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
cmd.arg("--backend").arg("vm").arg(&inline_path);
|
||||
cmd.env_remove("NYASH_USE_NY_COMPILER");
|
||||
cmd.env_remove("NYASH_CLI_VERBOSE");
|
||||
cmd.env("NYASH_JSON_ONLY", "1");
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
||||
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] spawn inline vm failed: {}", e); return false; } };
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] spawn inline vm failed: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let mut ch_stdout = child.stdout.take();
|
||||
let mut ch_stderr = child.stderr.take();
|
||||
let start = Instant::now();
|
||||
@ -386,39 +595,68 @@ impl NyashRunner {
|
||||
Ok(Some(_)) => break,
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill(); let _ = child.wait(); timed_out = true; break;
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(10));
|
||||
}
|
||||
Err(e) => { eprintln!("[ny-compiler] inline wait error: {}", e); break; }
|
||||
Err(e) => {
|
||||
eprintln!("[ny-compiler] inline wait error: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut out_buf = Vec::new();
|
||||
if let Some(mut s) = ch_stdout { let _ = s.read_to_end(&mut out_buf); }
|
||||
if let Some(mut s) = ch_stdout {
|
||||
let _ = s.read_to_end(&mut out_buf);
|
||||
}
|
||||
if timed_out {
|
||||
let head = String::from_utf8_lossy(&out_buf).chars().take(200).collect::<String>();
|
||||
eprintln!("[ny-compiler] inline timeout after {} ms; stdout(head)='{}'", timeout_ms, head.replace('\n', "\\n"));
|
||||
let head = String::from_utf8_lossy(&out_buf)
|
||||
.chars()
|
||||
.take(200)
|
||||
.collect::<String>();
|
||||
eprintln!(
|
||||
"[ny-compiler] inline timeout after {} ms; stdout(head)='{}'",
|
||||
timeout_ms,
|
||||
head.replace('\n', "\\n")
|
||||
);
|
||||
}
|
||||
raw = String::from_utf8_lossy(&out_buf).to_string();
|
||||
}
|
||||
let mut json_line = String::new();
|
||||
for line in raw.lines() {
|
||||
let t = line.trim();
|
||||
if t.starts_with('{') && t.contains("\"version\"") && t.contains("\"kind\"") { 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() {
|
||||
return false;
|
||||
}
|
||||
if json_line.is_empty() { return false; }
|
||||
match super::json_v0_bridge::parse_json_v0_to_module(&json_line) {
|
||||
Ok(module) => {
|
||||
super::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; }
|
||||
let emit_only = std::env::var("NYASH_NY_COMPILER_EMIT_ONLY")
|
||||
.unwrap_or_else(|_| "1".to_string())
|
||||
== "1";
|
||||
if emit_only {
|
||||
return false;
|
||||
}
|
||||
// Phase-15 policy: when NYASH_VM_USE_PY=1, prefer PyVM as reference executor
|
||||
// regardless of BoxCall presence to ensure semantics parity (e.g., PHI merges).
|
||||
let prefer_pyvm = std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1");
|
||||
// Backward compatibility: if not preferring PyVM explicitly, still auto-enable when BoxCalls exist.
|
||||
let needs_pyvm = !prefer_pyvm && module.functions.values().any(|f| {
|
||||
f.blocks.values().any(|bb| bb.instructions.iter().any(|inst| matches!(inst, crate::mir::MirInstruction::BoxCall { .. })))
|
||||
});
|
||||
let needs_pyvm = !prefer_pyvm
|
||||
&& module.functions.values().any(|f| {
|
||||
f.blocks.values().any(|bb| {
|
||||
bb.instructions.iter().any(|inst| {
|
||||
matches!(inst, crate::mir::MirInstruction::BoxCall { .. })
|
||||
})
|
||||
})
|
||||
});
|
||||
if prefer_pyvm || needs_pyvm {
|
||||
if let Ok(py3) = which::which("python3") {
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
@ -426,22 +664,52 @@ impl NyashRunner {
|
||||
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) {
|
||||
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);
|
||||
process::exit(1);
|
||||
}
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
let mode = if prefer_pyvm { "selfhost" } else { "selfhost-fallback" };
|
||||
eprintln!("[Bridge] using PyVM ({}) → {}", mode, mir_json_path.display());
|
||||
let mode = if prefer_pyvm {
|
||||
"selfhost"
|
||||
} else {
|
||||
"selfhost-fallback"
|
||||
};
|
||||
eprintln!(
|
||||
"[Bridge] using PyVM ({}) → {}",
|
||||
mode,
|
||||
mir_json_path.display()
|
||||
);
|
||||
}
|
||||
let entry = if module.functions.contains_key("Main.main") { "Main.main" } else if module.functions.contains_key("main") { "main" } else { "Main.main" };
|
||||
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(["tools/pyvm_runner.py", "--in", &mir_json_path.display().to_string(), "--entry", entry])
|
||||
.status().map_err(|e| format!("spawn pyvm: {}", e)).unwrap();
|
||||
.args([
|
||||
"tools/pyvm_runner.py",
|
||||
"--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 (selfhost-fallback) failed (status={})", code);
|
||||
eprintln!(
|
||||
"❌ PyVM (selfhost-fallback) failed (status={})",
|
||||
code
|
||||
);
|
||||
}
|
||||
}
|
||||
// Harmonize with interpreter path for smokes
|
||||
@ -453,7 +721,10 @@ impl NyashRunner {
|
||||
self.execute_mir_module(&module);
|
||||
true
|
||||
}
|
||||
Err(e) => { eprintln!("❌ JSON v0 bridge error: {}", e); false }
|
||||
Err(e) => {
|
||||
eprintln!("❌ JSON v0 bridge error: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,26 +3,51 @@ use std::path::PathBuf;
|
||||
/// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell
|
||||
pub(super) fn run_named_task(name: &str) -> Result<(), String> {
|
||||
let cfg_path = "nyash.toml";
|
||||
let text = std::fs::read_to_string(cfg_path).map_err(|e| format!("read {}: {}", cfg_path, e))?;
|
||||
let doc = toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_path, e))?;
|
||||
let text =
|
||||
std::fs::read_to_string(cfg_path).map_err(|e| format!("read {}: {}", cfg_path, e))?;
|
||||
let doc =
|
||||
toml::from_str::<toml::Value>(&text).map_err(|e| format!("parse {}: {}", cfg_path, e))?;
|
||||
// Apply [env]
|
||||
if let Some(env_tbl) = doc.get("env").and_then(|v| v.as_table()) {
|
||||
for (k, v) in env_tbl.iter() {
|
||||
if let Some(s) = v.as_str() { std::env::set_var(k, s); }
|
||||
if let Some(s) = v.as_str() {
|
||||
std::env::set_var(k, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Lookup [tasks]
|
||||
let tasks = doc.get("tasks").and_then(|v| v.as_table()).ok_or("[tasks] not found in nyash.toml")?;
|
||||
let cmd = tasks.get(name).and_then(|v| v.as_str()).ok_or_else(|| format!("task '{}' not found", name))?;
|
||||
let tasks = doc
|
||||
.get("tasks")
|
||||
.and_then(|v| v.as_table())
|
||||
.ok_or("[tasks] not found in nyash.toml")?;
|
||||
let cmd = tasks
|
||||
.get(name)
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| format!("task '{}' not found", name))?;
|
||||
// Basic variable substitution
|
||||
let root = std::env::current_dir().unwrap_or(PathBuf::from(".")).display().to_string();
|
||||
let root = std::env::current_dir()
|
||||
.unwrap_or(PathBuf::from("."))
|
||||
.display()
|
||||
.to_string();
|
||||
let cmd = cmd.replace("{root}", &root);
|
||||
// Run via shell
|
||||
#[cfg(windows)]
|
||||
let status = std::process::Command::new("cmd").args(["/C", &cmd]).status().map_err(|e| e.to_string())?;
|
||||
let status = std::process::Command::new("cmd")
|
||||
.args(["/C", &cmd])
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
#[cfg(not(windows))]
|
||||
let status = std::process::Command::new("sh").arg("-lc").arg(&cmd).status().map_err(|e| e.to_string())?;
|
||||
if !status.success() { return Err(format!("task '{}' failed with status {:?}", name, status.code())); }
|
||||
let status = std::process::Command::new("sh")
|
||||
.arg("-lc")
|
||||
.arg(&cmd)
|
||||
.status()
|
||||
.map_err(|e| e.to_string())?;
|
||||
if !status.success() {
|
||||
return Err(format!(
|
||||
"task '{}' failed with status {:?}",
|
||||
name,
|
||||
status.code()
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
//! Runner tracing helpers (verbose-guarded)
|
||||
|
||||
/// Return whether CLI verbose logging is enabled
|
||||
pub fn cli_verbose() -> bool { crate::config::env::cli_verbose() }
|
||||
pub fn cli_verbose() -> bool {
|
||||
crate::config::env::cli_verbose()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cli_v {
|
||||
@ -11,4 +13,6 @@ macro_rules! cli_v {
|
||||
}
|
||||
|
||||
/// Unstructured trace output function used by pipeline helpers
|
||||
pub fn log<S: AsRef<str>>(msg: S) { eprintln!("{}", msg.as_ref()); }
|
||||
pub fn log<S: AsRef<str>>(msg: S) {
|
||||
eprintln!("{}", msg.as_ref());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user