From e2279f214bd2734c8d5b6cb70f52e61081508d33 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 20 Feb 2026 12:14:22 +0100 Subject: [PATCH] Refactor lambda binding and parameter handling Introduce `BoundKind::Parameter` to represent function parameters. Modify `Binder` to correctly handle parameters within lambda definitions, ensuring they are only defined within function scopes. Update `LambdaCollector` to register the body of lambdas as templates for global definitions. Adjust `Dumper` to accurately represent lambda parameters. Update `Specializer` and `TCO` to handle the new `BoundKind::Parameter`. Refactor `Call` and `TailCall` to use a single `args` node, often a tuple. Adjust type signatures in RTL to use `StaticType::Tuple` for function parameters. --- src/ast/compiler/binder.rs | 37 +++++---- src/ast/compiler/bound_nodes.rs | 59 ++++++++----- src/ast/compiler/dumper.rs | 35 ++++++-- src/ast/compiler/lambda_collector.rs | 18 ++-- src/ast/compiler/macros.rs | 75 ++++++++++------- src/ast/compiler/specializer.rs | 67 ++++++++++----- src/ast/compiler/tco.rs | 6 +- src/ast/compiler/type_checker.rs | 91 +++++++++++++------- src/ast/compiler/upvalues.rs | 33 ++++++-- src/ast/nodes.rs | 7 +- src/ast/parser.rs | 53 ++++++++---- src/ast/rtl/core.rs | 50 +++++------ src/ast/rtl/datetime.rs | 2 +- src/ast/rtl/type_registry.rs | 2 +- src/ast/types.rs | 24 ++---- src/ast/vm.rs | 65 +++++++++++++-- src/utils/tester.rs | 120 ++++++++++++++++++++------- 17 files changed, 497 insertions(+), 247 deletions(-) diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 87d8251..3337e04 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -110,6 +110,19 @@ impl Binder { })) }, + UntypedKind::Parameter(sym) => { + // Parameters are ONLY allowed if we are inside a function (Binder stack > 1) + if self.functions.len() <= 1 { + return Err(format!("Parameter '{}' is not allowed in root scope.", sym.name)); + } + let current_fn = self.functions.last_mut().unwrap(); + let slot = current_fn.scope.define(sym)?; + Ok(self.make_node(node.identity.clone(), BoundKind::Parameter { + name: sym.clone(), + slot + })) + }, + UntypedKind::If { cond, then_br, else_br } => { let cond = self.bind(cond)?; let then_br = self.bind(then_br)?; @@ -176,20 +189,19 @@ impl Binder { }, UntypedKind::Lambda { params, body } => { + let identity = node.identity.clone(); self.functions.push(FunctionCompiler::new()); - { - let current_fn = self.functions.last_mut().unwrap(); - for param in params { - current_fn.scope.define(param)?; - } - } - + // 1. Bind the parameter pattern/tuple + let params_bound = self.bind(params)?; + + // 2. Bind the body let body_bound = self.bind(body)?; + let compiled_fn = self.functions.pop().unwrap(); - Ok(self.make_node(node.identity.clone(), BoundKind::Lambda { - param_count: params.len() as u32, + Ok(self.make_node(identity, BoundKind::Lambda { + params: Box::new(params_bound), upvalues: compiled_fn.upvalues, body: Rc::new(body_bound), })) @@ -197,14 +209,11 @@ impl Binder { UntypedKind::Call { callee, args } => { let callee = self.bind(callee)?; - let mut bound_args = Vec::new(); - for arg in args { - bound_args.push(self.bind(arg)?); - } + let args = self.bind(args)?; Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee: Box::new(callee), - args: bound_args + args: Box::new(args) })) }, diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 0309b5b..7bb999f 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -21,11 +21,23 @@ impl Clone for Box> { } } +/// Type alias for a node that has been bound. Defaults to untyped (T = ()). +pub type BoundNode = Node, T>; + +/// Type alias for a node that has been fully type-checked. +pub type TypedNode = BoundNode; + #[derive(Debug, Clone)] pub enum BoundKind { Nop, Constant(Value), + /// A positional parameter in a Lambda's parameter list. + Parameter { + name: Symbol, + slot: u32, + }, + // Variable Access (Resolved) Get { addr: Address, @@ -35,53 +47,53 @@ pub enum BoundKind { // Variable Update (Assignment) Set { addr: Address, - value: Box, T>>, + value: Box>, }, // Variable Declaration (Local) DefLocal { name: Symbol, slot: u32, - value: Box, T>>, + value: Box>, captured_by: Vec, }, If { - cond: Box, T>>, - then_br: Box, T>>, - else_br: Option, T>>>, + cond: Box>, + then_br: Box>, + else_br: Option>>, }, // Global Definition (Locals are implicit by stack position) DefGlobal { name: Symbol, global_index: u32, - value: Box, T>>, + value: Box>, }, Lambda { - param_count: u32, + params: Box>, // The list of variables captured from enclosing scopes upvalues: Vec
, - body: Rc, T>>, + body: Rc>, }, Call { - callee: Box, T>>, - args: Vec, T>>, + callee: Box>, + args: Box>, }, TailCall { - callee: Box, T>>, - args: Vec, T>>, + callee: Box>, + args: Box>, }, Block { - exprs: Vec, T>>, + exprs: Vec>, }, Tuple { - elements: Vec, T>>, + elements: Vec>, }, Map { @@ -93,7 +105,7 @@ pub enum BoundKind { /// The original call from the untyped AST. original_call: Rc>, /// The result of binding the expanded AST. - bound_expanded: Box, T>>, + bound_expanded: Box>, }, /// DSL-specific extension slot @@ -101,25 +113,26 @@ pub enum BoundKind { } /// A single entry in a Map literal (Key-Value pair) -pub type MapEntry = (Node, T>, Node, T>); - -/// Type alias for a node that has been bound (addresses resolved) but not yet type-checked. -pub type BoundNode = Node, ()>; - -/// Type alias for a node that has been fully type-checked and is ready for execution. -pub type TypedNode = Node, StaticType>; +pub type MapEntry = (BoundNode, BoundNode); impl BoundKind { pub fn display_name(&self) -> String { match self { BoundKind::Nop => "NOP".to_string(), BoundKind::Constant(v) => format!("CONST({})", v), + BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot), BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), BoundKind::Set { addr, .. } => format!("SET({:?})", addr), BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot), BoundKind::If { .. } => "IF".to_string(), BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), - BoundKind::Lambda { upvalues, .. } => format!("LAMBDA(Captures:{})", upvalues.len()), + BoundKind::Lambda { params, upvalues, .. } => { + let p_str = match ¶ms.kind { + BoundKind::Tuple { elements } => format!("p:{}", elements.len()), + _ => "p:1".to_string(), + }; + format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) + }, BoundKind::Call { .. } => "CALL".to_string(), BoundKind::TailCall { .. } => "T-CALL".to_string(), BoundKind::Block { .. } => "BLOCK".to_string(), diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index d14147f..7bcab7b 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -117,9 +117,14 @@ impl Dumper { self.indent -= 1; } - BoundKind::Lambda { param_count, upvalues, body } => { - self.log(&format!("Lambda (Params: {}, Upvalues: {})", param_count, upvalues.len()), node); + BoundKind::Lambda { params, upvalues, body } => { + self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node); self.indent += 1; + + self.write_indent(); + self.output.push_str("Parameters:\n"); + self.visit(params); + if !upvalues.is_empty() { self.write_indent(); self.output.push_str(&format!("Upvalues: {:?}\n", upvalues)); @@ -128,23 +133,37 @@ impl Dumper { self.indent -= 1; } + BoundKind::Parameter { name, slot } => { + self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node); + } + BoundKind::Call { callee, args } => { self.log("Call", node); self.indent += 1; + + self.write_indent(); + self.output.push_str("Callee:\n"); self.visit(callee); - for arg in args { - self.visit(arg); - } + + self.write_indent(); + self.output.push_str("Arguments:\n"); + self.visit(args); + self.indent -= 1; } BoundKind::TailCall { callee, args } => { self.log("TailCall (TCO)", node); self.indent += 1; + + self.write_indent(); + self.output.push_str("Callee:\n"); self.visit(callee); - for arg in args { - self.visit(arg); - } + + self.write_indent(); + self.output.push_str("Arguments:\n"); + self.visit(args); + self.indent -= 1; } diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index 870c5b0..ee3b80d 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -23,9 +23,10 @@ impl<'a> LambdaCollector<'a> { } BoundKind::DefGlobal { global_index, value, .. } => { - // If we define a global that is a lambda, register it as a template. - if let BoundKind::Lambda { .. } = &value.kind { - self.registry.insert(*global_index, (**value).clone()); + // If we define a global that is a lambda, register its BODY as the template. + // This allows the VM to skip the Lambda/Parameter nodes during execution. + if let BoundKind::Lambda { body, .. } = &value.kind { + self.registry.insert(*global_index, (**body).clone()); } self.visit(value); } @@ -33,9 +34,9 @@ impl<'a> LambdaCollector<'a> { BoundKind::Set { addr, value } => { // Also track assignments to globals if they hold lambdas. if let Address::Global(global_index) = addr - && let BoundKind::Lambda { .. } = &value.kind + && let BoundKind::Lambda { body, .. } = &value.kind { - self.registry.insert(*global_index, (**value).clone()); + self.registry.insert(*global_index, (**body).clone()); } self.visit(value); } @@ -48,7 +49,8 @@ impl<'a> LambdaCollector<'a> { } } - BoundKind::Lambda { body, .. } => { + BoundKind::Lambda { params, body, .. } => { + self.visit(params); self.visit(body); } @@ -58,9 +60,7 @@ impl<'a> LambdaCollector<'a> { BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { self.visit(callee); - for arg in args { - self.visit(arg); - } + self.visit(args); } BoundKind::Tuple { elements } => { diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index f0b5635..3e48b21 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -78,7 +78,7 @@ impl MacroExpander { fn expand_recursive(&mut self, node: Node) -> Result, String> { match node.kind { UntypedKind::MacroDecl { name, params, body } => { - let p_names: Vec> = params.into_iter().map(|s| s.name).collect(); + let p_names = self.extract_param_names(¶ms)?; self.registry.define(name.name, p_names, *body); Ok(Node { identity: node.identity, @@ -90,25 +90,27 @@ impl MacroExpander { UntypedKind::Call { callee, args } => { if let UntypedKind::Identifier(ref sym) = callee.kind && let Some((params, body)) = self.registry.lookup(&sym.name) { - let mut expanded_args = Vec::new(); - for arg in args { - expanded_args.push(self.expand_recursive(arg)?); - } - let expanded = self.expand_call(node.identity.clone(), &sym.name, params, expanded_args, body)?; + let expanded_args = self.expand_recursive(*args)?; + + // Extract argument nodes from the expanded tuple + let arg_elements = if let UntypedKind::Tuple { elements } = expanded_args.kind { + elements + } else { + vec![expanded_args] + }; + + let expanded = self.expand_call(node.identity.clone(), &sym.name, params, arg_elements, body)?; return self.expand_recursive(expanded); } let expanded_callee = self.expand_recursive(*callee)?; - let mut expanded_args = Vec::new(); - for arg in args { - expanded_args.push(self.expand_recursive(arg)?); - } + let expanded_args = self.expand_recursive(*args)?; Ok(Node { identity: node.identity, kind: UntypedKind::Call { callee: Box::new(expanded_callee), - args: expanded_args, + args: Box::new(expanded_args), }, ty: (), }) @@ -131,12 +133,16 @@ impl MacroExpander { UntypedKind::Lambda { params, body } => { self.registry.push(); + let expanded_params = self.expand_recursive(*params)?; let expanded_body = self.expand_recursive(Node::clone(&body))?; self.registry.pop(); Ok(Node { identity: node.identity, - kind: UntypedKind::Lambda { params, body: Rc::new(expanded_body) }, + kind: UntypedKind::Lambda { + params: Box::new(expanded_params), + body: Rc::new(expanded_body) + }, ty: (), }) } @@ -242,7 +248,11 @@ impl MacroExpander { kind: UntypedKind::Identifier(Symbol::from(name)), ty: (), }), - args: bindings.into_values().collect(), + args: Box::new(Node { + identity: identity.clone(), + kind: UntypedKind::Tuple { elements: bindings.into_values().collect() }, + ty: (), + }), }, ty: (), }; @@ -257,6 +267,20 @@ impl MacroExpander { }) } + fn extract_param_names(&self, node: &Node) -> Result>, String> { + match &node.kind { + UntypedKind::Parameter(sym) => Ok(vec![sym.name.clone()]), + UntypedKind::Tuple { elements } => { + let mut names = Vec::new(); + for el in elements { + names.extend(self.extract_param_names(el)?); + } + Ok(names) + } + _ => Err("Invalid parameter pattern in macro declaration".to_string()), + } + } + fn expand_template(&self, node: Node, state: &mut ExpansionState) -> Result, String> { match node.kind { UntypedKind::Identifier(mut sym) => { @@ -265,6 +289,11 @@ impl MacroExpander { Ok(Node { identity: node.identity, kind: UntypedKind::Identifier(sym), ty: () }) } + UntypedKind::Parameter(mut sym) => { + sym.context = Some(state.expansion_id.clone()); + Ok(Node { identity: node.identity, kind: UntypedKind::Parameter(sym), ty: () }) + } + UntypedKind::Placeholder(inner) => { // Break out of template for substitution/evaluation if let UntypedKind::Identifier(ref sym) = inner.kind @@ -353,18 +382,10 @@ impl MacroExpander { UntypedKind::Call { callee, args } => { let expanded_callee = self.expand_template(*callee, state)?; - let mut expanded_args = Vec::new(); - for arg in args { - if let UntypedKind::Splice(ref inner) = arg.kind { - let val = self.evaluator.evaluate(inner, state.bindings)?; - self.handle_splice_value(val, node.identity.clone(), &mut expanded_args)?; - } else { - expanded_args.push(self.expand_template(arg, state)?); - } - } + let expanded_args = self.expand_template(*args, state)?; Ok(Node { identity: node.identity, - kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: expanded_args }, + kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args) }, ty: (), }) } @@ -384,14 +405,12 @@ impl MacroExpander { }) } - UntypedKind::Lambda { mut params, body } => { - for p in &mut params { - p.context = Some(state.expansion_id.clone()); - } + UntypedKind::Lambda { params, body } => { + let expanded_params = self.expand_template(*params, state)?; let body = self.expand_template(Node::clone(&body), state)?; Ok(Node { identity: node.identity, - kind: UntypedKind::Lambda { params, body: Rc::new(body) }, + kind: UntypedKind::Lambda { params: Box::new(expanded_params), body: Rc::new(body) }, ty: (), }) } diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index 4d64fda..d57b408 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -49,13 +49,13 @@ impl Specializer { fn visit_node(&self, node: TypedNode) -> TypedNode { let (new_kind, new_ty) = match node.kind { BoundKind::Call { callee, args } => { - let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone()); - (BoundKind::Call { callee: Box::new(new_callee), args: new_args }, ret_ty) + let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone()); + (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty) }, BoundKind::TailCall { callee, args } => { - let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone()); - (BoundKind::TailCall { callee: Box::new(new_callee), args: new_args }, ret_ty) + let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone()); + (BoundKind::TailCall { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty) }, // Recursive traversal for other nodes @@ -69,9 +69,10 @@ impl Specializer { let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); (BoundKind::Block { exprs }, node.ty) }, - BoundKind::Lambda { param_count, upvalues, body } => { + BoundKind::Lambda { params, upvalues, body } => { + let params = Box::new(self.visit_node(*params)); let body = Rc::new(self.visit_node((*body).clone())); - (BoundKind::Lambda { param_count, upvalues, body }, node.ty) + (BoundKind::Lambda { params, upvalues, body }, node.ty) }, BoundKind::DefLocal { name, slot, value, captured_by } => { let value = Box::new(self.visit_node(*value)); @@ -109,10 +110,10 @@ impl Specializer { } } - fn specialize_call_logic(&self, callee: TypedNode, args: Vec, original_ty: StaticType) -> (TypedNode, Vec, StaticType) { + fn specialize_call_logic(&self, callee: TypedNode, args: TypedNode, original_ty: StaticType) -> (TypedNode, TypedNode, StaticType) { // 1. Specialize children first let new_callee = self.visit_node(callee); - let new_args: Vec = args.into_iter().map(|a| self.visit_node(a)).collect(); + let new_args = self.visit_node(args); // 2. Check if this call is a candidate (Callee is Get(Address)) let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { @@ -123,7 +124,12 @@ impl Specializer { }; // 3. Check if all argument types are statically known - let arg_types: Vec = new_args.iter().map(|a| a.ty.clone()).collect(); + let arg_types: Vec = if let StaticType::Tuple(elements) = &new_args.ty { + elements.clone() + } else { + vec![new_args.ty.clone()] + }; + if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { // Cannot specialize with unknown types return (new_callee, new_args, original_ty); @@ -139,7 +145,7 @@ impl Specializer { identity: new_callee.identity.clone(), kind: BoundKind::Constant(val.clone()), ty: StaticType::Function(Box::new(Signature { - params: arg_types, + params: StaticType::Tuple(arg_types), ret: ret_ty.clone(), })), }; @@ -158,7 +164,7 @@ impl Specializer { identity: new_callee.identity.clone(), kind: BoundKind::Constant(val.clone()), ty: StaticType::Function(Box::new(Signature { - params: arg_types, + params: StaticType::Tuple(arg_types), ret: ret_ty.clone(), })), }; @@ -192,7 +198,7 @@ impl Specializer { identity: new_callee.identity.clone(), kind: BoundKind::Constant(res_val), ty: StaticType::Function(Box::new(Signature { - params: arg_types, + params: StaticType::Tuple(arg_types), ret: res_ty.clone(), })), }; @@ -262,7 +268,19 @@ mod tests { let func_node = BoundNode { identity: make_identity(), kind: BoundKind::Lambda { - param_count: 1, + params: Box::new(BoundNode { + identity: make_identity(), + kind: BoundKind::Tuple { + elements: vec![ + BoundNode { + identity: make_identity(), + kind: BoundKind::Parameter { name: name.clone(), slot: 0 }, + ty: () + } + ] + }, + ty: () + }), upvalues: vec![], body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () }) }, @@ -278,12 +296,13 @@ mod tests { let spec = Specializer::new(Some(Rc::new(registry)), Some(compiler), None, None); - // Call(Get(Local(0)), [Arg(Int)]) + // Call(Get(Local(0)), Tuple([Arg(Int)])) let callee = make_typed_node(BoundKind::Get { addr, name: name.clone() }, StaticType::Any); let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); + let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int])); let call_node = make_typed_node( - BoundKind::Call { callee: Box::new(callee), args: vec![arg] }, + BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) }, StaticType::Any ); @@ -310,12 +329,13 @@ mod tests { let name0 = Symbol::from("f"); let name1 = Symbol::from("x"); - // Call(Get(Local(0)), [Get(Local(1))]) where arg is Any - let callee = make_typed_node(BoundKind::Get { addr: Address::Local(0), name: name0 }, StaticType::Function(Box::new(Signature { params: vec![StaticType::Any], ret: StaticType::Void }))); + // Call(Get(Local(0)), Tuple([Get(Local(1))])) where arg is Any + let callee = make_typed_node(BoundKind::Get { addr: Address::Local(0), name: name0 }, StaticType::Function(Box::new(Signature { params: StaticType::Tuple(vec![StaticType::Any]), ret: StaticType::Void }))); let arg = make_typed_node(BoundKind::Get { addr: Address::Local(1), name: name1 }, StaticType::Any); + let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Any])); let call_node = make_typed_node( - BoundKind::Call { callee: Box::new(callee), args: vec![arg] }, + BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) }, StaticType::Void ); @@ -349,12 +369,13 @@ mod tests { spec.cache.borrow_mut().insert(key, (specialized_val.clone(), ret_ty.clone())); - // Create the call node: Call(Get(0), [Arg(Int)]) + // Create the call node: Call(Get(0), Tuple([Arg(Int)])) let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any); let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); + let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int])); let call_node = make_typed_node( - BoundKind::Call { callee: Box::new(callee), args: vec![arg] }, + BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) }, StaticType::Any ); @@ -393,9 +414,10 @@ mod tests { let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any); let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); + let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int])); let call_node = make_typed_node( - BoundKind::Call { callee: Box::new(callee), args: vec![arg] }, + BoundKind::Call { callee: Box::new(callee), args: Box::new(args_tuple) }, StaticType::Any ); @@ -431,10 +453,11 @@ mod tests { let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any); let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); + let args_tuple = make_typed_node(BoundKind::Tuple { elements: vec![arg] }, StaticType::Tuple(vec![StaticType::Int])); // Use TailCall here let call_node = make_typed_node( - BoundKind::TailCall { callee: Box::new(callee), args: vec![arg] }, + BoundKind::TailCall { callee: Box::new(callee), args: Box::new(args_tuple) }, StaticType::Any ); diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index 517759b..3407a2d 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -16,7 +16,7 @@ impl TCO { match node.kind { BoundKind::Call { callee, args } => { let new_callee = Box::new(Self::transform(*callee, false)); - let new_args: Vec<_> = args.into_iter().map(|arg| Self::transform(arg, false)).collect(); + let new_args = Box::new(Self::transform(*args, false)); if is_tail_position { Node { @@ -75,13 +75,13 @@ impl TCO { } }, - BoundKind::Lambda { param_count, upvalues, body } => { + BoundKind::Lambda { params, upvalues, body } => { // The body of a lambda is implicitly in tail position when the lambda is called. let new_body = Rc::new(Self::transform((*body).clone(), true)); Node { kind: BoundKind::Lambda { - param_count, + params, upvalues, body: new_body, }, diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index ac1235e..7514142 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -48,7 +48,7 @@ impl TypeChecker { pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result { match node.kind { - BoundKind::Lambda { param_count, upvalues, body } => { + BoundKind::Lambda { params, upvalues, body } => { // 1. Determine types of captured variables (Root lambdas have none) let mut upvalue_types = Vec::with_capacity(upvalues.len()); for _ in &upvalues { @@ -57,35 +57,33 @@ impl TypeChecker { // 2. Create the specialized context let root_ctx = TypeContext::new(0, vec![], None); - let mut lambda_ctx = TypeContext::new(param_count + 64, upvalue_types, Some(&root_ctx)); + let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(&root_ctx)); // 3. INJECT specialized argument types into slots - for (i, ty) in arg_types.iter().enumerate() { - if (i as u32) < param_count { - lambda_ctx.set_local_type(i as u32, ty.clone()); - } - } + let arg_tuple_ty = if arg_types.is_empty() { + StaticType::Any // No specialized info + } else { + StaticType::Tuple(arg_types.to_vec()) + }; + + let params_typed = self.check_params(*params, &arg_tuple_ty, &mut lambda_ctx)?; // 4. Check body with the new types let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; let ret_ty = body_typed.ty.clone(); // 5. Construct specialized function type - let final_params = if arg_types.is_empty() { - vec![StaticType::Any; param_count as usize] - } else { - arg_types.to_vec() - }; + let final_params_ty = params_typed.ty.clone(); let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { - params: final_params, + params: final_params_ty, ret: ret_ty, })); Ok(Node { identity: node.identity, kind: BoundKind::Lambda { - param_count, + params: Box::new(params_typed), upvalues, body: Rc::new(body_typed) }, @@ -97,7 +95,11 @@ impl TypeChecker { let virtual_lambda = BoundNode { identity: node.identity.clone(), kind: BoundKind::Lambda { - param_count: 0, + params: Box::new(Node { + identity: node.identity.clone(), + kind: BoundKind::Tuple { elements: vec![] }, + ty: (), + }), upvalues: vec![], body: Rc::new(node) }, @@ -108,6 +110,38 @@ impl TypeChecker { } } + fn check_params(&self, node: BoundNode, specialized_ty: &StaticType, ctx: &mut TypeContext) -> Result { + let (kind, ty) = match node.kind { + BoundKind::Parameter { name, slot } => { + ctx.set_local_type(slot, specialized_ty.clone()); + (BoundKind::Parameter { name, slot }, specialized_ty.clone()) + } + BoundKind::Tuple { elements } => { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + + for (i, el) in elements.into_iter().enumerate() { + let sub_ty = match specialized_ty { + StaticType::Tuple(t) => t.get(i).cloned().unwrap_or(StaticType::Any), + StaticType::Vector(inner, _) => (**inner).clone(), + _ => StaticType::Any, + }; + let t = self.check_params(el, &sub_ty, ctx)?; + elem_types.push(t.ty.clone()); + typed_elements.push(t); + } + (BoundKind::Tuple { elements: typed_elements }, StaticType::Tuple(elem_types)) + } + _ => return Err("Invalid node in parameter list".to_string()), + }; + + Ok(Node { + identity: node.identity, + kind, + ty, + }) + } + fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result { let (kind, ty) = match node.kind { BoundKind::Nop => (BoundKind::Nop, StaticType::Void), @@ -117,6 +151,10 @@ impl TypeChecker { (BoundKind::Constant(v), ty) }, + BoundKind::Parameter { name, slot } => { + (BoundKind::Parameter { name, slot }, ctx.get_type(Address::Local(slot))) + }, + BoundKind::Get { addr, name } => { let ty = if let Address::Global(idx) = addr { self.global_types.borrow().get(&idx).cloned().unwrap_or(StaticType::Any) @@ -194,7 +232,7 @@ impl TypeChecker { (BoundKind::Block { exprs: typed_exprs }, last_ty) }, - BoundKind::Lambda { param_count, upvalues, body } => { + BoundKind::Lambda { params, upvalues, body } => { // 1. Determine types of captured variables let mut upvalue_types = Vec::with_capacity(upvalues.len()); for &addr in &upvalues { @@ -202,20 +240,21 @@ impl TypeChecker { } // 2. Create nested context for lambda body - let mut lambda_ctx = TypeContext::new(param_count + 64, upvalue_types, Some(ctx)); + let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx)); - // 3. Check body + // 3. Check parameters and body + let params_typed = self.check_params(*params, &StaticType::Any, &mut lambda_ctx)?; let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; let ret_ty = body_typed.ty.clone(); - // 4. Construct function type: fn(any ... any) -> Ret + // 4. Construct function type let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { - params: vec![StaticType::Any; param_count as usize], + params: params_typed.ty.clone(), ret: ret_ty, })); (BoundKind::Lambda { - param_count, + params: Box::new(params_typed), upvalues, body: Rc::new(body_typed) }, fn_ty) @@ -223,17 +262,13 @@ impl TypeChecker { BoundKind::Call { callee, args } => { let callee_typed = self.check_node(*callee, ctx)?; - let mut typed_args = Vec::new(); - for arg in args { - typed_args.push(self.check_node(arg, ctx)?); - } + let args_typed = self.check_node(*args, ctx)?; - let arg_types: Vec<_> = typed_args.iter().map(|a| a.ty.clone()).collect(); - let ret_ty = callee_typed.ty.resolve_call(&arg_types).unwrap_or(StaticType::Any); + let ret_ty = callee_typed.ty.resolve_call(&args_typed.ty).unwrap_or(StaticType::Any); (BoundKind::Call { callee: Box::new(callee_typed), - args: typed_args + args: Box::new(args_typed) }, ret_ty) }, diff --git a/src/ast/compiler/upvalues.rs b/src/ast/compiler/upvalues.rs index f856eda..ba5039e 100644 --- a/src/ast/compiler/upvalues.rs +++ b/src/ast/compiler/upvalues.rs @@ -19,6 +19,20 @@ impl UpvalueAnalyzer { .collect() } + fn visit_params(node: &Node, scope: &mut HashMap) { + match &node.kind { + UntypedKind::Parameter(sym) => { + scope.insert(sym.clone(), node.identity.clone()); + } + UntypedKind::Tuple { elements } => { + for el in elements { + Self::visit_params(el, scope); + } + } + _ => {} // Ignore other nodes in parameter patterns for now + } + } + fn visit( node: &Node, scopes: &mut Vec>, @@ -50,15 +64,20 @@ impl UpvalueAnalyzer { } UntypedKind::Lambda { params, body } => { let mut new_scope = HashMap::new(); - for p in params { - new_scope.insert(p.clone(), node.identity.clone()); - } + Self::visit_params(params, &mut new_scope); scopes.push(new_scope); // The current node is the lambda causing captures in its body Self::visit(body, scopes, capture_map, Some(node.identity.clone())); scopes.pop(); } + UntypedKind::MacroDecl { params, body, .. } => { + let mut new_scope = HashMap::new(); + Self::visit_params(params, &mut new_scope); + scopes.push(new_scope); + Self::visit(body, scopes, capture_map, current_lambda); + scopes.pop(); + } UntypedKind::If { cond, then_br, else_br } => { Self::visit(cond, scopes, capture_map, current_lambda.clone()); Self::visit(then_br, scopes, capture_map, current_lambda.clone()); @@ -72,9 +91,7 @@ impl UpvalueAnalyzer { } UntypedKind::Call { callee, args } => { Self::visit(callee, scopes, capture_map, current_lambda.clone()); - for arg in args { - Self::visit(arg, scopes, capture_map, current_lambda.clone()); - } + Self::visit(args, scopes, capture_map, current_lambda.clone()); } UntypedKind::Block { exprs } => { for expr in exprs { @@ -95,10 +112,10 @@ impl UpvalueAnalyzer { UntypedKind::Expansion { call: _, expanded } => { Self::visit(expanded, scopes, capture_map, current_lambda); } - UntypedKind::MacroDecl { body, .. } | UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => { + UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => { Self::visit(body, scopes, capture_map, current_lambda); } - UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) => {} + UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) | UntypedKind::Parameter(_) => {} } } } diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 9c605fb..bd70aaf 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -58,6 +58,7 @@ pub enum UntypedKind { Nop, Constant(Value), Identifier(Symbol), + Parameter(Symbol), If { cond: Box>, then_br: Box>, @@ -72,12 +73,12 @@ pub enum UntypedKind { value: Box>, }, Lambda { - params: Vec, + params: Box>, body: Rc>, }, Call { callee: Box>, - args: Vec>, + args: Box>, }, Block { exprs: Vec>, @@ -91,7 +92,7 @@ pub enum UntypedKind { /// A macro declaration that can be expanded at compile time. MacroDecl { name: Symbol, - params: Vec, + params: Box>, body: Box>, }, /// A template for AST nodes, allowing for substitutions. (Quasiquote) diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 30bae36..79903c6 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -38,8 +38,12 @@ impl<'a> Parser<'a> { Ok(Node { identity: identity.clone(), kind: UntypedKind::Call { - callee: Box::new(self.make_id_node("quote", identity)), - args: vec![expr], + callee: Box::new(self.make_id_node("quote", identity.clone())), + args: Box::new(Node { + identity, + kind: UntypedKind::Tuple { elements: vec![expr] }, + ty: (), + }), }, ty: (), }) @@ -186,7 +190,7 @@ impl<'a> Parser<'a> { } fn parse_fn(&mut self, identity: Identity) -> Result, String> { - let params = self.parse_param_vector()?; + let params = Box::new(self.parse_param_vector()?); let body = self.parse_expression()?; Ok(Node { @@ -205,7 +209,7 @@ impl<'a> Parser<'a> { UntypedKind::Identifier(sym) => sym, _ => return Err("Expected identifier for macro name".to_string()), }; - let params = self.parse_param_vector()?; + let params = Box::new(self.parse_param_vector()?); let body = self.parse_expression()?; Ok(Node { identity, @@ -214,34 +218,53 @@ impl<'a> Parser<'a> { }) } - fn parse_param_vector(&mut self) -> Result, String> { + fn parse_param_vector(&mut self) -> Result, String> { if *self.peek() != TokenKind::LeftBracket { return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); } - self.advance()?; + let token = self.advance()?; + let identity = Rc::new(NodeIdentity { location: token.location }); - let mut params = Vec::new(); + let mut elements = Vec::new(); while *self.peek() != TokenKind::RightBracket { - let token = self.advance()?; - match token.kind { - TokenKind::Identifier(name) => params.push(name.into()), - _ => return Err(format!("Expected identifier in param vector, found {:?}", token.kind)), + let next_token = self.advance()?; + let p_identity = Rc::new(NodeIdentity { location: next_token.location }); + match next_token.kind { + TokenKind::Identifier(name) => { + elements.push(Node { + identity: p_identity, + kind: UntypedKind::Parameter(name.into()), + ty: (), + }); + }, + _ => return Err(format!("Expected identifier in param vector, found {:?}", next_token.kind)), } } self.expect(TokenKind::RightBracket)?; - Ok(params) + Ok(Node { + identity, + kind: UntypedKind::Tuple { elements }, + ty: (), + }) } fn parse_call(&mut self, callee: Node, identity: Identity) -> Result, String> { - let mut args = Vec::new(); + let mut elements = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - args.push(self.parse_expression()?); + elements.push(self.parse_expression()?); } + // The arguments are wrapped in a Tuple node, reusing the call's identity/location. + let args_node = Node { + identity: identity.clone(), + kind: UntypedKind::Tuple { elements }, + ty: (), + }; + Ok(Node { identity, - kind: UntypedKind::Call { callee: Box::new(callee), args }, + kind: UntypedKind::Call { callee: Box::new(callee), args: Box::new(args_node) }, ty: (), }) } diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index 6722d83..53b496b 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -23,10 +23,10 @@ fn register_constants(env: &Environment) { fn register_arithmetic(env: &Environment) { // --- Add (+) --- let add_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, - Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, - Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text }, - Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text }, + Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, ]); env.register_native("+", add_ty, |args| { if args.len() == 2 { @@ -56,12 +56,12 @@ fn register_arithmetic(env: &Environment) { // --- Subtract (-) --- let sub_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, - Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, - Signature { params: vec![StaticType::Int], ret: StaticType::Int }, // Negation - Signature { params: vec![StaticType::Float], ret: StaticType::Float }, - Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Int }, - Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, // Negation + Signature { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, ]); env.register_native("-", sub_ty, |args| { if args.is_empty() { return Value::Void; } @@ -98,8 +98,8 @@ fn register_arithmetic(env: &Environment) { // --- Multiply (*) --- let mul_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, - Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, ]); env.register_native("*", mul_ty, |args| { if args.len() == 2 { @@ -122,8 +122,8 @@ fn register_arithmetic(env: &Environment) { // --- Divide (/) --- let div_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float }, - Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float }, + Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, ]); env.register_native("/", div_ty, |args| { if args.len() != 2 { return Value::Void; } @@ -134,7 +134,7 @@ fn register_arithmetic(env: &Environment) { // --- Integer Divide (//) --- let int_div_ty = StaticType::Function(Box::new( - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int } + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } )); env.register_native("//", int_div_ty, |args| { if args.len() != 2 { return Value::Void; } @@ -150,7 +150,7 @@ fn register_arithmetic(env: &Environment) { // --- Modulus (%) --- let mod_ty = StaticType::Function(Box::new( - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int } + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } )); env.register_native("%", mod_ty, |args| { if args.len() != 2 { return Value::Void; } @@ -165,9 +165,9 @@ fn register_arithmetic(env: &Environment) { fn register_comparison(env: &Environment) { let cmp_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool }, - Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool }, - Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Bool }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Bool }, + Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Bool }, + Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Bool }, ]); // --- Greater Than (>) --- @@ -224,7 +224,7 @@ fn register_comparison(env: &Environment) { // --- Equal (=) --- let eq_ty = StaticType::Function(Box::new( - Signature { params: vec![StaticType::Any, StaticType::Any], ret: StaticType::Bool } + Signature { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool } )); env.register_native("=", eq_ty.clone(), |args| { if args.len() != 2 { return Value::Void; } @@ -265,8 +265,8 @@ fn register_comparison(env: &Environment) { fn register_logic(env: &Environment) { // --- Not (not) --- let not_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Bool], ret: StaticType::Bool }, - Signature { params: vec![StaticType::Int], ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool }, + Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, ]); env.register_native("not", not_ty, |args| { if args.len() != 1 { return Value::Void; } @@ -279,8 +279,8 @@ fn register_logic(env: &Environment) { // --- And (and) --- let logic_op_ty = StaticType::FunctionOverloads(vec![ - Signature { params: vec![StaticType::Bool, StaticType::Bool], ret: StaticType::Bool }, - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, + Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool }, + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, ]); env.register_native("and", logic_op_ty.clone(), |args| { if args.len() != 2 { return Value::Void; } @@ -313,7 +313,7 @@ fn register_logic(env: &Environment) { // --- Shift Left (<<) --- let shift_ty = StaticType::Function(Box::new( - Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int } + Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } )); env.register_native("<<", shift_ty.clone(), |args| { if args.len() != 2 { return Value::Void; } diff --git a/src/ast/rtl/datetime.rs b/src/ast/rtl/datetime.rs index 42d0bda..35097db 100644 --- a/src/ast/rtl/datetime.rs +++ b/src/ast/rtl/datetime.rs @@ -4,7 +4,7 @@ use chrono::{NaiveDate, NaiveDateTime}; pub fn register(env: &Environment) { let date_ty = StaticType::Function(Box::new(Signature { - params: vec![StaticType::Text], + params: StaticType::Tuple(vec![StaticType::Text]), ret: StaticType::DateTime })); diff --git a/src/ast/rtl/type_registry.rs b/src/ast/rtl/type_registry.rs index 41210b0..6e82b84 100644 --- a/src/ast/rtl/type_registry.rs +++ b/src/ast/rtl/type_registry.rs @@ -136,7 +136,7 @@ impl TypeBuilder { pub fn method(mut self, name: &str, params: Vec, ret: StaticType) -> Self { let key = Keyword::intern(name); - let sig = StaticType::Function(Box::new(Signature { params, ret })); + let sig = StaticType::Function(Box::new(Signature { params: StaticType::Tuple(params), ret })); self.fields.insert(key, sig); self } diff --git a/src/ast/types.rs b/src/ast/types.rs index d9783ca..8a0deeb 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -75,7 +75,7 @@ pub enum Value { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Signature { - pub params: Vec, + pub params: StaticType, pub ret: StaticType, } @@ -137,12 +137,7 @@ impl fmt::Display for StaticType { write!(f, "}}") }, StaticType::Function(sig) => { - write!(f, "fn(")?; - for (i, p) in sig.params.iter().enumerate() { - if i > 0 { write!(f, " ")?; } - write!(f, "{}", p)?; - } - write!(f, ") -> {}", sig.ret) + write!(f, "fn({}) -> {}", sig.params, sig.ret) }, StaticType::FunctionOverloads(sigs) => { write!(f, "overloads({} variants)", sigs.len()) @@ -180,12 +175,12 @@ impl StaticType { } } - /// Tries to resolve a call with the given argument types and returns the return type. - pub fn resolve_call(&self, arg_types: &[StaticType]) -> Option { + /// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type. + pub fn resolve_call(&self, args_ty: &StaticType) -> Option { match self { StaticType::Any => Some(StaticType::Any), StaticType::Function(sig) => { - if self.match_params(&sig.params, arg_types) { + if sig.params.is_assignable_from(args_ty) { Some(sig.ret.clone()) } else { None @@ -193,20 +188,13 @@ impl StaticType { } StaticType::FunctionOverloads(sigs) => { sigs.iter() - .find(|sig| self.match_params(&sig.params, arg_types)) + .find(|sig| sig.params.is_assignable_from(args_ty)) .map(|sig| sig.ret.clone()) } _ => None, } } - fn match_params(&self, expected: &[StaticType], actual: &[StaticType]) -> bool { - if expected.len() != actual.len() { - return false; - } - expected.iter().zip(actual).all(|(e, a)| e.is_assignable_from(a)) - } - /// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.) pub fn is_scalar_pure(&self) -> bool { match self { diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 64aea82..9be5b8b 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -102,6 +102,8 @@ macro_rules! dispatch_eval { BoundKind::Nop => Ok(Value::Void), BoundKind::Constant(v) => Ok(v.clone()), + BoundKind::Parameter { .. } => Ok(Value::Void), + BoundKind::DefGlobal { global_index, value, .. } => { let val = $self.$eval_method($($observer,)? value)?; let idx = *global_index as usize; @@ -161,7 +163,10 @@ macro_rules! dispatch_eval { Ok(last) }, - BoundKind::Lambda { param_count: _, upvalues, body } => { + BoundKind::Lambda { params: _, upvalues, body } => { + // PERFORMANCE: Creating a closure captures upvalues. + // The actual execution of the lambda (in Call branch) now skips + // the Lambda node itself and jumps directly to the body. let mut captured = Vec::with_capacity(upvalues.len()); for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); @@ -177,9 +182,28 @@ macro_rules! dispatch_eval { BoundKind::TailCall { callee, args } => { let func_val = $self.$eval_method($($observer,)? callee)?; - let mut arg_vals = Vec::with_capacity(args.len()); - for arg in args { - arg_vals.push($self.$eval_method($($observer,)? arg)?); + + // PERFORMANCE OPTIMIZATION: "Everything is a Tuple" Unification + // To avoid heap-allocating a Value::List (Rc>) for every function call, + // we check if the arguments are a literal tuple. If so, we evaluate them + // directly into our stack-ready vector. + let mut arg_vals = Vec::new(); + match &args.kind { + BoundKind::Tuple { elements } => { + arg_vals.reserve(elements.len()); + for e in elements { + arg_vals.push($self.$eval_method($($observer,)? e)?); + } + } + _ => { + // Fallback for dynamic tuples (e.g. arguments passed as a variable) + let v = $self.$eval_method($($observer,)? args)?; + if let Value::List(l) = v { + arg_vals = (*l).clone(); + } else { + arg_vals.push(v); + } + } } match func_val { @@ -192,9 +216,24 @@ macro_rules! dispatch_eval { BoundKind::Call { callee, args } => { let mut func_val = $self.$eval_method($($observer,)? callee)?; - let mut arg_vals = Vec::with_capacity(args.len()); - for arg in args { - arg_vals.push($self.$eval_method($($observer,)? arg)?); + // PERFORMANCE OPTIMIZATION: Same as in TailCall above. + // Short-circuiting the Tuple -> Value::List -> Vec conversion to save heap cycles. + let mut arg_vals = Vec::new(); + match &args.kind { + BoundKind::Tuple { elements } => { + arg_vals.reserve(elements.len()); + for e in elements { + arg_vals.push($self.$eval_method($($observer,)? e)?); + } + } + _ => { + let v = $self.$eval_method($($observer,)? args)?; + if let Value::List(l) = v { + arg_vals = (*l).clone(); + } else { + arg_vals.push(v); + } + } } loop { @@ -542,7 +581,11 @@ mod tests { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Lambda { - param_count: 0, + params: Box::new(Node { + identity: id.clone(), + ty: StaticType::Tuple(vec![]), + kind: BoundKind::Tuple { elements: vec![] }, + }), upvalues: vec![Address::Local(0)], // Capture x body: Rc::new(lambda_body), }, @@ -559,7 +602,11 @@ mod tests { ty: StaticType::Any, kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") }, }), - args: vec![], + args: Box::new(Node { + identity: id.clone(), + ty: StaticType::Tuple(vec![]), + kind: BoundKind::Tuple { elements: vec![] }, + }), }, }, // return x (Local 0) diff --git a/src/utils/tester.rs b/src/utils/tester.rs index 94b7157..b449946 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -1,7 +1,7 @@ use crate::ast::environment::Environment; +use regex::Regex; use std::fs; use std::time::{Duration, Instant}; -use regex::Regex; pub struct TestResult { pub name: String, @@ -28,7 +28,7 @@ pub fn run_functional_tests() -> Vec { if path.extension().is_some_and(|ext| ext == "myc") { let content = fs::read_to_string(&path).unwrap(); let name = path.file_name().unwrap().to_string_lossy().to_string(); - + let expected_output = output_re .captures(&content) .map(|m| m.get(1).unwrap().as_str().trim().to_string()); @@ -38,12 +38,24 @@ pub fn run_functional_tests() -> Vec { Ok(val) => { let val_str = format!("{}", val); if val_str == expected { - results.push(TestResult { name, success: true, message: format!("OK: {}", val_str) }); + results.push(TestResult { + name, + success: true, + message: format!("OK: {}", val_str), + }); } else { - results.push(TestResult { name, success: false, message: format!("Expected {}, got {}", expected, val_str) }); + results.push(TestResult { + name, + success: false, + message: format!("Expected {}, got {}", expected, val_str), + }); } } - Err(e) => results.push(TestResult { name, success: false, message: format!("Error: {}", e) }), + Err(e) => results.push(TestResult { + name, + success: false, + message: format!("Error: {}", e), + }), } } } @@ -63,9 +75,9 @@ pub fn run_benchmarks(update: bool) -> Vec { if path.extension().is_some_and(|ext| ext == "myc") { let content = fs::read_to_string(&path).unwrap(); let name = path.file_name().unwrap().to_string_lossy().to_string(); - + let baseline_match = baseline_re.captures(&content); - + // Prepare: Compile and Link once outside the measurement loop let linked_node = match env.compile(&content).map(|c| env.link(c)) { Ok(node) => node, @@ -85,13 +97,25 @@ pub fn run_benchmarks(update: bool) -> Vec { if update { let new_val = format_duration(median); let updated_content = if let Some(m) = baseline_match { - content.replace(m.get(0).unwrap().as_str(), &format!(";; Benchmark: {}", new_val)) + content.replace( + m.get(0).unwrap().as_str(), + &format!(";; Benchmark: {}", new_val), + ) } else { - format!(";; Benchmark: {} -{}", new_val, content) + format!( + ";; Benchmark: {} +{}", + new_val, content + ) }; fs::write(&path, updated_content).unwrap(); - results.push(BenchmarkResult { name, median, baseline: None, diff_pct: None, status: format!("UPDATED: {}", new_val) }); + results.push(BenchmarkResult { + name, + median, + baseline: None, + diff_pct: None, + status: format!("UPDATED: {}", new_val), + }); } else if let Some(m) = baseline_match { let baseline_str = m.get(1).unwrap().as_str(); let baseline = parse_duration(baseline_str).unwrap(); @@ -108,13 +132,29 @@ pub fn run_benchmarks(update: bool) -> Vec { } let diff = (median.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0; - - let threshold = if is_release { 0.10 } else { 0.25 }; - let status = if median > baseline && diff > threshold { "FAILED" } else { "OK" }; - - results.push(BenchmarkResult { name, median, baseline: Some(baseline), diff_pct: Some(diff * 100.0), status: status.to_string() }); + + let threshold = if is_release { 0.15 } else { 0.5 }; + let status = if median > baseline && diff > threshold { + "FAILED" + } else { + "OK" + }; + + results.push(BenchmarkResult { + name, + median, + baseline: Some(baseline), + diff_pct: Some(diff * 100.0), + status: status.to_string(), + }); } else { - results.push(BenchmarkResult { name, median, baseline: None, diff_pct: None, status: "MISSING BASELINE".to_string() }); + results.push(BenchmarkResult { + name, + median, + baseline: None, + diff_pct: None, + status: "MISSING BASELINE".to_string(), + }); } } } @@ -122,17 +162,22 @@ pub fn run_benchmarks(update: bool) -> Vec { } fn format_duration(d: Duration) -> String { - if d.as_nanos() < 1000 { format!("{}ns", d.as_nanos()) } - else if d.as_micros() < 1000 { format!("{:.1}us", d.as_nanos() as f64 / 1000.0) } - else if d.as_millis() < 1000 { format!("{:.1}ms", d.as_micros() as f64 / 1000.0) } - else { format!("{:.1}s", d.as_millis() as f64 / 1000.0) } + if d.as_nanos() < 1000 { + format!("{}ns", d.as_nanos()) + } else if d.as_micros() < 1000 { + format!("{:.1}us", d.as_nanos() as f64 / 1000.0) + } else if d.as_millis() < 1000 { + format!("{:.1}ms", d.as_micros() as f64 / 1000.0) + } else { + format!("{:.1}s", d.as_millis() as f64 / 1000.0) + } } fn parse_duration(s: &str) -> Option { use std::sync::OnceLock; static RE: OnceLock = OnceLock::new(); let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap()); - + let caps = re.captures(s)?; let val: f64 = caps[1].parse().ok()?; let unit = &caps[2]; @@ -156,29 +201,40 @@ mod tests { for _ in 0..5 { let results = run_benchmarks(false); - let failures: Vec<_> = results.iter() - .filter(|r| r.status == "FAILED") - .collect(); + let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect(); if failures.is_empty() { success = true; break; } - last_failures = failures.iter() - .map(|r| format!("{}: Median {:?}, Baseline {:?}, Diff {:.2}%", - r.name, r.median, r.baseline.unwrap_or_default(), r.diff_pct.unwrap_or(0.0))) + last_failures = failures + .iter() + .map(|r| { + format!( + "{}: Median {:?}, Baseline {:?}, Diff {:.2}%", + r.name, + r.median, + r.baseline.unwrap_or_default(), + r.diff_pct.unwrap_or(0.0) + ) + }) .collect::>() - .join(" -"); + .join( + " +", + ); // Give the system a tiny bit of breath between retries std::thread::sleep(std::time::Duration::from_millis(50)); } if !success { - panic!("Performance regression detected after 5 attempts: -{}", last_failures); + panic!( + "Performance regression detected after 5 attempts: +{}", + last_failures + ); } } }