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.
This commit is contained in:
Michael Schimmel
2026-02-20 12:14:22 +01:00
parent 8d6d3be5c2
commit e2279f214b
17 changed files with 497 additions and 247 deletions
+22 -13
View File
@@ -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)
}))
},
+36 -23
View File
@@ -21,11 +21,23 @@ impl<T> Clone for Box<dyn BoundExtension<T>> {
}
}
/// Type alias for a node that has been bound. Defaults to untyped (T = ()).
pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>;
#[derive(Debug, Clone)]
pub enum BoundKind<T = ()> {
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<T = ()> {
// Variable Update (Assignment)
Set {
addr: Address,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
},
// Variable Declaration (Local)
DefLocal {
name: Symbol,
slot: u32,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
captured_by: Vec<Identity>,
},
If {
cond: Box<Node<BoundKind<T>, T>>,
then_br: Box<Node<BoundKind<T>, T>>,
else_br: Option<Box<Node<BoundKind<T>, T>>>,
cond: Box<BoundNode<T>>,
then_br: Box<BoundNode<T>>,
else_br: Option<Box<BoundNode<T>>>,
},
// Global Definition (Locals are implicit by stack position)
DefGlobal {
name: Symbol,
global_index: u32,
value: Box<Node<BoundKind<T>, T>>,
value: Box<BoundNode<T>>,
},
Lambda {
param_count: u32,
params: Box<BoundNode<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<Node<BoundKind<T>, T>>,
body: Rc<BoundNode<T>>,
},
Call {
callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind<T>, T>>,
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
TailCall {
callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind<T>, T>>,
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
Block {
exprs: Vec<Node<BoundKind<T>, T>>,
exprs: Vec<BoundNode<T>>,
},
Tuple {
elements: Vec<Node<BoundKind<T>, T>>,
elements: Vec<BoundNode<T>>,
},
Map {
@@ -93,7 +105,7 @@ pub enum BoundKind<T = ()> {
/// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST.
bound_expanded: Box<Node<BoundKind<T>, T>>,
bound_expanded: Box<BoundNode<T>>,
},
/// DSL-specific extension slot
@@ -101,25 +113,26 @@ pub enum BoundKind<T = ()> {
}
/// A single entry in a Map literal (Key-Value pair)
pub type MapEntry<T> = (Node<BoundKind<T>, T>, Node<BoundKind<T>, T>);
/// Type alias for a node that has been bound (addresses resolved) but not yet type-checked.
pub type BoundNode = Node<BoundKind<()>, ()>;
/// Type alias for a node that has been fully type-checked and is ready for execution.
pub type TypedNode = Node<BoundKind<StaticType>, StaticType>;
pub type MapEntry<T> = (BoundNode<T>, BoundNode<T>);
impl<T> BoundKind<T> {
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 &params.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(),
+27 -8
View File
@@ -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;
}
+9 -9
View File
@@ -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 } => {
+47 -28
View File
@@ -78,7 +78,7 @@ impl<E: MacroEvaluator> MacroExpander<E> {
fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
match node.kind {
UntypedKind::MacroDecl { name, params, body } => {
let p_names: Vec<Rc<str>> = params.into_iter().map(|s| s.name).collect();
let p_names = self.extract_param_names(&params)?;
self.registry.define(name.name, p_names, *body);
Ok(Node {
identity: node.identity,
@@ -90,25 +90,27 @@ impl<E: MacroEvaluator> MacroExpander<E> {
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<E: MacroEvaluator> MacroExpander<E> {
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<E: MacroEvaluator> MacroExpander<E> {
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<E: MacroEvaluator> MacroExpander<E> {
})
}
fn extract_param_names(&self, node: &Node<UntypedKind>) -> Result<Vec<Rc<str>>, 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<UntypedKind>, state: &mut ExpansionState) -> Result<Node<UntypedKind>, String> {
match node.kind {
UntypedKind::Identifier(mut sym) => {
@@ -265,6 +289,11 @@ impl<E: MacroEvaluator> MacroExpander<E> {
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<E: MacroEvaluator> MacroExpander<E> {
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<E: MacroEvaluator> MacroExpander<E> {
})
}
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: (),
})
}
+45 -22
View File
@@ -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<TypedNode>, original_ty: StaticType) -> (TypedNode, Vec<TypedNode>, 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<TypedNode> = 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<StaticType> = new_args.iter().map(|a| a.ty.clone()).collect();
let arg_types: Vec<StaticType> = 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
);
+3 -3
View File
@@ -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,
},
+63 -28
View File
@@ -48,7 +48,7 @@ impl TypeChecker {
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
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<TypedNode, String> {
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<TypedNode, String> {
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)
},
+25 -8
View File
@@ -19,6 +19,20 @@ impl UpvalueAnalyzer {
.collect()
}
fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
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<UntypedKind>,
scopes: &mut Vec<HashMap<Symbol, Identity>>,
@@ -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(_) => {}
}
}
}
+4 -3
View File
@@ -58,6 +58,7 @@ pub enum UntypedKind {
Nop,
Constant(Value),
Identifier(Symbol),
Parameter(Symbol),
If {
cond: Box<Node<UntypedKind>>,
then_br: Box<Node<UntypedKind>>,
@@ -72,12 +73,12 @@ pub enum UntypedKind {
value: Box<Node<UntypedKind>>,
},
Lambda {
params: Vec<Symbol>,
params: Box<Node<UntypedKind>>,
body: Rc<Node<UntypedKind>>,
},
Call {
callee: Box<Node<UntypedKind>>,
args: Vec<Node<UntypedKind>>,
args: Box<Node<UntypedKind>>,
},
Block {
exprs: Vec<Node<UntypedKind>>,
@@ -91,7 +92,7 @@ pub enum UntypedKind {
/// A macro declaration that can be expanded at compile time.
MacroDecl {
name: Symbol,
params: Vec<Symbol>,
params: Box<Node<UntypedKind>>,
body: Box<Node<UntypedKind>>,
},
/// A template for AST nodes, allowing for substitutions. (Quasiquote)
+38 -15
View File
@@ -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<Node<UntypedKind>, 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<Vec<Symbol>, String> {
fn parse_param_vector(&mut self) -> Result<Node<UntypedKind>, 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<UntypedKind>, identity: Identity) -> Result<Node<UntypedKind>, 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: (),
})
}
+25 -25
View File
@@ -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; }
+1 -1
View File
@@ -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
}));
+1 -1
View File
@@ -136,7 +136,7 @@ impl TypeBuilder {
pub fn method(mut self, name: &str, params: Vec<StaticType>, 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
}
+6 -18
View File
@@ -75,7 +75,7 @@ pub enum Value {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Signature {
pub params: Vec<StaticType>,
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<StaticType> {
/// 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<StaticType> {
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 {
+56 -9
View File
@@ -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<Vec<Value>>) 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)
+82 -26
View File
@@ -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,
@@ -38,12 +38,24 @@ pub fn run_functional_tests() -> Vec<TestResult> {
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),
}),
}
}
}
@@ -85,13 +97,25 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
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();
@@ -109,12 +133,28 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
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" };
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() });
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,10 +162,15 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
}
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<Duration> {
@@ -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::<Vec<_>>()
.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
);
}
}
}