Merge selfhosting-dev into main (Core-13 pure CI/tests + LLVM bridge) (#126)
* WIP: sync before merging origin/main * fix: unify using/module + build CLI; add missing helper in runner; build passes; core smokes green; jit any.len string now returns 3 * Apply local changes after merging main; keep docs/phase-15 removed per main; add phase-15.1 docs and tests * Remove legacy docs/phase-15/README.md to align with main * integration: add Core-13 pure CI, tests, and minimal LLVM execute bridge (no docs) (#125) Co-authored-by: Tomoaki <tomoaki@example.com> --------- Co-authored-by: Selfhosting Dev <selfhost@example.invalid> Co-authored-by: Tomoaki <tomoaki@example.com>
This commit is contained in:
@ -15,13 +15,19 @@ struct ProgramV0 {
|
||||
#[serde(tag = "type")]
|
||||
enum StmtV0 {
|
||||
Return { expr: ExprV0 },
|
||||
Extern { iface: String, method: String, args: Vec<ExprV0> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "type")]
|
||||
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> }, // short-circuit: &&, || (or: "and"/"or")
|
||||
}
|
||||
|
||||
pub fn parse_json_v0_to_module(json: &str) -> Result<MirModule, String> {
|
||||
@ -29,28 +35,60 @@ pub fn parse_json_v0_to_module(json: &str) -> Result<MirModule, String> {
|
||||
if prog.version != 0 || prog.kind != "Program" {
|
||||
return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into());
|
||||
}
|
||||
let stmt = prog.body.get(0).ok_or("empty body")?;
|
||||
|
||||
// Create module and main function
|
||||
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 entry = BasicBlockId::new(0);
|
||||
let mut f = MirFunction::new(sig, entry);
|
||||
// Build expression
|
||||
let ret_val = match stmt {
|
||||
StmtV0::Return { expr } => lower_expr(&mut f, expr)?,
|
||||
};
|
||||
// Return
|
||||
if let Some(bb) = f.get_block_mut(entry) {
|
||||
bb.set_terminator(MirInstruction::Return { value: Some(ret_val) });
|
||||
|
||||
if prog.body.is_empty() { return Err("empty body".into()); }
|
||||
|
||||
// Lower all statements; capture last expression for return when the last is Return
|
||||
let mut last_ret: Option<(crate::mir::ValueId, BasicBlockId)> = None;
|
||||
for (i, stmt) in prog.body.iter().enumerate() {
|
||||
match stmt {
|
||||
StmtV0::Extern { iface, method, args } => {
|
||||
// void extern call
|
||||
let entry_bb = f.entry_block;
|
||||
let (arg_ids, _cur) = lower_args(&mut f, entry_bb, args)?;
|
||||
if let Some(bb) = f.get_block_mut(entry) {
|
||||
bb.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
|
||||
}
|
||||
if i == prog.body.len()-1 { last_ret = None; }
|
||||
}
|
||||
StmtV0::Return { expr } => {
|
||||
let entry_bb = f.entry_block;
|
||||
last_ret = Some(lower_expr(&mut f, entry_bb, expr)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Infer return type (integer only for v0)
|
||||
f.signature.return_type = MirType::Integer;
|
||||
// Return last value (or 0)
|
||||
if let Some((rv, cur)) = last_ret {
|
||||
if let Some(bb) = f.get_block_mut(cur) {
|
||||
bb.set_terminator(MirInstruction::Return { value: Some(rv) });
|
||||
} else {
|
||||
return Err("invalid block when setting return".into());
|
||||
}
|
||||
} else {
|
||||
let dst_id = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(entry) {
|
||||
bb.add_instruction(MirInstruction::Const { dst: dst_id, value: ConstValue::Integer(0) });
|
||||
bb.set_terminator(MirInstruction::Return { value: Some(dst_id) });
|
||||
}
|
||||
}
|
||||
// Keep return type unknown to allow dynamic display (VM/Interpreter)
|
||||
f.signature.return_type = MirType::Unknown;
|
||||
module.add_function(f);
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fn lower_expr(f: &mut MirFunction, e: &ExprV0) -> Result<crate::mir::ValueId, String> {
|
||||
fn next_block_id(f: &MirFunction) -> BasicBlockId {
|
||||
let mut mx = 0u32;
|
||||
for k in f.blocks.keys() { if k.0 >= mx { mx = k.0 + 1; } }
|
||||
BasicBlockId::new(mx)
|
||||
}
|
||||
|
||||
fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(crate::mir::ValueId, BasicBlockId), String> {
|
||||
match e {
|
||||
ExprV0::Int { value } => {
|
||||
// Accept number or stringified digits
|
||||
@ -60,24 +98,123 @@ fn lower_expr(f: &mut MirFunction, e: &ExprV0) -> Result<crate::mir::ValueId, St
|
||||
return Err("invalid int literal".into());
|
||||
};
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(f.entry_block) {
|
||||
if let Some(bb) = f.get_block_mut(cur_bb) {
|
||||
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) });
|
||||
}
|
||||
Ok(dst)
|
||||
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()) });
|
||||
}
|
||||
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) });
|
||||
}
|
||||
Ok((dst, cur_bb))
|
||||
}
|
||||
ExprV0::Binary { op, lhs, rhs } => {
|
||||
let l = lower_expr(f, lhs)?;
|
||||
let r = lower_expr(f, rhs)?;
|
||||
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
|
||||
let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?;
|
||||
let bop = match op.as_str() { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, "*" => BinaryOp::Mul, "/" => BinaryOp::Div, _ => return Err("unsupported op".into()) };
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(f.entry_block) {
|
||||
if let Some(bb) = f.get_block_mut(cur_after_r) {
|
||||
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
|
||||
}
|
||||
Ok(dst)
|
||||
Ok((dst, cur_after_r))
|
||||
}
|
||||
ExprV0::Extern { iface, method, args } => {
|
||||
let (arg_ids, cur2) = lower_args(f, cur_bb, args)?;
|
||||
let dst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(cur2) {
|
||||
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
|
||||
}
|
||||
Ok((dst, cur2))
|
||||
}
|
||||
ExprV0::Compare { op, lhs, rhs } => {
|
||||
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
|
||||
let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?;
|
||||
let cop = match op.as_str() {
|
||||
"==" => crate::mir::CompareOp::Eq,
|
||||
"!=" => crate::mir::CompareOp::Ne,
|
||||
"<" => crate::mir::CompareOp::Lt,
|
||||
"<=" => crate::mir::CompareOp::Le,
|
||||
">" => crate::mir::CompareOp::Gt,
|
||||
">=" => crate::mir::CompareOp::Ge,
|
||||
_ => return Err("unsupported compare op".into()),
|
||||
};
|
||||
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 });
|
||||
}
|
||||
Ok((dst, cur_after_r))
|
||||
}
|
||||
ExprV0::Logical { op, lhs, rhs } => {
|
||||
// Short-circuit boolean logic with branches + phi
|
||||
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
|
||||
let rhs_bb = next_block_id(f);
|
||||
let fall_bb = BasicBlockId::new(rhs_bb.0 + 1);
|
||||
let merge_bb = BasicBlockId::new(rhs_bb.0 + 2);
|
||||
f.add_block(crate::mir::BasicBlock::new(rhs_bb));
|
||||
f.add_block(crate::mir::BasicBlock::new(fall_bb));
|
||||
f.add_block(crate::mir::BasicBlock::new(merge_bb));
|
||||
// Branch depending on op
|
||||
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 });
|
||||
} else {
|
||||
// OR: if lhs true, go to fall_bb (true path), else evaluate rhs
|
||||
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb });
|
||||
}
|
||||
}
|
||||
// Telemetry: note short-circuit lowering
|
||||
crate::jit::events::emit_lower(
|
||||
serde_json::json!({
|
||||
"id": "shortcircuit",
|
||||
"op": if is_and { "and" } else { "or" },
|
||||
"rhs_bb": rhs_bb.0,
|
||||
"fall_bb": fall_bb.0,
|
||||
"merge_bb": merge_bb.0
|
||||
}),
|
||||
"shortcircuit",
|
||||
"<json_v0>"
|
||||
);
|
||||
// false/true constant in fall_bb depending on op
|
||||
let cdst = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(fall_bb) {
|
||||
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 });
|
||||
}
|
||||
// evaluate rhs in rhs_bb
|
||||
let (rval, _rhs_end) = lower_expr(f, rhs_bb, rhs)?;
|
||||
if let Some(bb) = f.get_block_mut(rhs_bb) {
|
||||
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
|
||||
}
|
||||
// merge with phi
|
||||
let out = f.next_value_id();
|
||||
if let Some(bb) = f.get_block_mut(merge_bb) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_bb, rval), (fall_bb, cdst)] });
|
||||
}
|
||||
Ok((out, merge_bb))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_args(f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0]) -> Result<(Vec<crate::mir::ValueId>, BasicBlockId), String> {
|
||||
let mut out = Vec::with_capacity(args.len());
|
||||
let mut cur = cur_bb;
|
||||
for a in args {
|
||||
let (v, c) = lower_expr(f, cur, a)?; out.push(v); cur = c;
|
||||
}
|
||||
Ok((out, cur))
|
||||
}
|
||||
|
||||
pub fn maybe_dump_mir(module: &MirModule) {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
let mut p = MirPrinter::new();
|
||||
|
||||
@ -83,6 +83,57 @@ impl NyashRunner {
|
||||
}
|
||||
return;
|
||||
}
|
||||
// CLI using/module overrides (MVP): apply early so JSON pipeline can observe them
|
||||
if self.config.using.is_some() || self.config.using_path.is_some() || self.config.modules.is_some()
|
||||
|| std::env::var("NYASH_USING_PATH").is_ok() || std::env::var("NYASH_MODULES").is_ok() {
|
||||
let mut using_paths: Vec<String> = Vec::new();
|
||||
if let Some(p) = self.config.using_path.clone() { for s in p.split(':') { let s=s.trim(); if !s.is_empty() { using_paths.push(s.to_string()); } } }
|
||||
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 using_paths.is_empty() { using_paths.extend(["apps","lib","."].into_iter().map(|s| s.to_string())); }
|
||||
|
||||
// modules mapping
|
||||
let mut modules: Vec<(String,String)> = Vec::new();
|
||||
if let Some(m) = self.config.modules.clone() { for ent in m.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
|
||||
if let Ok(ms) = std::env::var("NYASH_MODULES") { for ent in ms.split(',') { if let Some((k,v)) = ent.split_once('=') { let k=k.trim(); let v=v.trim(); if !k.is_empty() && !v.is_empty() { modules.push((k.to_string(), v.to_string())); } } } }
|
||||
for (ns, path) in &modules { let sb = crate::box_trait::StringBox::new(path.clone()); crate::runtime::modules_registry::set(ns.clone(), Box::new(sb)); }
|
||||
|
||||
// using specs
|
||||
let mut pending_using: Vec<(String, Option<String>, bool)> = Vec::new(); // (target, alias, is_path)
|
||||
if let Some(u) = self.config.using.clone() {
|
||||
for ent in u.split(',') {
|
||||
let s = ent.trim().trim_end_matches(';').trim(); if s.is_empty() { continue; }
|
||||
let (tgt, 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) };
|
||||
let is_path = tgt.starts_with('"') || tgt.starts_with("./") || tgt.starts_with('/') || tgt.ends_with(".nyash");
|
||||
pending_using.push((tgt.trim_matches('"').to_string(), alias, is_path));
|
||||
}
|
||||
}
|
||||
// Resolve using
|
||||
for (tgt, alias, is_path) in pending_using.into_iter() {
|
||||
if is_path {
|
||||
let missing = !std::path::Path::new(&tgt).exists();
|
||||
if missing {
|
||||
if std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1") {
|
||||
eprintln!("❌ using: path not found: {}", tgt);
|
||||
std::process::exit(1);
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] path not found (continuing): {}", tgt);
|
||||
}
|
||||
}
|
||||
}
|
||||
let value = if is_path { tgt.clone() } else if let Some((_n,p)) = modules.iter().find(|(n,_)| n==&tgt) { p.clone() } else {
|
||||
let rel = tgt.replace('.', "/") + ".nyash";
|
||||
let mut found: Option<String> = None;
|
||||
for base in &using_paths { let cand = std::path::Path::new(base).join(&rel); if cand.exists() { found = Some(cand.to_string_lossy().to_string()); break; } }
|
||||
if found.is_none() && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] unresolved '{}'; tried {}", tgt, using_paths.join(":"));
|
||||
}
|
||||
found.unwrap_or(tgt.clone())
|
||||
};
|
||||
let sb = crate::box_trait::StringBox::new(value.clone());
|
||||
crate::runtime::modules_registry::set(tgt.clone(), Box::new(sb));
|
||||
if let Some(a) = alias { let sb2 = crate::box_trait::StringBox::new(value); crate::runtime::modules_registry::set(a, Box::new(sb2)); }
|
||||
}
|
||||
}
|
||||
// Phase-15: JSON IR v0 bridge (stdin/file)
|
||||
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
|
||||
let json = if let Some(path) = &self.config.json_file {
|
||||
@ -102,8 +153,12 @@ impl NyashRunner {
|
||||
Ok(module) => {
|
||||
// Optional dump via env verbose
|
||||
json_v0_bridge::maybe_dump_mir(&module);
|
||||
// Execute via MIR interpreter
|
||||
self.execute_mir_module(&module);
|
||||
// Execute via selected backend (vm or interpreter)
|
||||
if self.config.backend == "vm" {
|
||||
self.execute_vm_module(&module);
|
||||
} else {
|
||||
self.execute_mir_module(&module);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
@ -130,6 +185,9 @@ impl NyashRunner {
|
||||
if let Some(ref filename) = self.config.file {
|
||||
if let Ok(code) = fs::read_to_string(filename) {
|
||||
// Scan first 128 lines for directives
|
||||
let mut pending_using: Vec<(String, Option<String>)> = Vec::new();
|
||||
let mut pending_modules: Vec<(String, String)> = Vec::new();
|
||||
let mut using_paths: Vec<String> = Vec::new();
|
||||
for (i, line) in code.lines().take(128).enumerate() {
|
||||
let l = line.trim();
|
||||
if !(l.starts_with("//") || l.starts_with("#!") || l.is_empty()) {
|
||||
@ -143,6 +201,24 @@ impl NyashRunner {
|
||||
let key = k.trim(); let val = v.trim();
|
||||
if !key.is_empty() { std::env::set_var(key, val); }
|
||||
}
|
||||
} else if let Some(dir) = rest.strip_prefix("@using ") {
|
||||
// @using ns[ as Alias]
|
||||
let s = dir.trim().trim_end_matches(';').trim();
|
||||
let (ns, 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) };
|
||||
pending_using.push((ns, alias));
|
||||
} else if let Some(dir) = rest.strip_prefix("@module ") {
|
||||
// @module ns=path
|
||||
if let Some((ns, path)) = dir.split_once('=') {
|
||||
let ns = ns.trim().to_string();
|
||||
let path = path.trim().trim_matches('"').to_string();
|
||||
pending_modules.push((ns, path));
|
||||
}
|
||||
} else if let Some(dir) = rest.strip_prefix("@using-path ") {
|
||||
// @using-path apps:lib:. (':' separated)
|
||||
let s = dir.trim();
|
||||
for p in s.split(':') { let p = p.trim(); if !p.is_empty() { using_paths.push(p.to_string()); } }
|
||||
} else if rest == "@jit-debug" {
|
||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||
std::env::set_var("NYASH_JIT_THRESHOLD", "1");
|
||||
@ -160,6 +236,50 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Env overrides for using rules
|
||||
if let Ok(paths) = std::env::var("NYASH_USING_PATH") {
|
||||
for p in paths.split(':') { let p = p.trim(); if !p.is_empty() { 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() { pending_modules.push((k.to_string(), v.to_string())); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pending modules to registry as StringBox (path or ns token)
|
||||
for (ns, path) in pending_modules.iter() {
|
||||
let sb = nyash_rust::box_trait::StringBox::new(path.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
}
|
||||
// Resolve pending using via modules map or using_paths (best-effort)
|
||||
for (ns, alias) in pending_using.iter() {
|
||||
// direct mapping first
|
||||
let value = if let Some((_n, p)) = pending_modules.iter().find(|(n, _)| n == ns) {
|
||||
p.clone()
|
||||
} else {
|
||||
// try search paths: <path>/<ns as a/b/c>.nyash
|
||||
let rel = ns.replace('.', "/") + ".nyash";
|
||||
let mut found = None;
|
||||
if let Some(dir) = std::path::Path::new(filename).parent() {
|
||||
let cand = dir.join(&rel);
|
||||
if cand.exists() { found = Some(cand.to_string_lossy().to_string()); }
|
||||
}
|
||||
if found.is_none() {
|
||||
for base in &using_paths { let cand = std::path::Path::new(base).join(&rel); if cand.exists() { found = Some(cand.to_string_lossy().to_string()); break; } }
|
||||
}
|
||||
found.unwrap_or_else(|| ns.clone())
|
||||
};
|
||||
let sb = nyash_rust::box_trait::StringBox::new(value.clone());
|
||||
nyash_rust::runtime::modules_registry::set(ns.clone(), Box::new(sb));
|
||||
if let Some(a) = alias {
|
||||
let sb2 = nyash_rust::box_trait::StringBox::new(value);
|
||||
nyash_rust::runtime::modules_registry::set(a.clone(), Box::new(sb2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -944,9 +1064,23 @@ impl NyashRunner {
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
// Ensure NyRT static library exists
|
||||
let nyrt_root = cwd.join("target").join("release").join("libnyrt.a");
|
||||
let nyrt_crate = cwd.join("crates").join("nyrt").join("target").join("release").join("libnyrt.a");
|
||||
if !nyrt_root.exists() && !nyrt_crate.exists() {
|
||||
// Try to build crates/nyrt
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
cmd.arg("--release");
|
||||
cmd.current_dir(cwd.join("crates").join("nyrt"));
|
||||
println!("[link] building NyRT (libnyrt.a) ...");
|
||||
let st = cmd.status().map_err(|e| format!("spawn cargo (nyrt): {}", e))?;
|
||||
if !st.success() { return Err("failed to build NyRT (libnyrt.a)".into()); }
|
||||
}
|
||||
let status = std::process::Command::new("cc")
|
||||
.arg(&obj_path)
|
||||
.args(["-L", &cwd.join("target").join("release").display().to_string()])
|
||||
.args(["-L", &cwd.join("crates").join("nyrt").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))?;
|
||||
@ -958,6 +1092,55 @@ impl NyashRunner {
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
/// Execute a prepared MIR module via the VM
|
||||
fn execute_vm_module(&self, module: &crate::mir::MirModule) {
|
||||
use crate::backend::VM;
|
||||
use crate::mir::MirType;
|
||||
use crate::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
|
||||
use crate::boxes::FloatBox;
|
||||
let mut vm = VM::new();
|
||||
match vm.execute_module(module) {
|
||||
Ok(result) => {
|
||||
if let Some(func) = module.functions.get("main") {
|
||||
let (ety, sval) = 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 { ("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) }
|
||||
}
|
||||
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) }
|
||||
}
|
||||
MirType::String => {
|
||||
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||
("String", sb.value.clone())
|
||||
} else { ("String", result.to_string_box().value) }
|
||||
}
|
||||
_ => { (result.type_name(), result.to_string_box().value) }
|
||||
};
|
||||
println!("ResultType(VM): {}", ety);
|
||||
println!("Result: {}", sval);
|
||||
} else {
|
||||
println!("Result: {:?}", result);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ VM execution error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Run a file through independent JIT engine (no VM execute loop)
|
||||
fn run_file_jit_direct(&self, filename: &str) {
|
||||
use std::fs;
|
||||
|
||||
@ -5,6 +5,34 @@ use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
|
||||
use nyash_rust::runner_plugin_init;
|
||||
use std::{fs, process};
|
||||
|
||||
// limited directory walk: add matching files ending with .nyash and given leaf name
|
||||
fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
|
||||
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 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; }
|
||||
} 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let p = std::path::Path::new(base);
|
||||
walk(p, leaf, out, 4);
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
/// File-mode dispatcher (thin wrapper around backend/mode selection)
|
||||
pub(crate) fn run_file(&self, filename: &str) {
|
||||
@ -115,6 +143,7 @@ impl NyashRunner {
|
||||
|
||||
/// Execute Nyash file with interpreter (common helper)
|
||||
pub(crate) fn execute_nyash_file(&self, filename: &str) {
|
||||
let quiet_pipe = std::env::var("NYASH_JSON_ONLY").ok().as_deref() == Some("1");
|
||||
// Ensure plugin host and provider mappings are initialized (idempotent)
|
||||
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
|
||||
// Call via lib crate to avoid referring to the bin crate root
|
||||
@ -125,25 +154,112 @@ impl NyashRunner {
|
||||
Ok(content) => content,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") && !quiet_pipe {
|
||||
println!("📝 File contents:\n{}", code);
|
||||
println!("\n🚀 Parsing and executing...\n");
|
||||
}
|
||||
|
||||
println!("📝 File contents:\n{}", code);
|
||||
println!("\n🚀 Parsing and executing...\n");
|
||||
// Optional Phase-15: strip `using` lines (gate) for minimal acceptance
|
||||
let mut code_ref: &str = &code;
|
||||
let enable_using = std::env::var("NYASH_ENABLE_USING").ok().as_deref() == Some("1");
|
||||
let cleaned_code_owned;
|
||||
if enable_using {
|
||||
let mut out = String::with_capacity(code.len());
|
||||
let mut used_names: Vec<(String, Option<String>)> = Vec::new();
|
||||
for line in code.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("using ") {
|
||||
// Skip `using ns` or `using ns as alias` lines (MVP)
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] stripped line: {}", line);
|
||||
}
|
||||
// Parse namespace or path and optional alias
|
||||
let rest0 = t.strip_prefix("using ").unwrap().trim();
|
||||
// allow trailing semicolon
|
||||
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
|
||||
// Split alias
|
||||
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) };
|
||||
// If quoted or looks like relative/absolute path, treat as path; else as namespace
|
||||
let is_path = target.starts_with('"') || target.starts_with("./") || target.starts_with('/') || target.ends_with(".nyash");
|
||||
if is_path {
|
||||
let mut path = target.trim_matches('"').to_string();
|
||||
// existence check and strict handling
|
||||
let missing = !std::path::Path::new(&path).exists();
|
||||
if missing {
|
||||
if std::env::var("NYASH_USING_STRICT").ok().as_deref() == Some("1") {
|
||||
eprintln!("❌ using: path not found: {}", path);
|
||||
std::process::exit(1);
|
||||
} else if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] path not found (continuing): {}", path);
|
||||
}
|
||||
}
|
||||
// choose alias or derive from filename stem
|
||||
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()
|
||||
});
|
||||
// register alias only (path-backed)
|
||||
used_names.push((name, Some(path)));
|
||||
} else {
|
||||
used_names.push((target, alias));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
out.push_str(line);
|
||||
out.push('\n');
|
||||
}
|
||||
cleaned_code_owned = out;
|
||||
code_ref = &cleaned_code_owned;
|
||||
|
||||
// Register modules into minimal registry with best-effort path resolution
|
||||
for (ns_or_alias, alias_or_path) in used_names {
|
||||
// alias_or_path Some(path) means this entry was a direct path using
|
||||
if let Some(path) = alias_or_path {
|
||||
let sb = crate::box_trait::StringBox::new(path);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
} else {
|
||||
let rel = format!("apps/{}.nyash", ns_or_alias.replace('.', "/"));
|
||||
let exists = std::path::Path::new(&rel).exists();
|
||||
if !exists && std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[using] unresolved namespace '{}'; tried '{}'. Hint: add @module {}={} or --module {}={}", ns_or_alias, rel, ns_or_alias, rel, ns_or_alias, rel);
|
||||
// naive candidates by suffix within common bases
|
||||
let leaf = ns_or_alias.split('.').last().unwrap_or(&ns_or_alias);
|
||||
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.is_empty() {
|
||||
eprintln!("[using] candidates: {}", cands.join(", "));
|
||||
}
|
||||
}
|
||||
let path_or_ns = if exists { rel } else { ns_or_alias.clone() };
|
||||
let sb = crate::box_trait::StringBox::new(path_or_ns);
|
||||
crate::runtime::modules_registry::set(ns_or_alias, Box::new(sb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the code with debug fuel limit
|
||||
eprintln!("🔍 DEBUG: Starting parse with fuel: {:?}...", self.config.debug_fuel);
|
||||
let ast = match NyashParser::parse_from_string_with_fuel(&code, self.config.debug_fuel) {
|
||||
let ast = match NyashParser::parse_from_string_with_fuel(code_ref, self.config.debug_fuel) {
|
||||
Ok(ast) => { eprintln!("🔍 DEBUG: Parse completed, AST created"); ast },
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
|
||||
println!("✅ Parse successful!");
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") && !quiet_pipe {
|
||||
println!("✅ Parse successful!");
|
||||
}
|
||||
|
||||
// Execute the AST
|
||||
let mut interpreter = NyashInterpreter::new();
|
||||
eprintln!("🔍 DEBUG: Starting execution...");
|
||||
match interpreter.execute(ast) {
|
||||
Ok(result) => {
|
||||
println!("✅ Execution completed successfully!");
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") && !quiet_pipe {
|
||||
println!("✅ Execution completed successfully!");
|
||||
}
|
||||
// Normalize display via semantics: prefer numeric, then string, then fallback
|
||||
let disp = {
|
||||
// Special-case: plugin IntegerBox → call .get to fetch numeric value
|
||||
@ -182,7 +298,7 @@ impl NyashRunner {
|
||||
.unwrap_or_else(|| result.to_string_box().value)
|
||||
}
|
||||
};
|
||||
println!("Result: {}", disp);
|
||||
if !quiet_pipe { println!("Result: {}", disp); }
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Runtime error:\n{}", e.detailed_message(Some(&code)));
|
||||
|
||||
Reference in New Issue
Block a user