mir: implement proper short-circuit lowering (&&/||) via branch+phi; vm: add NYASH_VM_TRACE exec/phi logs and reg_load diagnostics; vm-fallback: minimal Void guards (push/get_position/line/column), MapBox.birth no-op; smokes: filter builtin Array/Map plugin notices; docs: CURRENT_TASK updated
This commit is contained in:
@ -3,6 +3,11 @@ use crate::mir::basic_block::BasicBlock;
|
||||
use std::mem;
|
||||
|
||||
impl MirInterpreter {
|
||||
fn trace_enabled() -> bool {
|
||||
std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
|
||||
}
|
||||
|
||||
pub(super) fn exec_function_inner(
|
||||
&mut self,
|
||||
func: &MirFunction,
|
||||
@ -28,8 +33,25 @@ impl MirInterpreter {
|
||||
.get(&cur)
|
||||
.ok_or_else(|| VMError::InvalidBasicBlock(format!("bb {:?} not found", cur)))?;
|
||||
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] enter bb={:?} pred={:?} fn={}",
|
||||
cur,
|
||||
last_pred,
|
||||
self.cur_fn.as_deref().unwrap_or("")
|
||||
);
|
||||
}
|
||||
|
||||
self.apply_phi_nodes(block, last_pred)?;
|
||||
self.execute_block_instructions(block)?;
|
||||
if let Err(e) = self.execute_block_instructions(block) {
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] error in bb={:?}: {:?}\n last_inst={:?}",
|
||||
cur, e, self.last_inst
|
||||
);
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
match self.handle_terminator(block)? {
|
||||
BlockOutcome::Return(result) => {
|
||||
@ -60,10 +82,22 @@ impl MirInterpreter {
|
||||
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
||||
let v = self.reg_load(*val)?;
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} take pred={:?} val={:?}",
|
||||
dst_id, pred, val
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if let Some((_, val)) = inputs.first() {
|
||||
let v = self.reg_load(*val)?;
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} take default val={:?}",
|
||||
dst_id, val
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -72,6 +106,11 @@ impl MirInterpreter {
|
||||
|
||||
fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> {
|
||||
for inst in block.non_phi_instructions() {
|
||||
self.last_block = Some(block.id);
|
||||
self.last_inst = Some(inst.clone());
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst);
|
||||
}
|
||||
self.execute_instruction(inst)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@ -92,12 +92,52 @@ impl MirInterpreter {
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<(), VMError> {
|
||||
// Graceful void guard for common short-circuit patterns in user code
|
||||
// e.g., `A or not last.is_eof()` should not crash when last is absent.
|
||||
match self.reg_load(box_val)? {
|
||||
VMValue::Void => {
|
||||
match method {
|
||||
"is_eof" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Bool(false)); } return Ok(()); }
|
||||
"length" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"substring" => { if let Some(d) = dst { self.regs.insert(d, VMValue::String(String::new())); } return Ok(()); }
|
||||
"push" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Void); } return Ok(()); }
|
||||
"get_position" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"get_line" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
"get_column" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
VMValue::BoxRef(ref b) => {
|
||||
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
match method {
|
||||
"is_eof" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Bool(false)); } return Ok(()); }
|
||||
"length" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"substring" => { if let Some(d) = dst { self.regs.insert(d, VMValue::String(String::new())); } return Ok(()); }
|
||||
"push" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Void); } return Ok(()); }
|
||||
"get_position" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(0)); } return Ok(()); }
|
||||
"get_line" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
"get_column" => { if let Some(d) = dst { self.regs.insert(d, VMValue::Integer(1)); } return Ok(()); }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.try_handle_object_fields(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_instance_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_string_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_array_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
if self.try_handle_map_box(dst, box_val, method, args)? {
|
||||
return Ok(());
|
||||
}
|
||||
self.invoke_plugin_box(dst, box_val, method, args)
|
||||
}
|
||||
|
||||
@ -149,6 +189,78 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_handle_map_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(mb) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init for MapBox
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("MapBox.set expects 2 args".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let v = self.reg_load(args[1])?.to_nyash_box();
|
||||
let ret = mb.set(k, v);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.get expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.get(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"has" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.has expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.has(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"delete" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("MapBox.delete expects 1 arg".into())); }
|
||||
let k = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = mb.delete(k);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"size" => {
|
||||
let ret = mb.size();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"keys" => {
|
||||
let ret = mb.keys();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"values" => {
|
||||
let ret = mb.values();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_string_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
@ -173,6 +285,29 @@ impl MirInterpreter {
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"substring" => {
|
||||
if args.len() != 2 {
|
||||
return Err(VMError::InvalidInstruction(
|
||||
"substring expects 2 args (start, end)".into(),
|
||||
));
|
||||
}
|
||||
let s_idx = self.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let e_idx = self.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
let len = sb.value.chars().count() as i64;
|
||||
let start = s_idx.max(0).min(len) as usize;
|
||||
let end = e_idx.max(start as i64).min(len) as usize;
|
||||
let chars: Vec<char> = sb.value.chars().collect();
|
||||
let sub: String = chars[start..end].iter().collect();
|
||||
if let Some(d) = dst {
|
||||
self.regs.insert(
|
||||
d,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(
|
||||
sub,
|
||||
))),
|
||||
);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
"concat" => {
|
||||
if args.len() != 1 {
|
||||
return Err(VMError::InvalidInstruction("concat expects 1 arg".into()));
|
||||
@ -195,6 +330,93 @@ impl MirInterpreter {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_instance_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv_vm = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv_vm.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(inst) = recv_box_any.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
// Resolve lowered method function: "Class.method/arity"
|
||||
let fname = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||
if let Some(func) = self.functions.get(&fname).cloned() {
|
||||
// Build argv: me + args
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
||||
argv.push(recv_vm.clone());
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
let ret = self.exec_function_inner(&func, Some(&argv))?;
|
||||
if let Some(d) = dst {
|
||||
self.regs.insert(d, ret);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn try_handle_array_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
box_val: ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = self.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(ab) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"push" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("push expects 1 arg".into())); }
|
||||
let val = self.reg_load(args[0])?.to_nyash_box();
|
||||
let _ = ab.push(val);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = ab.length();
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
if args.len() != 1 { return Err(VMError::InvalidInstruction("get expects 1 arg".into())); }
|
||||
let idx = self.reg_load(args[0])?.to_nyash_box();
|
||||
let ret = ab.get(idx);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::from_nyash_box(ret)); }
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
if args.len() != 2 { return Err(VMError::InvalidInstruction("set expects 2 args".into())); }
|
||||
let idx = self.reg_load(args[0])?.to_nyash_box();
|
||||
let val = self.reg_load(args[1])?.to_nyash_box();
|
||||
let _ = ab.set(idx, val);
|
||||
if let Some(d) = dst { self.regs.insert(d, VMValue::Void); }
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn invoke_plugin_box(
|
||||
&mut self,
|
||||
dst: Option<ValueId>,
|
||||
|
||||
@ -2,10 +2,31 @@ use super::*;
|
||||
|
||||
impl MirInterpreter {
|
||||
pub(super) fn reg_load(&self, id: ValueId) -> Result<VMValue, VMError> {
|
||||
self.regs
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or_else(|| VMError::InvalidValue(format!("use of undefined value {:?}", id)))
|
||||
match self.regs.get(&id).cloned() {
|
||||
Some(v) => Ok(v),
|
||||
None => {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
|
||||
{
|
||||
let keys: Vec<String> = self
|
||||
.regs
|
||||
.keys()
|
||||
.map(|k| format!("{:?}", k))
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[vm-trace] reg_load undefined id={:?} last_block={:?} last_inst={:?} regs={}",
|
||||
id,
|
||||
self.last_block,
|
||||
self.last_inst,
|
||||
keys.join(", ")
|
||||
);
|
||||
}
|
||||
Err(VMError::InvalidValue(format!(
|
||||
"use of undefined value {:?}",
|
||||
id
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn eval_binop(
|
||||
@ -40,6 +61,8 @@ impl MirInterpreter {
|
||||
(BitAnd, Integer(x), Integer(y)) => Integer(x & y),
|
||||
(BitOr, Integer(x), Integer(y)) => Integer(x | y),
|
||||
(BitXor, Integer(x), Integer(y)) => Integer(x ^ y),
|
||||
(And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y),
|
||||
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
|
||||
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
|
||||
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
|
||||
(opk, va, vb) => {
|
||||
@ -65,6 +88,10 @@ impl MirInterpreter {
|
||||
(Le, Float(x), Float(y)) => x <= y,
|
||||
(Gt, Float(x), Float(y)) => x > y,
|
||||
(Ge, Float(x), Float(y)) => x >= y,
|
||||
(Lt, VMValue::String(ref s), VMValue::String(ref t)) => s < t,
|
||||
(Le, VMValue::String(ref s), VMValue::String(ref t)) => s <= t,
|
||||
(Gt, VMValue::String(ref s), VMValue::String(ref t)) => s > t,
|
||||
(Ge, VMValue::String(ref s), VMValue::String(ref t)) => s >= t,
|
||||
(opk, va, vb) => {
|
||||
return Err(VMError::TypeError(format!(
|
||||
"unsupported compare {:?} on {:?} and {:?}",
|
||||
|
||||
@ -27,6 +27,9 @@ pub struct MirInterpreter {
|
||||
pub(super) obj_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
||||
pub(super) functions: HashMap<String, MirFunction>,
|
||||
pub(super) cur_fn: Option<String>,
|
||||
// Trace context (dev-only; enabled with NYASH_VM_TRACE=1)
|
||||
pub(super) last_block: Option<BasicBlockId>,
|
||||
pub(super) last_inst: Option<MirInstruction>,
|
||||
}
|
||||
|
||||
impl MirInterpreter {
|
||||
@ -37,6 +40,8 @@ impl MirInterpreter {
|
||||
obj_fields: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
cur_fn: None,
|
||||
last_block: None,
|
||||
last_inst: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -420,9 +420,9 @@ impl MirBuilder {
|
||||
// Record origin for optimization: dst was created by NewBox of class
|
||||
self.value_origin_newbox.insert(dst, class.clone());
|
||||
|
||||
// For plugin/builtin boxes, call birth(...). For user-defined boxes, skip (InstanceBox already constructed)
|
||||
// Special-case: StringBox is already fully constructed via from_i8_string in LLVM lowering; skip birth
|
||||
if !self.user_defined_boxes.contains(&class) && class != "StringBox" {
|
||||
// Call birth(...) for all boxes except StringBox (special-cased in LLVM path)
|
||||
// User-defined boxes require birth to initialize fields (scanner/tokens etc.)
|
||||
if class != "StringBox" {
|
||||
let birt_mid = resolve_slot_by_type_name(&class, "birth");
|
||||
self.emit_box_or_plugin_call(
|
||||
None,
|
||||
|
||||
@ -373,6 +373,22 @@ impl super::MirBuilder {
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
// Secondary fallback: search already-materialized functions in the current module
|
||||
if let Some(ref module) = self.current_module {
|
||||
let tail = format!(".{}{}", name, format!("/{}", arg_values.len()));
|
||||
let mut cands: Vec<String> = module
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.ends_with(&tail))
|
||||
.cloned()
|
||||
.collect();
|
||||
if cands.len() == 1 {
|
||||
let func_name = cands.remove(0);
|
||||
let dst = self.value_gen.next();
|
||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
// Propagate original error
|
||||
return Err(format!("Unresolved function: '{}'. {}", name, super::call_resolution::suggest_resolution(&name)));
|
||||
}
|
||||
|
||||
@ -46,15 +46,9 @@ pub fn resolve_call_target(
|
||||
return Ok(Callee::Extern(name.to_string()));
|
||||
}
|
||||
|
||||
// 5. Fallback: when inside a static box, treat bare `name()` as a static method of the box.
|
||||
// This helps scripts that omit the box qualifier inside the same static box scope.
|
||||
if let Some(box_name) = current_static_box {
|
||||
return Ok(Callee::Method {
|
||||
box_name: box_name.clone(),
|
||||
method: name.to_string(),
|
||||
receiver: None,
|
||||
});
|
||||
}
|
||||
// 5. Do not assume bare `name()` refers to current static box.
|
||||
// Leave it unresolved so caller can try static_method_index fallback
|
||||
// or report a clear unresolved error.
|
||||
|
||||
// 6. Resolution failed - prevent runtime string-based resolution
|
||||
Err(format!("Unresolved function: '{}'. {}", name, suggest_resolution(name)))
|
||||
|
||||
@ -3,15 +3,20 @@ use crate::ast::ASTNode;
|
||||
|
||||
// Lifecycle routines extracted from builder.rs
|
||||
impl super::MirBuilder {
|
||||
fn preindex_static_methods_from_ast(&mut self, node: &ASTNode) {
|
||||
/// Unified declaration indexing (Phase A): collect symbols before lowering
|
||||
/// - user_defined_boxes: non-static Box names (for NewBox birth() skip)
|
||||
/// - static_method_index: name -> [(BoxName, arity)] (for bare-call fallback)
|
||||
fn index_declarations(&mut self, node: &ASTNode) {
|
||||
match node {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
for st in statements {
|
||||
self.preindex_static_methods_from_ast(st);
|
||||
self.index_declarations(st);
|
||||
}
|
||||
}
|
||||
ASTNode::BoxDeclaration { name, methods, is_static, .. } => {
|
||||
if *is_static {
|
||||
if !*is_static {
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
} else {
|
||||
for (mname, mast) in methods {
|
||||
if let ASTNode::FunctionDeclaration { params, .. } = mast {
|
||||
self.static_method_index
|
||||
@ -59,8 +64,88 @@ impl super::MirBuilder {
|
||||
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
|
||||
// Pre-index static methods to enable safe fallback for bare calls in using-prepended code
|
||||
let snapshot = ast.clone();
|
||||
self.preindex_static_methods_from_ast(&snapshot);
|
||||
self.build_expression(ast)
|
||||
// Phase A: collect declarations in one pass (symbols available to lowering)
|
||||
self.index_declarations(&snapshot);
|
||||
// Phase B: top-level program lowering with declaration-first pass
|
||||
match ast {
|
||||
ASTNode::Program { statements, .. } => {
|
||||
use crate::ast::ASTNode as N;
|
||||
// First pass: lower declarations (static boxes except Main, and instance boxes)
|
||||
let mut main_static: Option<(String, std::collections::HashMap<String, ASTNode>)> = None;
|
||||
for st in &statements {
|
||||
if let N::BoxDeclaration {
|
||||
name,
|
||||
methods,
|
||||
is_static,
|
||||
fields,
|
||||
constructors,
|
||||
weak_fields,
|
||||
..
|
||||
} = st
|
||||
{
|
||||
if *is_static {
|
||||
if name == "Main" {
|
||||
main_static = Some((name.clone(), methods.clone()));
|
||||
} else {
|
||||
// Lower all static methods into standalone functions: BoxName.method/Arity
|
||||
for (mname, mast) in methods.iter() {
|
||||
if let N::FunctionDeclaration { params, body, .. } = mast {
|
||||
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
|
||||
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
|
||||
self.static_method_index
|
||||
.entry(mname.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((name.clone(), params.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Instance box: register type and lower instance methods/ctors as functions
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
self.build_box_declaration(
|
||||
name.clone(),
|
||||
methods.clone(),
|
||||
fields.clone(),
|
||||
weak_fields.clone(),
|
||||
)?;
|
||||
for (ctor_key, ctor_ast) in constructors.iter() {
|
||||
if let N::FunctionDeclaration { params, body, .. } = ctor_ast {
|
||||
let func_name = format!("{}.{}", name, ctor_key);
|
||||
self.lower_method_as_function(
|
||||
func_name,
|
||||
name.clone(),
|
||||
params.clone(),
|
||||
body.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for (mname, mast) in methods.iter() {
|
||||
if let N::FunctionDeclaration { params, body, is_static, .. } = mast {
|
||||
if !*is_static {
|
||||
let func_name = format!("{}.{}{}", name, mname, format!("/{}", params.len()));
|
||||
self.lower_method_as_function(
|
||||
func_name,
|
||||
name.clone(),
|
||||
params.clone(),
|
||||
body.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: lower Main.main body as Program (entry). If absent, fall back to sequential block.
|
||||
if let Some((box_name, methods)) = main_static {
|
||||
self.build_static_main_box(box_name, methods)
|
||||
} else {
|
||||
// Fallback: sequential lowering (keeps legacy behavior for scripts without Main)
|
||||
self.cf_block(statements)
|
||||
}
|
||||
}
|
||||
other => self.build_expression(other),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn finalize_module(
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use super::{MirInstruction, MirType, ValueId};
|
||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||
use crate::ast::{ASTNode, BinaryOperator};
|
||||
use crate::mir::{BinaryOp, CompareOp, TypeOpKind, UnaryOp};
|
||||
|
||||
@ -17,6 +18,11 @@ impl super::MirBuilder {
|
||||
operator: BinaryOperator,
|
||||
right: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
// Short-circuit logical ops: lower to control-flow so RHS is evaluated conditionally
|
||||
if matches!(operator, BinaryOperator::And | BinaryOperator::Or) {
|
||||
return self.build_logical_shortcircuit(left, operator, right);
|
||||
}
|
||||
|
||||
let lhs = self.build_expression(left)?;
|
||||
let rhs = self.build_expression(right)?;
|
||||
let dst = self.value_gen.next();
|
||||
@ -94,6 +100,169 @@ impl super::MirBuilder {
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Lower logical && / || with proper short-circuit semantics.
|
||||
/// Result is a Bool, and RHS is only evaluated if needed.
|
||||
fn build_logical_shortcircuit(
|
||||
&mut self,
|
||||
left: ASTNode,
|
||||
operator: BinaryOperator,
|
||||
right: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
let is_and = matches!(operator, BinaryOperator::And);
|
||||
|
||||
// Evaluate LHS only once
|
||||
let lhs_val = self.build_expression(left)?;
|
||||
|
||||
// Prepare blocks
|
||||
let then_block = self.block_gen.next();
|
||||
let else_block = self.block_gen.next();
|
||||
let merge_block = self.block_gen.next();
|
||||
|
||||
// Branch on LHS truthiness (runtime to_bool semantics in interpreter/LLVM)
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: lhs_val,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
})?;
|
||||
|
||||
// Snapshot variables before entering branches
|
||||
let pre_if_var_map = self.variable_map.clone();
|
||||
|
||||
// ---- THEN branch ----
|
||||
self.current_block = Some(then_block);
|
||||
self.ensure_block_exists(then_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
// Reset scope to pre-if snapshot for clean deltas
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
|
||||
// AND: then → evaluate RHS and reduce to bool
|
||||
// OR: then → constant true
|
||||
let then_value_raw = if is_and {
|
||||
// Reduce arbitrary RHS to bool by branching on its truthiness and returning consts
|
||||
let rhs_true = self.block_gen.next();
|
||||
let rhs_false = self.block_gen.next();
|
||||
let rhs_join = self.block_gen.next();
|
||||
let rhs_val = self.build_expression(right.clone())?;
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: rhs_val,
|
||||
then_bb: rhs_true,
|
||||
else_bb: rhs_false,
|
||||
})?;
|
||||
// true path
|
||||
self.start_new_block(rhs_true)?;
|
||||
let t_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
|
||||
let rhs_true_exit = self.current_block()?;
|
||||
// false path
|
||||
self.start_new_block(rhs_false)?;
|
||||
let f_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
|
||||
let rhs_false_exit = self.current_block()?;
|
||||
// join rhs result into a single bool
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: rhs_bool, inputs })?;
|
||||
self.value_types.insert(rhs_bool, MirType::Bool);
|
||||
rhs_bool
|
||||
} else {
|
||||
let t_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
|
||||
t_id
|
||||
};
|
||||
let then_exit_block = self.current_block()?;
|
||||
let then_var_map_end = self.variable_map.clone();
|
||||
if !self.is_current_block_terminated() {
|
||||
self.hint_scope_leave(0);
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// ---- ELSE branch ----
|
||||
self.current_block = Some(else_block);
|
||||
self.ensure_block_exists(else_block)?;
|
||||
self.hint_scope_enter(0);
|
||||
self.variable_map = pre_if_var_map.clone();
|
||||
// AND: else → false
|
||||
// OR: else → evaluate RHS and reduce to bool
|
||||
let else_value_raw = if is_and {
|
||||
let f_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
|
||||
f_id
|
||||
} else {
|
||||
let rhs_true = self.block_gen.next();
|
||||
let rhs_false = self.block_gen.next();
|
||||
let rhs_join = self.block_gen.next();
|
||||
let rhs_val = self.build_expression(right)?;
|
||||
self.emit_instruction(MirInstruction::Branch {
|
||||
condition: rhs_val,
|
||||
then_bb: rhs_true,
|
||||
else_bb: rhs_false,
|
||||
})?;
|
||||
// true path
|
||||
self.start_new_block(rhs_true)?;
|
||||
let t_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: t_id, value: crate::mir::ConstValue::Bool(true) })?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
|
||||
let rhs_true_exit = self.current_block()?;
|
||||
// false path
|
||||
self.start_new_block(rhs_false)?;
|
||||
let f_id = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const { dst: f_id, value: crate::mir::ConstValue::Bool(false) })?;
|
||||
self.emit_instruction(MirInstruction::Jump { target: rhs_join })?;
|
||||
let rhs_false_exit = self.current_block()?;
|
||||
// join rhs result into a single bool
|
||||
self.start_new_block(rhs_join)?;
|
||||
let rhs_bool = self.value_gen.next();
|
||||
let inputs = vec![(rhs_true_exit, t_id), (rhs_false_exit, f_id)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: rhs_bool, inputs })?;
|
||||
self.value_types.insert(rhs_bool, MirType::Bool);
|
||||
rhs_bool
|
||||
};
|
||||
let else_exit_block = self.current_block()?;
|
||||
let else_var_map_end = self.variable_map.clone();
|
||||
if !self.is_current_block_terminated() {
|
||||
self.hint_scope_leave(0);
|
||||
self.emit_instruction(MirInstruction::Jump { target: merge_block })?;
|
||||
}
|
||||
|
||||
// ---- MERGE ----
|
||||
self.current_block = Some(merge_block);
|
||||
self.ensure_block_exists(merge_block)?;
|
||||
self.push_if_merge(merge_block);
|
||||
|
||||
// Result PHI (bool)
|
||||
let result_val = self.value_gen.next();
|
||||
let inputs = vec![(then_exit_block, then_value_raw), (else_exit_block, else_value_raw)];
|
||||
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
|
||||
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
|
||||
}
|
||||
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
|
||||
self.value_types.insert(result_val, MirType::Bool);
|
||||
|
||||
// Merge modified vars from both branches back into current scope
|
||||
self.merge_modified_vars(
|
||||
then_block,
|
||||
else_block,
|
||||
then_exit_block,
|
||||
Some(else_exit_block),
|
||||
&pre_if_var_map,
|
||||
&then_var_map_end,
|
||||
&Some(else_var_map_end),
|
||||
None,
|
||||
)?;
|
||||
|
||||
self.pop_if_merge();
|
||||
Ok(result_val)
|
||||
}
|
||||
|
||||
// Build a unary operation
|
||||
pub(super) fn build_unary_op(
|
||||
&mut self,
|
||||
|
||||
@ -84,13 +84,32 @@ impl NyashParser {
|
||||
}
|
||||
}
|
||||
|
||||
// Tolerate trailing NEWLINE(s) before the closing '}' of the static box
|
||||
while self.match_token(&TokenType::NEWLINE) { self.advance(); }
|
||||
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[parser][static-box] closing '}}' at token={:?}",
|
||||
self.current_token().token_type
|
||||
);
|
||||
}
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
if self.match_token(&TokenType::RBRACE) {
|
||||
self.consume(TokenType::RBRACE)?;
|
||||
} else if self.is_at_end() {
|
||||
// Safety valve: if EOF is reached right after members (common at file end),
|
||||
// accept as implicitly closed static box. This keeps behavior stable for
|
||||
// well-formed sources and avoids false negatives on seam edges.
|
||||
if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") {
|
||||
eprintln!("[parser][static-box] accepting EOF as closing '}}' (at file end)");
|
||||
}
|
||||
} else {
|
||||
// Still something else here; report a structured error
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "RBRACE".to_string(),
|
||||
found: self.current_token().token_type.clone(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
|
||||
// 🔥 Static初期化ブロックから依存関係を抽出
|
||||
if let Some(ref init_stmts) = static_init {
|
||||
|
||||
@ -63,13 +63,84 @@ impl NyashRunner {
|
||||
std::process::exit(1);
|
||||
}
|
||||
if use_ast {
|
||||
// Parse each prelude file into AST and store
|
||||
for p in paths {
|
||||
// Parse each prelude file into AST after stripping its own using-lines.
|
||||
// Recursively process nested preludes to avoid parse errors.
|
||||
let mut visited = std::collections::HashSet::<String>::new();
|
||||
// Normalize initial paths relative to filename or $NYASH_ROOT
|
||||
let mut stack: Vec<String> = Vec::new();
|
||||
for raw in paths {
|
||||
let mut pb = std::path::PathBuf::from(&raw);
|
||||
if pb.is_relative() {
|
||||
if let Some(dir) = std::path::Path::new(filename).parent() {
|
||||
let cand = dir.join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
}
|
||||
if pb.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
} else {
|
||||
// Fallback: resolve relative to project root guessed from the nyash binary path
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push(pb.to_string_lossy().to_string());
|
||||
}
|
||||
while let Some(mut p) = stack.pop() {
|
||||
// Normalize relative path against $NYASH_ROOT as a last resort
|
||||
if std::path::Path::new(&p).is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
p = cand.to_string_lossy().to_string();
|
||||
} else if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&p);
|
||||
p = cand.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
if !visited.insert(p.clone()) { continue; }
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(src) => {
|
||||
match NyashParser::parse_from_string(&src) {
|
||||
Ok(ast) => prelude_asts.push(ast),
|
||||
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); std::process::exit(1); }
|
||||
match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &src, &p) {
|
||||
Ok((clean_src, nested)) => {
|
||||
// Normalize and push nested first so they are parsed before the current file (DFS)
|
||||
for np in nested {
|
||||
let mut npp = std::path::PathBuf::from(&np);
|
||||
if npp.is_relative() {
|
||||
if let Some(dir) = std::path::Path::new(&p).parent() {
|
||||
let cand = dir.join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
}
|
||||
if npp.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
} else {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let nps = npp.to_string_lossy().to_string();
|
||||
if !visited.contains(&nps) { stack.push(nps); }
|
||||
}
|
||||
match NyashParser::parse_from_string(&clean_src) {
|
||||
Ok(ast) => prelude_asts.push(ast),
|
||||
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); std::process::exit(1); }
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("❌ {}", e); std::process::exit(1); }
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); std::process::exit(1); }
|
||||
|
||||
@ -43,7 +43,23 @@ pub fn collect_using_and_strip(
|
||||
let mut p = std::path::PathBuf::from(&path);
|
||||
if p.is_relative() {
|
||||
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
||||
// Also try NYASH_ROOT when available (repo-root relative like "apps/...")
|
||||
if p.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
} else {
|
||||
// Fallback: guess project root from executable path (target/release/nyash)
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbose { crate::runner::trace::log(format!("[using/resolve] file '{}' -> '{}'", target, p.display())); }
|
||||
prelude_paths.push(p.to_string_lossy().to_string());
|
||||
continue;
|
||||
}
|
||||
@ -103,7 +119,21 @@ pub fn collect_using_and_strip(
|
||||
let mut p = std::path::PathBuf::from(&value);
|
||||
if p.is_relative() {
|
||||
if let Some(dir) = ctx_dir { let cand = dir.join(&p); if cand.exists() { p = cand; } }
|
||||
if p.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
} else {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&p);
|
||||
if cand.exists() { p = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if verbose { crate::runner::trace::log(format!("[using/resolve] dev-file '{}' -> '{}'", value, p.display())); }
|
||||
prelude_paths.push(p.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
@ -174,4 +204,3 @@ pub fn preexpand_at_local(src: &str) -> String {
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
pub mod llvm;
|
||||
pub mod mir;
|
||||
pub mod vm_fallback;
|
||||
// vm module removed with vm-legacy
|
||||
pub mod pyvm;
|
||||
pub mod macro_child;
|
||||
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
use super::super::NyashRunner;
|
||||
use crate::{parser::NyashParser, mir::MirCompiler, backend::MirInterpreter};
|
||||
use crate::{
|
||||
backend::MirInterpreter,
|
||||
box_factory::{BoxFactory, RuntimeError},
|
||||
core::model::BoxDeclaration as CoreBoxDecl,
|
||||
instance_v2::InstanceBox,
|
||||
mir::MirCompiler,
|
||||
parser::NyashParser,
|
||||
};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
@ -25,11 +33,74 @@ impl NyashRunner {
|
||||
eprintln!("❌ using: AST prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines.");
|
||||
process::exit(1);
|
||||
}
|
||||
for p in paths {
|
||||
// Normalize initial prelude paths relative to filename or $NYASH_ROOT,
|
||||
// then recursively process prelude files: strip their using-lines and parse cleaned ASTs
|
||||
let mut visited = std::collections::HashSet::<String>::new();
|
||||
let mut stack: Vec<String> = Vec::new();
|
||||
for raw in paths {
|
||||
let mut pb = std::path::PathBuf::from(&raw);
|
||||
if pb.is_relative() {
|
||||
if let Some(dir) = std::path::Path::new(filename).parent() {
|
||||
let cand = dir.join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
}
|
||||
if pb.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
} else {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&pb);
|
||||
if cand.exists() { pb = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push(pb.to_string_lossy().to_string());
|
||||
}
|
||||
while let Some(mut p) = stack.pop() {
|
||||
if std::path::Path::new(&p).is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&p);
|
||||
p = cand.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
if !visited.insert(p.clone()) { continue; }
|
||||
match std::fs::read_to_string(&p) {
|
||||
Ok(src) => match NyashParser::parse_from_string(&src) {
|
||||
Ok(ast) => prelude_asts.push(ast),
|
||||
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); process::exit(1); }
|
||||
Ok(src) => match crate::runner::modes::common_util::resolve::collect_using_and_strip(self, &src, &p) {
|
||||
Ok((clean_src, nested)) => {
|
||||
for np in nested {
|
||||
let mut npp = std::path::PathBuf::from(&np);
|
||||
if npp.is_relative() {
|
||||
if let Some(dir) = std::path::Path::new(&p).parent() {
|
||||
let cand = dir.join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
}
|
||||
if npp.is_relative() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let cand = std::path::Path::new(&root).join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
} else {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(root) = exe.parent().and_then(|p| p.parent()).and_then(|p| p.parent()) {
|
||||
let cand = root.join(&npp);
|
||||
if cand.exists() { npp = cand; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let nps = npp.to_string_lossy().to_string();
|
||||
if !visited.contains(&nps) { stack.push(nps); }
|
||||
}
|
||||
match NyashParser::parse_from_string(&clean_src) {
|
||||
Ok(ast) => prelude_asts.push(ast),
|
||||
Err(e) => { eprintln!("❌ Parse error in using prelude {}: {}", p, e); process::exit(1); }
|
||||
}
|
||||
}
|
||||
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
||||
},
|
||||
Err(e) => { eprintln!("❌ Error reading using prelude {}: {}", p, e); process::exit(1); }
|
||||
}
|
||||
@ -82,6 +153,101 @@ impl NyashRunner {
|
||||
eprintln!("[ast] dump end");
|
||||
}
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
|
||||
|
||||
// Minimal user-defined Box support (Option A):
|
||||
// Collect BoxDeclaration entries from AST and register a lightweight
|
||||
// factory into the unified registry so `new UserBox()` works on the
|
||||
// VM fallback path as well.
|
||||
{
|
||||
use nyash_rust::ast::ASTNode;
|
||||
|
||||
// Collect user-defined (non-static) box declarations at program level.
|
||||
let mut decls: std::collections::HashMap<String, CoreBoxDecl> =
|
||||
std::collections::HashMap::new();
|
||||
if let ASTNode::Program { statements, .. } = &ast {
|
||||
for st in statements {
|
||||
if let ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
weak_fields,
|
||||
is_interface,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters,
|
||||
is_static,
|
||||
..
|
||||
} = st
|
||||
{
|
||||
if *is_static {
|
||||
continue; // modules/static boxes are not user-instantiable
|
||||
}
|
||||
let decl = CoreBoxDecl {
|
||||
name: name.clone(),
|
||||
fields: fields.clone(),
|
||||
public_fields: public_fields.clone(),
|
||||
private_fields: private_fields.clone(),
|
||||
methods: methods.clone(),
|
||||
constructors: constructors.clone(),
|
||||
init_fields: init_fields.clone(),
|
||||
weak_fields: weak_fields.clone(),
|
||||
is_interface: *is_interface,
|
||||
extends: extends.clone(),
|
||||
implements: implements.clone(),
|
||||
type_parameters: type_parameters.clone(),
|
||||
};
|
||||
decls.insert(name.clone(), decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !decls.is_empty() {
|
||||
// Inline factory: minimal User factory backed by collected declarations
|
||||
struct InlineUserBoxFactory {
|
||||
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
|
||||
}
|
||||
impl BoxFactory for InlineUserBoxFactory {
|
||||
fn create_box(
|
||||
&self,
|
||||
name: &str,
|
||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError> {
|
||||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
||||
let decl = match opt {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!("Unknown Box type: {}", name),
|
||||
})
|
||||
}
|
||||
};
|
||||
let mut inst = InstanceBox::from_declaration(
|
||||
decl.name.clone(),
|
||||
decl.fields.clone(),
|
||||
decl.methods.clone(),
|
||||
);
|
||||
let _ = inst.init(args);
|
||||
Ok(Box::new(inst))
|
||||
}
|
||||
|
||||
fn box_types(&self) -> Vec<&str> { vec![] }
|
||||
|
||||
fn is_available(&self) -> bool { true }
|
||||
|
||||
fn factory_type(
|
||||
&self,
|
||||
) -> crate::box_factory::FactoryType {
|
||||
crate::box_factory::FactoryType::User
|
||||
}
|
||||
}
|
||||
let factory = InlineUserBoxFactory { decls: Arc::new(RwLock::new(decls)) };
|
||||
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
|
||||
}
|
||||
}
|
||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile = match compiler.compile(ast) {
|
||||
Ok(c) => c,
|
||||
|
||||
Reference in New Issue
Block a user