diff --git a/src/backend/llvm/compiler/codegen/function.rs b/src/backend/llvm/compiler/codegen/function.rs new file mode 100644 index 00000000..1335a838 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/function.rs @@ -0,0 +1,424 @@ +use super::*; + +pub(super) fn lower_one_function<'ctx>( + codegen: &CodegenContext<'ctx>, + llvm_func: FunctionValue<'ctx>, + func: &crate::mir::function::MirFunction, + name: &str, + box_type_ids: &HashMap, + llvm_funcs: &HashMap>, +) -> Result<(), String> { + // Create basic blocks (prefix names with function label to avoid any ambiguity) + let fn_label = sanitize_symbol(name); + let (mut bb_map, entry_bb) = instructions::create_basic_blocks(codegen, llvm_func, func, &fn_label); + let mut cursor = instructions::builder_cursor::BuilderCursor::new(&codegen.builder); + cursor.at_end(func.entry_block, entry_bb); + let mut vmap: HashMap = HashMap::new(); + let mut allocas: HashMap = HashMap::new(); + let entry_builder = codegen.context.create_builder(); + entry_builder.position_at_end(entry_bb); + let mut alloca_elem_types: HashMap = HashMap::new(); + let mut phis_by_block: HashMap< + crate::mir::BasicBlockId, + Vec<(ValueId, PhiValue, Vec<(crate::mir::BasicBlockId, ValueId)>)>, + > = HashMap::new(); + // Snapshot of values at the end of each basic block (for sealed-SSA PHI wiring) + let mut block_end_values: HashMap> = HashMap::new(); + // Build successors and predecessors map (for optional sealed-SSA PHI wiring) + let mut succs: HashMap> = HashMap::new(); + for (bid, block) in &func.blocks { + let v: Vec = block.successors.iter().copied().collect(); + succs.insert(*bid, v); + } + let mut preds: HashMap> = HashMap::new(); + for (b, ss) in &succs { + for s in ss { preds.entry(*s).or_default().push(*b); } + } + // Track sealed blocks to know when all preds of a successor are sealed + let mut sealed_blocks: std::collections::HashSet = std::collections::HashSet::new(); + // Bind parameters + for (i, pid) in func.params.iter().enumerate() { + if let Some(av) = llvm_func.get_nth_param(i as u32) { + vmap.insert(*pid, av); + } + } + // Gather block order once for fallthrough handling + let block_ids: Vec = func.block_ids().into_iter().collect(); + + // Precreate phis + for bid in &block_ids { + let bb = *bb_map.get(bid).ok_or("missing bb in map")?; + codegen.builder.position_at_end(bb); + let block = func.blocks.get(bid).unwrap(); + for inst in block + .instructions + .iter() + .take_while(|i| matches!(i, MirInstruction::Phi { .. })) + { + if let MirInstruction::Phi { dst, inputs } = inst { + let mut phi_ty: Option = None; + if let Some(mt) = func.metadata.value_types.get(dst) { + phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); + } else if let Some((_, iv)) = inputs.first() { + if let Some(mt) = func.metadata.value_types.get(iv) { + phi_ty = Some(map_mirtype_to_basic(codegen.context, mt)); + } + } + let phi_ty = phi_ty.unwrap_or_else(|| codegen.context.i64_type().into()); + let phi = codegen + .builder + .build_phi(phi_ty, &format!("phi_{}", dst.as_u32())) + .map_err(|e| e.to_string())?; + vmap.insert(*dst, phi.as_basic_value()); + phis_by_block + .entry(*bid) + .or_default() + .push((*dst, phi, inputs.clone())); + if std::env::var("NYASH_LLVM_TRACE_PHI").ok().as_deref() == Some("1") { + let ty_str = phi + .as_basic_value() + .get_type() + .print_to_string() + .to_string(); + let mut pairs: Vec = Vec::new(); + for (pb, vid) in inputs { + pairs.push(format!("({}->{})", pb.as_u32(), vid.as_u32())); + } + eprintln!( + "[PHI:new] fn={} bb={} dst={} ty={} inputs={}", + fn_label, + bid.as_u32(), + dst.as_u32(), + ty_str, + pairs.join(",") + ); + } + } + } + } + + // Map of const strings for Call resolution + let const_strs = utils::build_const_str_map(func); + + // Lower body + let mut loopform_loop_id: u32 = 0; + // Default sealed-SSA ON unless explicitly disabled with NYASH_LLVM_PHI_SEALED=0 + let sealed_mode = std::env::var("NYASH_LLVM_PHI_SEALED").ok().as_deref() != Some("0"); + // LoopForm registry (per-function lowering; gated) + let mut loopform_registry: HashMap = HashMap::new(); + let mut loopform_body_to_header: HashMap = HashMap::new(); + // Per-function Resolver for dominance-safe value access (i64 minimal) + let mut resolver = instructions::Resolver::new(); + for (bi, bid) in block_ids.iter().enumerate() { + let bb = *bb_map.get(bid).unwrap(); + // Use cursor to position at BB start for lowering + cursor.at_end(*bid, bb); + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] lowering bb={}", bid.as_u32()); + } + let block = func.blocks.get(bid).unwrap(); + let mut defined_in_block: std::collections::HashSet = std::collections::HashSet::new(); + for inst in &block.instructions { + match inst { + MirInstruction::NewBox { dst, box_type, args } => { + instructions::lower_newbox( + codegen, + &mut cursor, + &mut resolver, + *bid, + &mut vmap, + *dst, + box_type, + args, + box_type_ids, + &bb_map, + &preds, + &block_end_values, + )?; + defined_in_block.insert(*dst); + }, + MirInstruction::Const { dst, value } => { + let bval = match value { + ConstValue::Integer(i) => codegen.context.i64_type().const_int(*i as u64, true).into(), + ConstValue::Float(f) => codegen.context.f64_type().const_float(*f).into(), + ConstValue::Bool(b) => codegen.context.bool_type().const_int(*b as u64, false).into(), + ConstValue::String(s) => { + // Hoist string creation to entry block to dominate all uses. + let entry_term = unsafe { entry_bb.get_terminator() }; + if let Some(t) = entry_term { entry_builder.position_before(&t); } + else { entry_builder.position_at_end(entry_bb); } + let gv = entry_builder + .build_global_string_ptr(s, "str") + .map_err(|e| e.to_string())?; + let len = codegen.context.i32_type().const_int(s.len() as u64, false); + let rt = codegen.context.ptr_type(inkwell::AddressSpace::from(0)); + let fn_ty = rt.fn_type(&[ + codegen.context.ptr_type(inkwell::AddressSpace::from(0)).into(), + codegen.context.i32_type().into(), + ], false); + let callee = codegen + .module + .get_function("nyash_string_new") + .unwrap_or_else(|| codegen.module.add_function("nyash_string_new", fn_ty, None)); + let call = entry_builder + .build_call(callee, &[gv.as_pointer_value().into(), len.into()], "strnew") + .map_err(|e| e.to_string())?; + call.try_as_basic_value().left().ok_or("nyash_string_new returned void".to_string())? + } + ConstValue::Null => codegen.context.ptr_type(inkwell::AddressSpace::from(0)).const_zero().into(), + ConstValue::Void => codegen.context.i64_type().const_zero().into(), + }; + vmap.insert(*dst, bval); + defined_in_block.insert(*dst); + }, + MirInstruction::Call { dst, func: callee, args, .. } => { + instructions::lower_call( + codegen, + &mut cursor, + &mut resolver, + *bid, + func, + &mut vmap, + dst, + callee, + args, + &const_strs, + llvm_funcs, + &bb_map, + &preds, + &block_end_values, + )?; + if let Some(d) = dst { defined_in_block.insert(*d); } + }, + MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects: _ } => { + instructions::lower_boxcall( + codegen, + &mut cursor, + &mut resolver, + *bid, + func, + &mut vmap, + dst, + box_val, + method, + method_id, + args, + box_type_ids, + &entry_builder, + &bb_map, + &preds, + &block_end_values, + )?; + if let Some(d) = dst { defined_in_block.insert(*d); } + }, + MirInstruction::ExternCall { dst, iface_name, method_name, args, effects: _ } => { + instructions::lower_externcall( + codegen, + &mut cursor, + &mut resolver, + *bid, + func, + &mut vmap, + dst, + iface_name, + method_name, + args, + &bb_map, + &preds, + &block_end_values, + )?; + if let Some(d) = dst { defined_in_block.insert(*d); } + }, + MirInstruction::UnaryOp { dst, op, operand } => { + instructions::lower_unary( + codegen, + &mut cursor, + &mut resolver, + *bid, + func, + &mut vmap, + *dst, + op, + operand, + &bb_map, + &preds, + &block_end_values, + )?; + defined_in_block.insert(*dst); + }, + MirInstruction::BinOp { dst, op, lhs, rhs } => { + instructions::lower_binop(codegen, &mut cursor, &mut resolver, *bid, func, &mut vmap, *dst, op, lhs, rhs, &bb_map, &preds, &block_end_values)?; + defined_in_block.insert(*dst); + }, + MirInstruction::Compare { dst, op, lhs, rhs } => { + let out = instructions::lower_compare(codegen, &mut cursor, &mut resolver, *bid, func, &vmap, op, lhs, rhs, &bb_map, &preds, &block_end_values)?; + vmap.insert(*dst, out); + defined_in_block.insert(*dst); + }, + MirInstruction::Store { value, ptr } => { + instructions::lower_store( + codegen, + &mut cursor, + &mut resolver, + *bid, + &vmap, + &mut allocas, + &mut alloca_elem_types, + value, + ptr, + &bb_map, + &preds, + &block_end_values, + )?; + }, + MirInstruction::Load { dst, ptr } => { + instructions::lower_load(codegen, &mut cursor, *bid, &mut vmap, &mut allocas, &mut alloca_elem_types, dst, ptr)?; + defined_in_block.insert(*dst); + }, + MirInstruction::Phi { .. } => { /* precreated */ } + _ => { /* ignore others */ } + } + // Snapshot end-of-block values + let mut snap: HashMap = HashMap::new(); + for vid in &defined_in_block { if let Some(v) = vmap.get(vid).copied() { snap.insert(*vid, v); } } + block_end_values.insert(*bid, snap); + } + // Terminator handling + if let Some(term) = &block.terminator { + cursor.at_end(*bid, bb); + match term { + MirInstruction::Return { value } => { + instructions::emit_return(codegen, &mut cursor, &mut resolver, *bid, func, &vmap, value, &bb_map, &preds, &block_end_values)?; + } + MirInstruction::Jump { target } => { + let mut handled = false; + if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") && + std::env::var("NYASH_LOOPFORM_BODY2DISPATCH").ok().as_deref() == Some("1") { + if let Some(hdr) = loopform_body_to_header.get(bid) { + if hdr == target { + if let Some((dispatch_bb, tag_phi, payload_phi, _latch_bb)) = loopform_registry.get(hdr) { + let i8t = codegen.context.i8_type(); + let i64t = codegen.context.i64_type(); + let pred_llbb = *bb_map.get(bid).ok_or("loopform: body llbb missing")?; + let z = i8t.const_zero(); + let pz = i64t.const_zero(); + tag_phi.add_incoming(&[(&z, pred_llbb)]); + payload_phi.add_incoming(&[(&pz, pred_llbb)]); + cursor.emit_term(*bid, |b| { b.build_unconditional_branch(*dispatch_bb).unwrap(); }); + handled = true; + } + } + } + } + if !handled { + instructions::emit_jump(codegen, &mut cursor, *bid, target, &bb_map, &phis_by_block)?; + } + } + MirInstruction::Branch { condition, then_bb, else_bb } => { + let mut handled_by_loopform = false; + if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") { + let mut is_back = |start: crate::mir::BasicBlockId| -> u8 { + if let Some(b) = func.blocks.get(&start) { + if let Some(crate::mir::instruction::MirInstruction::Jump { target }) = &b.terminator { + if target == bid { return 1; } + if let Some(b2) = func.blocks.get(target) { + if let Some(crate::mir::instruction::MirInstruction::Jump { target: t2 }) = &b2.terminator { + if t2 == bid { return 2; } + } + } + } + } + 0 + }; + let d_then = is_back(*then_bb); + let d_else = is_back(*else_bb); + let choose_body = if d_then > 0 && d_else == 0 { Some((*then_bb, *else_bb)) } + else if d_else > 0 && d_then == 0 { Some((*else_bb, *then_bb)) } + else if d_then > 0 && d_else > 0 { if d_then <= d_else { Some((*then_bb, *else_bb)) } else { Some((*else_bb, *then_bb)) } } + else { None }; + if let Some((body_sel, after_sel)) = choose_body { + let body_block = func.blocks.get(&body_sel).unwrap(); + handled_by_loopform = instructions::lower_while_loopform( + codegen, &mut cursor, &mut resolver, func, llvm_func, condition, &body_block.instructions, + loopform_loop_id, &fn_label, *bid, body_sel, after_sel, &bb_map, &vmap, &preds, &block_end_values, + &mut loopform_registry, &mut loopform_body_to_header, + )?; + loopform_loop_id = loopform_loop_id.wrapping_add(1); + } + } + if !handled_by_loopform { + instructions::emit_branch(codegen, &mut cursor, &mut resolver, *bid, condition, then_bb, else_bb, &bb_map, &phis_by_block, &vmap, &preds, &block_end_values)?; + } + } + _ => { + cursor.at_end(*bid, bb); + if let Some(next_bid) = block_ids.get(bi + 1) { + instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; + } else { + let entry_first = func.entry_block; + instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; + } + } + } + } else { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] no terminator in MIR for bb={} (fallback)", bid.as_u32()); + } + cursor.at_end(*bid, bb); + if let Some(next_bid) = block_ids.get(bi + 1) { + instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; + } else { + let entry_first = func.entry_block; + instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; + } + } + if unsafe { bb.get_terminator() }.is_none() { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[LLVM] extra guard inserting fallback for bb={}", bid.as_u32()); + } + cursor.at_end(*bid, bb); + if let Some(next_bid) = block_ids.get(bi + 1) { + instructions::emit_jump(codegen, &mut cursor, *bid, next_bid, &bb_map, &phis_by_block)?; + } else { + let entry_first = func.entry_block; + instructions::emit_jump(codegen, &mut cursor, *bid, &entry_first, &bb_map, &phis_by_block)?; + } + } + if sealed_mode { + instructions::flow::seal_block(codegen, &mut cursor, func, *bid, &succs, &bb_map, &phis_by_block, &block_end_values)?; + sealed_blocks.insert(*bid); + } + } + if std::env::var("NYASH_ENABLE_LOOPFORM").ok().as_deref() == Some("1") && + std::env::var("NYASH_LOOPFORM_LATCH2HEADER").ok().as_deref() == Some("1") { + for (hdr_bid, (_dispatch_bb, _tag_phi, _payload_phi, latch_bb)) in &loopform_registry { + if let Some(phis) = phis_by_block.get(hdr_bid) { + instructions::normalize_header_phis_for_latch( + codegen, + *hdr_bid, + *latch_bb, + phis, + )?; + } + } + instructions::dev_check_dispatch_only_phi(&phis_by_block, &loopform_registry); + } + for bb in llvm_func.get_basic_blocks() { + if unsafe { bb.get_terminator() }.is_none() { + codegen.builder.position_at_end(bb); + let _ = codegen.builder.build_unreachable(); + } + } + if !llvm_func.verify(true) { + if std::env::var("NYASH_LLVM_DUMP_ON_FAIL").ok().as_deref() == Some("1") { + let ir = codegen.module.print_to_string().to_string(); + let dump_dir = std::path::Path::new("tmp"); + let _ = std::fs::create_dir_all(dump_dir); + let dump_path = dump_dir.join(format!("llvm_fail_{}.ll", sanitize_symbol(name))); + let _ = std::fs::write(&dump_path, ir); + eprintln!("[LLVM] wrote IR dump: {}", dump_path.display()); + } + return Err(format!("Function verification failed: {}", name)); + } + Ok(()) +} diff --git a/src/backend/llvm/compiler/codegen/mod.rs b/src/backend/llvm/compiler/codegen/mod.rs index 845d114e..dad91d91 100644 --- a/src/backend/llvm/compiler/codegen/mod.rs +++ b/src/backend/llvm/compiler/codegen/mod.rs @@ -18,32 +18,19 @@ use self::types::{ classify_tag, cmp_eq_ne_any, i64_to_ptr, map_mirtype_to_basic, to_bool, to_i64_any, }; mod instructions; +mod utils; +mod function; // --- Local helpers (refactor to keep compile_module slim) --- -fn sanitize_symbol(name: &str) -> String { - name.chars() - .map(|c| match c { '.' | '/' | '-' => '_', other => other }) - .collect() -} +// Moved to utils.rs +fn sanitize_symbol(name: &str) -> String { utils::sanitize_symbol(name) } -fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap { - let mut m = HashMap::new(); - for bid in f.block_ids() { - if let Some(b) = f.blocks.get(&bid) { - for inst in &b.instructions { - if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst { - m.insert(*dst, s.clone()); - } - } - if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator { - m.insert(*dst, s.clone()); - } - } - } - m -} +// Moved to utils.rs +fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap { utils::build_const_str_map(f) } +// moved: lower_one_function is implemented in function.rs +#[cfg(any())] fn lower_one_function<'ctx>( codegen: &CodegenContext<'ctx>, llvm_func: FunctionValue<'ctx>, @@ -142,7 +129,7 @@ fn lower_one_function<'ctx>( } // Map of const strings for Call resolution - let const_strs = build_const_str_map(func); + let const_strs = utils::build_const_str_map(func); // Lower body let mut loopform_loop_id: u32 = 0; @@ -572,7 +559,7 @@ impl LLVMCompiler { BasicTypeEnum::PointerType(t) => t.fn_type(¶ms_bt.iter().map(|t| (*t).into()).collect::>(), false), _ => return Err("Unsupported return basic type".to_string()), }; - let sym = format!("ny_f_{}", sanitize_symbol(name)); + let sym = format!("ny_f_{}", utils::sanitize_symbol(name)); let lf = codegen.module.add_function(&sym, ll_fn_ty, None); llvm_funcs.insert(name.clone(), lf); } @@ -580,7 +567,7 @@ impl LLVMCompiler { // Lower all functions for (name, func) in &mir_module.functions { let llvm_func = *llvm_funcs.get(name).ok_or("predecl not found")?; - lower_one_function(&codegen, llvm_func, func, name, &box_type_ids, &llvm_funcs)?; + function::lower_one_function(&codegen, llvm_func, func, name, &box_type_ids, &llvm_funcs)?; } // Build entry wrapper and emit object diff --git a/src/backend/llvm/compiler/codegen/utils.rs b/src/backend/llvm/compiler/codegen/utils.rs new file mode 100644 index 00000000..1c004290 --- /dev/null +++ b/src/backend/llvm/compiler/codegen/utils.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; +use crate::mir::ValueId; +use crate::mir::instruction::{MirInstruction, ConstValue}; + +pub(super) fn sanitize_symbol(name: &str) -> String { + name.chars() + .map(|c| match c { '.' | '/' | '-' => '_', other => other }) + .collect() +} + +pub(super) fn build_const_str_map(f: &crate::mir::function::MirFunction) -> HashMap { + let mut m = HashMap::new(); + for bid in f.block_ids() { + if let Some(b) = f.blocks.get(&bid) { + for inst in &b.instructions { + if let MirInstruction::Const { dst, value: ConstValue::String(s) } = inst { + m.insert(*dst, s.clone()); + } + } + if let Some(MirInstruction::Const { dst, value: ConstValue::String(s) }) = &b.terminator { + m.insert(*dst, s.clone()); + } + } + } + m +} + diff --git a/src/mir/builder.rs b/src/mir/builder.rs index d21e78b5..17d1b826 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -16,6 +16,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::fs; mod builder_calls; +mod phi; mod stmts; mod ops; mod utils; @@ -28,6 +29,7 @@ mod exprs_peek; // peek expression mod exprs_lambda; // lambda lowering mod exprs_include; // include lowering mod plugin_sigs; // plugin signature loader +mod vars; // variables/scope helpers // moved helpers to builder/utils.rs @@ -284,12 +286,12 @@ impl MirBuilder { if let super::MirInstruction::Return { value: Some(v) } = inst { if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break 'outer; } // 追加: v が PHI の場合は入力側の型から推定 - if let Some(mt) = utils::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break 'outer; } + if let Some(mt) = phi::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break 'outer; } } } if let Some(super::MirInstruction::Return { value: Some(v) }) = &bb.terminator { if let Some(mt) = self.value_types.get(v).cloned() { inferred = Some(mt); break; } - if let Some(mt) = utils::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break; } + if let Some(mt) = phi::infer_type_from_phi(&function, *v, &self.value_types) { inferred = Some(mt); break; } } } if let Some(mt) = inferred { function.signature.return_type = mt; } @@ -524,52 +526,7 @@ impl MirBuilder { let mut used: HashSet = HashSet::new(); let mut locals: HashSet = HashSet::new(); for p in params.iter() { locals.insert(p.clone()); } - fn collect_vars(node: &crate::ast::ASTNode, used: &mut HashSet, locals: &mut HashSet) { - match node { - crate::ast::ASTNode::Variable { name, .. } => { - if name != "me" && name != "this" && !locals.contains(name) { - used.insert(name.clone()); - } - } - crate::ast::ASTNode::Local { variables, .. } => { for v in variables { locals.insert(v.clone()); } } - crate::ast::ASTNode::Assignment { target, value, .. } => { collect_vars(target, used, locals); collect_vars(value, used, locals); } - crate::ast::ASTNode::BinaryOp { left, right, .. } => { collect_vars(left, used, locals); collect_vars(right, used, locals); } - crate::ast::ASTNode::UnaryOp { operand, .. } => { collect_vars(operand, used, locals); } - crate::ast::ASTNode::MethodCall { object, arguments, .. } => { collect_vars(object, used, locals); for a in arguments { collect_vars(a, used, locals); } } - crate::ast::ASTNode::FunctionCall { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } } - crate::ast::ASTNode::Call { callee, arguments, .. } => { collect_vars(callee, used, locals); for a in arguments { collect_vars(a, used, locals); } } - crate::ast::ASTNode::FieldAccess { object, .. } => { collect_vars(object, used, locals); } - crate::ast::ASTNode::New { arguments, .. } => { for a in arguments { collect_vars(a, used, locals); } } - crate::ast::ASTNode::If { condition, then_body, else_body, .. } => { - collect_vars(condition, used, locals); - for st in then_body { collect_vars(st, used, locals); } - if let Some(eb) = else_body { for st in eb { collect_vars(st, used, locals); } } - } - crate::ast::ASTNode::Loop { condition, body, .. } => { collect_vars(condition, used, locals); for st in body { collect_vars(st, used, locals); } } - crate::ast::ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { - for st in try_body { collect_vars(st, used, locals); } - for c in catch_clauses { for st in &c.body { collect_vars(st, used, locals); } } - if let Some(fb) = finally_body { for st in fb { collect_vars(st, used, locals); } } - } - crate::ast::ASTNode::Throw { expression, .. } => { collect_vars(expression, used, locals); } - crate::ast::ASTNode::Print { expression, .. } => { collect_vars(expression, used, locals); } - crate::ast::ASTNode::Return { value, .. } => { if let Some(v) = value { collect_vars(v, used, locals); } } - crate::ast::ASTNode::AwaitExpression { expression, .. } => { collect_vars(expression, used, locals); } - crate::ast::ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => { - collect_vars(scrutinee, used, locals); - for (_, e) in arms { collect_vars(e, used, locals); } - collect_vars(else_expr, used, locals); - } - crate::ast::ASTNode::Program { statements, .. } => { for st in statements { collect_vars(st, used, locals); } } - crate::ast::ASTNode::FunctionDeclaration { params, body, .. } => { - let mut inner = locals.clone(); - for p in params { inner.insert(p.clone()); } - for st in body { collect_vars(st, used, &mut inner); } - } - _ => {} - } - } - for st in body.iter() { collect_vars(st, &mut used, &mut locals); } + for st in body.iter() { vars::collect_free_vars(st, &mut used, &mut locals); } // Materialize captures from current variable_map if known let mut captures: Vec<(String, ValueId)> = Vec::new(); for name in used.into_iter() { diff --git a/src/mir/builder/phi.rs b/src/mir/builder/phi.rs new file mode 100644 index 00000000..d39dfdae --- /dev/null +++ b/src/mir/builder/phi.rs @@ -0,0 +1,99 @@ + +use crate::mir::{MirFunction, ValueId, MirType, MirInstruction, BasicBlockId}; +use std::collections::HashMap; +use crate::ast::ASTNode; +use super::MirBuilder; + +// PHI-based return type inference helper +pub(super) fn infer_type_from_phi( + function: &MirFunction, + ret_val: ValueId, + types: &HashMap, +) -> Option { + for (_bid, bb) in function.blocks.iter() { + for inst in bb.instructions.iter() { + if let MirInstruction::Phi { dst, inputs } = inst { + if *dst == ret_val { + let mut it = inputs.iter().filter_map(|(_, v)| types.get(v)); + if let Some(first) = it.next() { + if it.all(|mt| mt == first) { + return Some(first.clone()); + } + } + } + } + } + } + None +} + +// Local helper for if-statement analysis (moved from stmts.rs) +pub(super) fn extract_assigned_var(ast: &ASTNode) -> Option { + match ast { + ASTNode::Assignment { target, .. } => { + if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None } + } + ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var(st)), + ASTNode::If { then_body, else_body, .. } => { + // Look into nested if: if both sides assign the same variable, propagate that name upward. + let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() }; + let tvar = extract_assigned_var(&then_prog); + let evar = else_body.as_ref().and_then(|eb| { + let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() }; + extract_assigned_var(&ep) + }); + match (tvar, evar) { + (Some(tv), Some(ev)) if tv == ev => Some(tv), + _ => None, + } + } + _ => None, + } +} + +impl MirBuilder { + /// Normalize Phi creation for if/else constructs. + /// This handles variable reassignment patterns and ensures a single exit value. + pub(super) fn normalize_if_else_phi( + &mut self, + then_block: BasicBlockId, + else_block: BasicBlockId, + then_value_raw: ValueId, + else_value_raw: ValueId, + pre_if_var_map: &HashMap, + then_ast_for_analysis: &ASTNode, + else_ast_for_analysis: &Option, + then_var_map_end: &HashMap, + else_var_map_end_opt: &Option>, + pre_then_var_value: Option, + ) -> Result { + // If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else + // does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value). + let assigned_var_then = extract_assigned_var(then_ast_for_analysis); + let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a)); + let result_val = self.value_gen.next(); + + if let Some(var_name) = assigned_var_then.clone() { + let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false); + // Resolve branch-end values for the assigned variable + let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw); + let else_value_for_var = if else_assigns_same { + else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).unwrap_or(else_value_raw) + } else { + // Else doesn't assign: use pre-if value if available + pre_then_var_value.unwrap_or(else_value_raw) + }; + // Emit Phi for the assigned variable and bind it + self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_for_var), (else_block, else_value_for_var)] })?; + self.variable_map = pre_if_var_map.clone(); + self.variable_map.insert(var_name, result_val); + } else { + // No variable assignment pattern detected – just emit Phi for expression result + self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_raw), (else_block, else_value_raw)] })?; + // Merge variable map conservatively to pre-if snapshot (no new bindings) + self.variable_map = pre_if_var_map.clone(); + } + + Ok(result_val) + } +} diff --git a/src/mir/builder/stmts.rs b/src/mir/builder/stmts.rs index 3268c3aa..aa2e27c5 100644 --- a/src/mir/builder/stmts.rs +++ b/src/mir/builder/stmts.rs @@ -2,6 +2,7 @@ use super::{MirInstruction, EffectMask, Effect, ConstValue, ValueId}; use crate::mir::TypeOpKind; use crate::mir::loop_builder::LoopBuilder; use crate::ast::ASTNode; +use super::phi::extract_assigned_var; impl super::MirBuilder { // Print statement: env.console.log(value) with early TypeOp handling @@ -140,31 +141,18 @@ impl super::MirBuilder { // merge + phi self.current_block = Some(merge_block); self.ensure_block_exists(merge_block)?; - // If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else - // does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value). - let assigned_var_then = extract_assigned_var(&then_ast_for_analysis); - let assigned_var_else = else_ast_for_analysis.as_ref().and_then(|a| extract_assigned_var(a)); - let result_val = self.value_gen.next(); - if let Some(var_name) = assigned_var_then.clone() { - let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false); - // Resolve branch-end values for the assigned variable - let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw); - let else_value_for_var = if else_assigns_same { - else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).unwrap_or(else_value_raw) - } else { - // Else doesn't assign: use pre-if value if available - pre_then_var_value.unwrap_or(else_value_raw) - }; - // Emit Phi for the assigned variable and bind it - self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_for_var), (else_block, else_value_for_var)] })?; - self.variable_map = pre_if_var_map.clone(); - self.variable_map.insert(var_name, result_val); - } else { - // No variable assignment pattern detected – just emit Phi for expression result - self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_raw), (else_block, else_value_raw)] })?; - // Merge variable map conservatively to pre-if snapshot (no new bindings) - self.variable_map = pre_if_var_map.clone(); - } + let result_val = self.normalize_if_else_phi( + then_block, + else_block, + then_value_raw, + else_value_raw, + &pre_if_var_map, + &then_ast_for_analysis, + &else_ast_for_analysis, + &then_var_map_end, + &else_var_map_end_opt, + pre_then_var_value, + )?; Ok(result_val) } @@ -328,26 +316,4 @@ impl super::MirBuilder { } } -// Local helper for if-statement analysis -fn extract_assigned_var(ast: &ASTNode) -> Option { - match ast { - ASTNode::Assignment { target, .. } => { - if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None } - } - ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var(st)), - ASTNode::If { then_body, else_body, .. } => { - // Look into nested if: if both sides assign the same variable, propagate that name upward. - let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() }; - let tvar = extract_assigned_var(&then_prog); - let evar = else_body.as_ref().and_then(|eb| { - let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() }; - extract_assigned_var(&ep) - }); - match (tvar, evar) { - (Some(tv), Some(ev)) if tv == ev => Some(tv), - _ => None, - } - } - _ => None, - } -} + diff --git a/src/mir/builder/utils.rs b/src/mir/builder/utils.rs index 1e96b66d..39676745 100644 --- a/src/mir/builder/utils.rs +++ b/src/mir/builder/utils.rs @@ -41,28 +41,7 @@ pub(super) fn builder_debug_log(msg: &str) { } } -// PHI-based return type inference helper -pub(super) fn infer_type_from_phi( - function: &super::MirFunction, - ret_val: super::ValueId, - types: &std::collections::HashMap, -) -> Option { - for (_bid, bb) in function.blocks.iter() { - for inst in bb.instructions.iter() { - if let super::MirInstruction::Phi { dst, inputs } = inst { - if *dst == ret_val { - let mut it = inputs.iter().filter_map(|(_, v)| types.get(v)); - if let Some(first) = it.next() { - if it.all(|mt| mt == first) { - return Some(first.clone()); - } - } - } - } - } - } - None -} + // Lightweight helpers moved from builder.rs to reduce file size impl super::MirBuilder { diff --git a/src/mir/builder/vars.rs b/src/mir/builder/vars.rs new file mode 100644 index 00000000..10b7e0e9 --- /dev/null +++ b/src/mir/builder/vars.rs @@ -0,0 +1,74 @@ +use crate::ast::ASTNode; +use std::collections::HashSet; + +/// Collect free variables used in `node` into `used`, excluding names present in `locals`. +/// `locals` is updated as new local declarations are encountered. +pub(super) fn collect_free_vars(node: &ASTNode, used: &mut HashSet, locals: &mut HashSet) { + match node { + ASTNode::Variable { name, .. } => { + if name != "me" && name != "this" && !locals.contains(name) { + used.insert(name.clone()); + } + } + ASTNode::Local { variables, .. } => { + for v in variables { locals.insert(v.clone()); } + } + ASTNode::Assignment { target, value, .. } => { + collect_free_vars(target, used, locals); + collect_free_vars(value, used, locals); + } + ASTNode::BinaryOp { left, right, .. } => { + collect_free_vars(left, used, locals); + collect_free_vars(right, used, locals); + } + ASTNode::UnaryOp { operand, .. } => { collect_free_vars(operand, used, locals); } + ASTNode::MethodCall { object, arguments, .. } => { + collect_free_vars(object, used, locals); + for a in arguments { collect_free_vars(a, used, locals); } + } + ASTNode::FunctionCall { arguments, .. } => { + for a in arguments { collect_free_vars(a, used, locals); } + } + ASTNode::Call { callee, arguments, .. } => { + collect_free_vars(callee, used, locals); + for a in arguments { collect_free_vars(a, used, locals); } + } + ASTNode::FieldAccess { object, .. } => { collect_free_vars(object, used, locals); } + ASTNode::New { arguments, .. } => { + for a in arguments { collect_free_vars(a, used, locals); } + } + ASTNode::If { condition, then_body, else_body, .. } => { + collect_free_vars(condition, used, locals); + for st in then_body { collect_free_vars(st, used, locals); } + if let Some(eb) = else_body { for st in eb { collect_free_vars(st, used, locals); } } + } + ASTNode::Loop { condition, body, .. } => { + collect_free_vars(condition, used, locals); + for st in body { collect_free_vars(st, used, locals); } + } + ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + for st in try_body { collect_free_vars(st, used, locals); } + for c in catch_clauses { for st in &c.body { collect_free_vars(st, used, locals); } } + if let Some(fb) = finally_body { for st in fb { collect_free_vars(st, used, locals); } } + } + ASTNode::Throw { expression, .. } => { collect_free_vars(expression, used, locals); } + ASTNode::Print { expression, .. } => { collect_free_vars(expression, used, locals); } + ASTNode::Return { value, .. } => { if let Some(v) = value { collect_free_vars(v, used, locals); } } + ASTNode::AwaitExpression { expression, .. } => { collect_free_vars(expression, used, locals); } + ASTNode::PeekExpr { scrutinee, arms, else_expr, .. } => { + collect_free_vars(scrutinee, used, locals); + for (_, e) in arms { collect_free_vars(e, used, locals); } + collect_free_vars(else_expr, used, locals); + } + ASTNode::Program { statements, .. } => { + for st in statements { collect_free_vars(st, used, locals); } + } + ASTNode::FunctionDeclaration { params, body, .. } => { + let mut inner = locals.clone(); + for p in params { inner.insert(p.clone()); } + for st in body { collect_free_vars(st, used, &mut inner); } + } + _ => {} + } +} + diff --git a/src/runner/build.rs b/src/runner/build.rs new file mode 100644 index 00000000..d5a43366 --- /dev/null +++ b/src/runner/build.rs @@ -0,0 +1,140 @@ +use super::NyashRunner; +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) }; + // 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::(&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); } } + } + // 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 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 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]); } + cmd.current_dir(&p); + println!("[build] plugin {} at {}", name, p.display()); + 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())); + } + } + } + } + // 4) Build nyash core (features) + { + 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()); } + } + // 5) Determine app entry + 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() } + }; + let app = if !app.is_empty() { app } else { + // collect candidates under apps/**/main.nyash + let mut cand: Vec = Vec::new(); + fn walk(dir: &Path, acc: &mut Vec) { + 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) { + acc.push(p.display().to_string()); + } + } + } + } + walk(&cwd.join("apps"), &mut cand); + 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 - ")) + }; + return Err(msg); + }; + // 6) Emit object + let obj_dir = cwd.join("target").join("aot_objects"); + 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()); + } + 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()); } + } 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()); } + } + if !obj_path.exists() { + // In Cranelift path we produce target/aot_objects/.o; fall back to main.o default + 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") } }; + // 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())]) + .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(()); } + } + 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()) + .arg("-lntdll") + .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()); + } + #[cfg(not(windows))] + { + 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(["-o", &out_path.display().to_string()]) + .status().map_err(|e| format!("spawn cc: {}", e))?; + if !status.success() { return Err("link failed (cc)".into()); } + } + println!("✅ Success: {}", out_path.display()); + Ok(()) +} + diff --git a/src/runner/demos.rs b/src/runner/demos.rs index 12637ae3..78c87349 100644 --- a/src/runner/demos.rs +++ b/src/runner/demos.rs @@ -134,3 +134,18 @@ pub(super) fn demo_interpreter_system() { Err(e) => println!(" ❌ Parse error: {}", e), } } + +/// Run all demo sections (moved from runner/mod.rs) +pub(super) fn run_all_demos() { + println!("🦀 Nyash Rust Implementation - Everything is Box! 🦀"); + println!("===================================================="); + demo_basic_boxes(); + demo_box_operations(); + demo_box_collections(); + demo_environment_system(); + demo_tokenizer_system(); + demo_parser_system(); + demo_interpreter_system(); + println!("\n🎉 All Box operations completed successfully!"); + println!("Memory safety guaranteed by Rust's borrow checker! 🛡️"); +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index d92a351d..cb12026d 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -33,6 +33,8 @@ mod json_v0_bridge; mod mir_json_emit; mod pipe_io; mod pipeline; +mod tasks; +mod build; mod dispatch; mod selfhost; @@ -43,35 +45,7 @@ use std::path::PathBuf; /// Resolve a using target according to priority: modules > relative > using-paths /// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error. -fn resolve_using_target( - tgt: &str, - is_path: bool, - modules: &[(String, String)], - using_paths: &[String], - context_dir: Option<&std::path::Path>, - strict: bool, - verbose: bool, -) -> Result { - if is_path { return Ok(tgt.to_string()); } - // 1) modules mapping - if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); } - // 2) build candidate list: relative then using-paths - let rel = tgt.replace('.', "/") + ".nyash"; - let mut cand: Vec = Vec::new(); - 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 cand.is_empty() { - if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); } - return Ok(tgt.to_string()); - } - if cand.len() > 1 && strict { - return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", "))); - } - Ok(cand.remove(0)) -} +use pipeline::resolve_using_target; /// Main execution coordinator pub struct NyashRunner { @@ -79,30 +53,7 @@ pub struct NyashRunner { } /// Minimal task runner: read nyash.toml [env] and [tasks], run the named task via shell -fn run_named_task(name: &str) -> Result<(), String> { - let cfg_path = "nyash.toml"; - let text = fs::read_to_string(cfg_path).map_err(|e| format!("read {}: {}", cfg_path, e))?; - let doc = toml::from_str::(&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); } - } - } - // 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))?; - // Basic variable substitution - 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())?; - #[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())); } - Ok(()) -} +use tasks::run_named_task; impl NyashRunner { /// Create a new runner with the given configuration @@ -392,7 +343,7 @@ impl NyashRunner { // Delegate file-mode execution to modes::common dispatcher self.run_file(filename); } else { - self.execute_demo_mode(); + demos::run_all_demos(); } } @@ -400,20 +351,7 @@ impl NyashRunner { /// Execute file-based mode with backend selection - /// Execute demo mode with all demonstrations - fn execute_demo_mode(&self) { - println!("🦀 Nyash Rust Implementation - Everything is Box! 🦀"); - println!("===================================================="); - demos::demo_basic_boxes(); - demos::demo_box_operations(); - demos::demo_box_collections(); - demos::demo_environment_system(); - demos::demo_tokenizer_system(); - demos::demo_parser_system(); - demos::demo_interpreter_system(); - println!("\n🎉 All Box operations completed successfully!"); - println!("Memory safety guaranteed by Rust's borrow checker! 🛡️"); - } + // demo runner moved to runner/demos.rs /// Execute Nyash file with interpreter (moved to modes/common.rs) #[cfg(any())] @@ -838,143 +776,9 @@ impl NyashRunner { // execute_mir_module moved to runner/dispatch.rs - /// Minimal AOT build pipeline driven by nyash.toml (MVP, single-platform, best-effort) + /// Minimal AOT build pipeline driven by nyash.toml (mvp) fn run_build_mvp(&self, cfg_path: &str) -> Result<(), String> { - use std::path::{Path, PathBuf}; - 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) }; - // 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::(&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); } } - } - // Derive options - let profile = self.config.build_profile.clone().unwrap_or_else(|| "release".into()); - let aot = self.config.build_aot.clone().unwrap_or_else(|| "cranelift".into()); - let out = self.config.build_out.clone(); - let target = self.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 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]); } - cmd.current_dir(&p); - println!("[build] plugin {} at {}", name, p.display()); - 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())); - } - } - } - } - // 4) Build nyash core (features) - { - 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()); } - } - // 5) Determine app entry - let app = if let Some(a) = self.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() } - }; - let app = if !app.is_empty() { app } else { - // collect candidates under apps/**/main.nyash - let mut cand: Vec = Vec::new(); - fn walk(dir: &Path, acc: &mut Vec) { - 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) { - acc.push(p.display().to_string()); - } - } - } - } - walk(&cwd.join("apps"), &mut cand); - 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 - ")) - }; - return Err(msg); - }; - // 6) Emit object - let obj_dir = cwd.join("target").join("aot_objects"); - 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()); - } - 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()); } - } 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()); } - } - if !obj_path.exists() { - // In Cranelift path we produce target/aot_objects/.o; fall back to main.o default - 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") } }; - // 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())]) - .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(()); } - } - 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()) - .arg("-lntdll") - .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()); - } - #[cfg(not(windows))] - { - 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(["-o", &out_path.display().to_string()]) - .status().map_err(|e| format!("spawn cc: {}", e))?; - if !status.success() { return Err("link failed (cc)".into()); } - } - println!("✅ Success: {}", out_path.display()); - Ok(()) + build::run_build_mvp_impl(self, cfg_path) } } diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs index 60418821..a7b2c8d2 100644 --- a/src/runner/pipeline.rs +++ b/src/runner/pipeline.rs @@ -99,3 +99,35 @@ pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec) { let p = std::path::Path::new(base); walk(p, leaf, out, 4); } + +/// Resolve a using target according to priority: modules > relative > using-paths +/// Returns Ok(resolved_path_or_token). On strict mode, ambiguous matches cause error. +pub(super) fn resolve_using_target( + tgt: &str, + is_path: bool, + modules: &[(String, String)], + using_paths: &[String], + context_dir: Option<&std::path::Path>, + strict: bool, + verbose: bool, +) -> Result { + if is_path { return Ok(tgt.to_string()); } + // 1) modules mapping + if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) { return Ok(p.clone()); } + // 2) build candidate list: relative then using-paths + let rel = tgt.replace('.', "/") + ".nyash"; + let mut cand: Vec = Vec::new(); + 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 cand.is_empty() { + if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); } + return Ok(tgt.to_string()); + } + if cand.len() > 1 && strict { + return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", "))); + } + Ok(cand.remove(0)) +} diff --git a/src/runner/tasks.rs b/src/runner/tasks.rs new file mode 100644 index 00000000..ef4d03e5 --- /dev/null +++ b/src/runner/tasks.rs @@ -0,0 +1,28 @@ +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::(&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); } + } + } + // 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))?; + // Basic variable substitution + 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())?; + #[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())); } + Ok(()) +} +