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
+23 -14
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 } => { UntypedKind::If { cond, then_br, else_br } => {
let cond = self.bind(cond)?; let cond = self.bind(cond)?;
let then_br = self.bind(then_br)?; let then_br = self.bind(then_br)?;
@@ -176,20 +189,19 @@ impl Binder {
}, },
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new()); self.functions.push(FunctionCompiler::new());
{ // 1. Bind the parameter pattern/tuple
let current_fn = self.functions.last_mut().unwrap(); let params_bound = self.bind(params)?;
for param in params {
current_fn.scope.define(param)?; // 2. Bind the body
}
}
let body_bound = self.bind(body)?; let body_bound = self.bind(body)?;
let compiled_fn = self.functions.pop().unwrap(); let compiled_fn = self.functions.pop().unwrap();
Ok(self.make_node(node.identity.clone(), BoundKind::Lambda { Ok(self.make_node(identity, BoundKind::Lambda {
param_count: params.len() as u32, params: Box::new(params_bound),
upvalues: compiled_fn.upvalues, upvalues: compiled_fn.upvalues,
body: Rc::new(body_bound), body: Rc::new(body_bound),
})) }))
@@ -197,14 +209,11 @@ impl Binder {
UntypedKind::Call { callee, args } => { UntypedKind::Call { callee, args } => {
let callee = self.bind(callee)?; let callee = self.bind(callee)?;
let mut bound_args = Vec::new(); let args = self.bind(args)?;
for arg in args {
bound_args.push(self.bind(arg)?);
}
Ok(self.make_node(node.identity.clone(), BoundKind::Call { Ok(self.make_node(node.identity.clone(), BoundKind::Call {
callee: Box::new(callee), 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)] #[derive(Debug, Clone)]
pub enum BoundKind<T = ()> { pub enum BoundKind<T = ()> {
Nop, Nop,
Constant(Value), Constant(Value),
/// A positional parameter in a Lambda's parameter list.
Parameter {
name: Symbol,
slot: u32,
},
// Variable Access (Resolved) // Variable Access (Resolved)
Get { Get {
addr: Address, addr: Address,
@@ -35,53 +47,53 @@ pub enum BoundKind<T = ()> {
// Variable Update (Assignment) // Variable Update (Assignment)
Set { Set {
addr: Address, addr: Address,
value: Box<Node<BoundKind<T>, T>>, value: Box<BoundNode<T>>,
}, },
// Variable Declaration (Local) // Variable Declaration (Local)
DefLocal { DefLocal {
name: Symbol, name: Symbol,
slot: u32, slot: u32,
value: Box<Node<BoundKind<T>, T>>, value: Box<BoundNode<T>>,
captured_by: Vec<Identity>, captured_by: Vec<Identity>,
}, },
If { If {
cond: Box<Node<BoundKind<T>, T>>, cond: Box<BoundNode<T>>,
then_br: Box<Node<BoundKind<T>, T>>, then_br: Box<BoundNode<T>>,
else_br: Option<Box<Node<BoundKind<T>, T>>>, else_br: Option<Box<BoundNode<T>>>,
}, },
// Global Definition (Locals are implicit by stack position) // Global Definition (Locals are implicit by stack position)
DefGlobal { DefGlobal {
name: Symbol, name: Symbol,
global_index: u32, global_index: u32,
value: Box<Node<BoundKind<T>, T>>, value: Box<BoundNode<T>>,
}, },
Lambda { Lambda {
param_count: u32, params: Box<BoundNode<T>>,
// The list of variables captured from enclosing scopes // The list of variables captured from enclosing scopes
upvalues: Vec<Address>, upvalues: Vec<Address>,
body: Rc<Node<BoundKind<T>, T>>, body: Rc<BoundNode<T>>,
}, },
Call { Call {
callee: Box<Node<BoundKind<T>, T>>, callee: Box<BoundNode<T>>,
args: Vec<Node<BoundKind<T>, T>>, args: Box<BoundNode<T>>,
}, },
TailCall { TailCall {
callee: Box<Node<BoundKind<T>, T>>, callee: Box<BoundNode<T>>,
args: Vec<Node<BoundKind<T>, T>>, args: Box<BoundNode<T>>,
}, },
Block { Block {
exprs: Vec<Node<BoundKind<T>, T>>, exprs: Vec<BoundNode<T>>,
}, },
Tuple { Tuple {
elements: Vec<Node<BoundKind<T>, T>>, elements: Vec<BoundNode<T>>,
}, },
Map { Map {
@@ -93,7 +105,7 @@ pub enum BoundKind<T = ()> {
/// The original call from the untyped AST. /// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>, original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST. /// The result of binding the expanded AST.
bound_expanded: Box<Node<BoundKind<T>, T>>, bound_expanded: Box<BoundNode<T>>,
}, },
/// DSL-specific extension slot /// DSL-specific extension slot
@@ -101,25 +113,26 @@ pub enum BoundKind<T = ()> {
} }
/// A single entry in a Map literal (Key-Value pair) /// A single entry in a Map literal (Key-Value pair)
pub type MapEntry<T> = (Node<BoundKind<T>, T>, Node<BoundKind<T>, T>); pub type MapEntry<T> = (BoundNode<T>, BoundNode<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>;
impl<T> BoundKind<T> { impl<T> BoundKind<T> {
pub fn display_name(&self) -> String { pub fn display_name(&self) -> String {
match self { match self {
BoundKind::Nop => "NOP".to_string(), BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v), 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::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr), BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot), BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot),
BoundKind::If { .. } => "IF".to_string(), BoundKind::If { .. } => "IF".to_string(),
BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), 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::Call { .. } => "CALL".to_string(),
BoundKind::TailCall { .. } => "T-CALL".to_string(), BoundKind::TailCall { .. } => "T-CALL".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(), BoundKind::Block { .. } => "BLOCK".to_string(),
+27 -8
View File
@@ -117,9 +117,14 @@ impl Dumper {
self.indent -= 1; self.indent -= 1;
} }
BoundKind::Lambda { param_count, upvalues, body } => { BoundKind::Lambda { params, upvalues, body } => {
self.log(&format!("Lambda (Params: {}, Upvalues: {})", param_count, upvalues.len()), node); self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1; self.indent += 1;
self.write_indent();
self.output.push_str("Parameters:\n");
self.visit(params);
if !upvalues.is_empty() { if !upvalues.is_empty() {
self.write_indent(); self.write_indent();
self.output.push_str(&format!("Upvalues: {:?}\n", upvalues)); self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
@@ -128,23 +133,37 @@ impl Dumper {
self.indent -= 1; self.indent -= 1;
} }
BoundKind::Parameter { name, slot } => {
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node);
}
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
self.log("Call", node); self.log("Call", node);
self.indent += 1; self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee); 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; self.indent -= 1;
} }
BoundKind::TailCall { callee, args } => { BoundKind::TailCall { callee, args } => {
self.log("TailCall (TCO)", node); self.log("TailCall (TCO)", node);
self.indent += 1; self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee); 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; self.indent -= 1;
} }
+9 -9
View File
@@ -23,9 +23,10 @@ impl<'a> LambdaCollector<'a> {
} }
BoundKind::DefGlobal { global_index, value, .. } => { BoundKind::DefGlobal { global_index, value, .. } => {
// If we define a global that is a lambda, register it as a template. // If we define a global that is a lambda, register its BODY as the template.
if let BoundKind::Lambda { .. } = &value.kind { // This allows the VM to skip the Lambda/Parameter nodes during execution.
self.registry.insert(*global_index, (**value).clone()); if let BoundKind::Lambda { body, .. } = &value.kind {
self.registry.insert(*global_index, (**body).clone());
} }
self.visit(value); self.visit(value);
} }
@@ -33,9 +34,9 @@ impl<'a> LambdaCollector<'a> {
BoundKind::Set { addr, value } => { BoundKind::Set { addr, value } => {
// Also track assignments to globals if they hold lambdas. // Also track assignments to globals if they hold lambdas.
if let Address::Global(global_index) = addr 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); self.visit(value);
} }
@@ -48,7 +49,8 @@ impl<'a> LambdaCollector<'a> {
} }
} }
BoundKind::Lambda { body, .. } => { BoundKind::Lambda { params, body, .. } => {
self.visit(params);
self.visit(body); self.visit(body);
} }
@@ -58,9 +60,7 @@ impl<'a> LambdaCollector<'a> {
BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => { BoundKind::Call { callee, args } | BoundKind::TailCall { callee, args } => {
self.visit(callee); self.visit(callee);
for arg in args { self.visit(args);
self.visit(arg);
}
} }
BoundKind::Tuple { elements } => { 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> { fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
match node.kind { match node.kind {
UntypedKind::MacroDecl { name, params, body } => { 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); self.registry.define(name.name, p_names, *body);
Ok(Node { Ok(Node {
identity: node.identity, identity: node.identity,
@@ -90,25 +90,27 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Call { callee, args } => { UntypedKind::Call { callee, args } => {
if let UntypedKind::Identifier(ref sym) = callee.kind if let UntypedKind::Identifier(ref sym) = callee.kind
&& let Some((params, body)) = self.registry.lookup(&sym.name) { && let Some((params, body)) = self.registry.lookup(&sym.name) {
let mut expanded_args = Vec::new(); let expanded_args = self.expand_recursive(*args)?;
for arg in args {
expanded_args.push(self.expand_recursive(arg)?); // Extract argument nodes from the expanded tuple
} let arg_elements = if let UntypedKind::Tuple { elements } = expanded_args.kind {
let expanded = self.expand_call(node.identity.clone(), &sym.name, params, expanded_args, body)?; elements
} else {
vec![expanded_args]
};
let expanded = self.expand_call(node.identity.clone(), &sym.name, params, arg_elements, body)?;
return self.expand_recursive(expanded); return self.expand_recursive(expanded);
} }
let expanded_callee = self.expand_recursive(*callee)?; let expanded_callee = self.expand_recursive(*callee)?;
let mut expanded_args = Vec::new(); let expanded_args = self.expand_recursive(*args)?;
for arg in args {
expanded_args.push(self.expand_recursive(arg)?);
}
Ok(Node { Ok(Node {
identity: node.identity, identity: node.identity,
kind: UntypedKind::Call { kind: UntypedKind::Call {
callee: Box::new(expanded_callee), callee: Box::new(expanded_callee),
args: expanded_args, args: Box::new(expanded_args),
}, },
ty: (), ty: (),
}) })
@@ -131,12 +133,16 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
self.registry.push(); self.registry.push();
let expanded_params = self.expand_recursive(*params)?;
let expanded_body = self.expand_recursive(Node::clone(&body))?; let expanded_body = self.expand_recursive(Node::clone(&body))?;
self.registry.pop(); self.registry.pop();
Ok(Node { Ok(Node {
identity: node.identity, 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: (), ty: (),
}) })
} }
@@ -242,7 +248,11 @@ impl<E: MacroEvaluator> MacroExpander<E> {
kind: UntypedKind::Identifier(Symbol::from(name)), kind: UntypedKind::Identifier(Symbol::from(name)),
ty: (), ty: (),
}), }),
args: bindings.into_values().collect(), args: Box::new(Node {
identity: identity.clone(),
kind: UntypedKind::Tuple { elements: bindings.into_values().collect() },
ty: (),
}),
}, },
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> { fn expand_template(&self, node: Node<UntypedKind>, state: &mut ExpansionState) -> Result<Node<UntypedKind>, String> {
match node.kind { match node.kind {
UntypedKind::Identifier(mut sym) => { UntypedKind::Identifier(mut sym) => {
@@ -265,6 +289,11 @@ impl<E: MacroEvaluator> MacroExpander<E> {
Ok(Node { identity: node.identity, kind: UntypedKind::Identifier(sym), ty: () }) 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) => { UntypedKind::Placeholder(inner) => {
// Break out of template for substitution/evaluation // Break out of template for substitution/evaluation
if let UntypedKind::Identifier(ref sym) = inner.kind if let UntypedKind::Identifier(ref sym) = inner.kind
@@ -353,18 +382,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Call { callee, args } => { UntypedKind::Call { callee, args } => {
let expanded_callee = self.expand_template(*callee, state)?; let expanded_callee = self.expand_template(*callee, state)?;
let mut expanded_args = Vec::new(); let expanded_args = self.expand_template(*args, state)?;
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)?);
}
}
Ok(Node { Ok(Node {
identity: node.identity, 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: (), ty: (),
}) })
} }
@@ -384,14 +405,12 @@ impl<E: MacroEvaluator> MacroExpander<E> {
}) })
} }
UntypedKind::Lambda { mut params, body } => { UntypedKind::Lambda { params, body } => {
for p in &mut params { let expanded_params = self.expand_template(*params, state)?;
p.context = Some(state.expansion_id.clone());
}
let body = self.expand_template(Node::clone(&body), state)?; let body = self.expand_template(Node::clone(&body), state)?;
Ok(Node { Ok(Node {
identity: node.identity, identity: node.identity,
kind: UntypedKind::Lambda { params, body: Rc::new(body) }, kind: UntypedKind::Lambda { params: Box::new(expanded_params), body: Rc::new(body) },
ty: (), ty: (),
}) })
} }
+45 -22
View File
@@ -49,13 +49,13 @@ impl Specializer {
fn visit_node(&self, node: TypedNode) -> TypedNode { fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind { let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone()); 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) (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
}, },
BoundKind::TailCall { callee, args } => { BoundKind::TailCall { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, args, node.ty.clone()); 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) (BoundKind::TailCall { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
}, },
// Recursive traversal for other nodes // Recursive traversal for other nodes
@@ -69,9 +69,10 @@ impl Specializer {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty) (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())); 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 } => { BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.visit_node(*value)); 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 // 1. Specialize children first
let new_callee = self.visit_node(callee); 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)) // 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { 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 // 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)) { if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
// Cannot specialize with unknown types // Cannot specialize with unknown types
return (new_callee, new_args, original_ty); return (new_callee, new_args, original_ty);
@@ -139,7 +145,7 @@ impl Specializer {
identity: new_callee.identity.clone(), identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()), kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature { ty: StaticType::Function(Box::new(Signature {
params: arg_types, params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(), ret: ret_ty.clone(),
})), })),
}; };
@@ -158,7 +164,7 @@ impl Specializer {
identity: new_callee.identity.clone(), identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()), kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature { ty: StaticType::Function(Box::new(Signature {
params: arg_types, params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(), ret: ret_ty.clone(),
})), })),
}; };
@@ -192,7 +198,7 @@ impl Specializer {
identity: new_callee.identity.clone(), identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val), kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature { ty: StaticType::Function(Box::new(Signature {
params: arg_types, params: StaticType::Tuple(arg_types),
ret: res_ty.clone(), ret: res_ty.clone(),
})), })),
}; };
@@ -262,7 +268,19 @@ mod tests {
let func_node = BoundNode { let func_node = BoundNode {
identity: make_identity(), identity: make_identity(),
kind: BoundKind::Lambda { 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![], upvalues: vec![],
body: Rc::new(BoundNode { identity: make_identity(), kind: BoundKind::Nop, ty: () }) 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); 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 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 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( 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 StaticType::Any
); );
@@ -310,12 +329,13 @@ mod tests {
let name0 = Symbol::from("f"); let name0 = Symbol::from("f");
let name1 = Symbol::from("x"); let name1 = Symbol::from("x");
// Call(Get(Local(0)), [Get(Local(1))]) where arg is Any // 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: vec![StaticType::Any], ret: StaticType::Void }))); 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 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( 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 StaticType::Void
); );
@@ -349,12 +369,13 @@ mod tests {
spec.cache.borrow_mut().insert(key, (specialized_val.clone(), ret_ty.clone())); 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 callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); 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( 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 StaticType::Any
); );
@@ -393,9 +414,10 @@ mod tests {
let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any); let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); 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( 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 StaticType::Any
); );
@@ -431,10 +453,11 @@ mod tests {
let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any); let callee = make_typed_node(BoundKind::Get { addr, name }, StaticType::Any);
let arg = make_typed_node(BoundKind::Constant(Value::Int(1)), StaticType::Int); 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 // Use TailCall here
let call_node = make_typed_node( 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 StaticType::Any
); );
+3 -3
View File
@@ -16,7 +16,7 @@ impl TCO {
match node.kind { match node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let new_callee = Box::new(Self::transform(*callee, false)); 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 { if is_tail_position {
Node { 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. // 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)); let new_body = Rc::new(Self::transform((*body).clone(), true));
Node { Node {
kind: BoundKind::Lambda { kind: BoundKind::Lambda {
param_count, params,
upvalues, upvalues,
body: new_body, 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> { pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
match node.kind { match node.kind {
BoundKind::Lambda { param_count, upvalues, body } => { BoundKind::Lambda { params, upvalues, body } => {
// 1. Determine types of captured variables (Root lambdas have none) // 1. Determine types of captured variables (Root lambdas have none)
let mut upvalue_types = Vec::with_capacity(upvalues.len()); let mut upvalue_types = Vec::with_capacity(upvalues.len());
for _ in &upvalues { for _ in &upvalues {
@@ -57,35 +57,33 @@ impl TypeChecker {
// 2. Create the specialized context // 2. Create the specialized context
let root_ctx = TypeContext::new(0, vec![], None); 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 // 3. INJECT specialized argument types into slots
for (i, ty) in arg_types.iter().enumerate() { let arg_tuple_ty = if arg_types.is_empty() {
if (i as u32) < param_count { StaticType::Any // No specialized info
lambda_ctx.set_local_type(i as u32, ty.clone()); } 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 // 4. Check body with the new types
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let ret_ty = body_typed.ty.clone(); let ret_ty = body_typed.ty.clone();
// 5. Construct specialized function type // 5. Construct specialized function type
let final_params = if arg_types.is_empty() { let final_params_ty = params_typed.ty.clone();
vec![StaticType::Any; param_count as usize]
} else {
arg_types.to_vec()
};
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
params: final_params, params: final_params_ty,
ret: ret_ty, ret: ret_ty,
})); }));
Ok(Node { Ok(Node {
identity: node.identity, identity: node.identity,
kind: BoundKind::Lambda { kind: BoundKind::Lambda {
param_count, params: Box::new(params_typed),
upvalues, upvalues,
body: Rc::new(body_typed) body: Rc::new(body_typed)
}, },
@@ -97,7 +95,11 @@ impl TypeChecker {
let virtual_lambda = BoundNode { let virtual_lambda = BoundNode {
identity: node.identity.clone(), identity: node.identity.clone(),
kind: BoundKind::Lambda { kind: BoundKind::Lambda {
param_count: 0, params: Box::new(Node {
identity: node.identity.clone(),
kind: BoundKind::Tuple { elements: vec![] },
ty: (),
}),
upvalues: vec![], upvalues: vec![],
body: Rc::new(node) 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> { fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result<TypedNode, String> {
let (kind, ty) = match node.kind { let (kind, ty) = match node.kind {
BoundKind::Nop => (BoundKind::Nop, StaticType::Void), BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
@@ -117,6 +151,10 @@ impl TypeChecker {
(BoundKind::Constant(v), ty) (BoundKind::Constant(v), ty)
}, },
BoundKind::Parameter { name, slot } => {
(BoundKind::Parameter { name, slot }, ctx.get_type(Address::Local(slot)))
},
BoundKind::Get { addr, name } => { BoundKind::Get { addr, name } => {
let ty = if let Address::Global(idx) = addr { let ty = if let Address::Global(idx) = addr {
self.global_types.borrow().get(&idx).cloned().unwrap_or(StaticType::Any) 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::Block { exprs: typed_exprs }, last_ty)
}, },
BoundKind::Lambda { param_count, upvalues, body } => { BoundKind::Lambda { params, upvalues, body } => {
// 1. Determine types of captured variables // 1. Determine types of captured variables
let mut upvalue_types = Vec::with_capacity(upvalues.len()); let mut upvalue_types = Vec::with_capacity(upvalues.len());
for &addr in &upvalues { for &addr in &upvalues {
@@ -202,20 +240,21 @@ impl TypeChecker {
} }
// 2. Create nested context for lambda body // 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 body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let ret_ty = body_typed.ty.clone(); 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 { 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, ret: ret_ty,
})); }));
(BoundKind::Lambda { (BoundKind::Lambda {
param_count, params: Box::new(params_typed),
upvalues, upvalues,
body: Rc::new(body_typed) body: Rc::new(body_typed)
}, fn_ty) }, fn_ty)
@@ -223,17 +262,13 @@ impl TypeChecker {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let callee_typed = self.check_node(*callee, ctx)?; let callee_typed = self.check_node(*callee, ctx)?;
let mut typed_args = Vec::new(); let args_typed = self.check_node(*args, ctx)?;
for arg in args {
typed_args.push(self.check_node(arg, ctx)?);
}
let arg_types: Vec<_> = typed_args.iter().map(|a| a.ty.clone()).collect(); let ret_ty = callee_typed.ty.resolve_call(&args_typed.ty).unwrap_or(StaticType::Any);
let ret_ty = callee_typed.ty.resolve_call(&arg_types).unwrap_or(StaticType::Any);
(BoundKind::Call { (BoundKind::Call {
callee: Box::new(callee_typed), callee: Box::new(callee_typed),
args: typed_args args: Box::new(args_typed)
}, ret_ty) }, ret_ty)
}, },
+25 -8
View File
@@ -19,6 +19,20 @@ impl UpvalueAnalyzer {
.collect() .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( fn visit(
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
scopes: &mut Vec<HashMap<Symbol, Identity>>, scopes: &mut Vec<HashMap<Symbol, Identity>>,
@@ -50,15 +64,20 @@ impl UpvalueAnalyzer {
} }
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
let mut new_scope = HashMap::new(); let mut new_scope = HashMap::new();
for p in params { Self::visit_params(params, &mut new_scope);
new_scope.insert(p.clone(), node.identity.clone());
}
scopes.push(new_scope); scopes.push(new_scope);
// The current node is the lambda causing captures in its body // The current node is the lambda causing captures in its body
Self::visit(body, scopes, capture_map, Some(node.identity.clone())); Self::visit(body, scopes, capture_map, Some(node.identity.clone()));
scopes.pop(); 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 } => { UntypedKind::If { cond, then_br, else_br } => {
Self::visit(cond, scopes, capture_map, current_lambda.clone()); Self::visit(cond, scopes, capture_map, current_lambda.clone());
Self::visit(then_br, 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 } => { UntypedKind::Call { callee, args } => {
Self::visit(callee, scopes, capture_map, current_lambda.clone()); Self::visit(callee, scopes, capture_map, current_lambda.clone());
for arg in args { Self::visit(args, scopes, capture_map, current_lambda.clone());
Self::visit(arg, scopes, capture_map, current_lambda.clone());
}
} }
UntypedKind::Block { exprs } => { UntypedKind::Block { exprs } => {
for expr in exprs { for expr in exprs {
@@ -95,10 +112,10 @@ impl UpvalueAnalyzer {
UntypedKind::Expansion { call: _, expanded } => { UntypedKind::Expansion { call: _, expanded } => {
Self::visit(expanded, scopes, capture_map, current_lambda); 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); 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, Nop,
Constant(Value), Constant(Value),
Identifier(Symbol), Identifier(Symbol),
Parameter(Symbol),
If { If {
cond: Box<Node<UntypedKind>>, cond: Box<Node<UntypedKind>>,
then_br: Box<Node<UntypedKind>>, then_br: Box<Node<UntypedKind>>,
@@ -72,12 +73,12 @@ pub enum UntypedKind {
value: Box<Node<UntypedKind>>, value: Box<Node<UntypedKind>>,
}, },
Lambda { Lambda {
params: Vec<Symbol>, params: Box<Node<UntypedKind>>,
body: Rc<Node<UntypedKind>>, body: Rc<Node<UntypedKind>>,
}, },
Call { Call {
callee: Box<Node<UntypedKind>>, callee: Box<Node<UntypedKind>>,
args: Vec<Node<UntypedKind>>, args: Box<Node<UntypedKind>>,
}, },
Block { Block {
exprs: Vec<Node<UntypedKind>>, exprs: Vec<Node<UntypedKind>>,
@@ -91,7 +92,7 @@ pub enum UntypedKind {
/// A macro declaration that can be expanded at compile time. /// A macro declaration that can be expanded at compile time.
MacroDecl { MacroDecl {
name: Symbol, name: Symbol,
params: Vec<Symbol>, params: Box<Node<UntypedKind>>,
body: Box<Node<UntypedKind>>, body: Box<Node<UntypedKind>>,
}, },
/// A template for AST nodes, allowing for substitutions. (Quasiquote) /// A template for AST nodes, allowing for substitutions. (Quasiquote)
+38 -15
View File
@@ -38,8 +38,12 @@ impl<'a> Parser<'a> {
Ok(Node { Ok(Node {
identity: identity.clone(), identity: identity.clone(),
kind: UntypedKind::Call { kind: UntypedKind::Call {
callee: Box::new(self.make_id_node("quote", identity)), callee: Box::new(self.make_id_node("quote", identity.clone())),
args: vec![expr], args: Box::new(Node {
identity,
kind: UntypedKind::Tuple { elements: vec![expr] },
ty: (),
}),
}, },
ty: (), ty: (),
}) })
@@ -186,7 +190,7 @@ impl<'a> Parser<'a> {
} }
fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> { 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()?; let body = self.parse_expression()?;
Ok(Node { Ok(Node {
@@ -205,7 +209,7 @@ impl<'a> Parser<'a> {
UntypedKind::Identifier(sym) => sym, UntypedKind::Identifier(sym) => sym,
_ => return Err("Expected identifier for macro name".to_string()), _ => 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()?; let body = self.parse_expression()?;
Ok(Node { Ok(Node {
identity, 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 { if *self.peek() != TokenKind::LeftBracket {
return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); 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 { while *self.peek() != TokenKind::RightBracket {
let token = self.advance()?; let next_token = self.advance()?;
match token.kind { let p_identity = Rc::new(NodeIdentity { location: next_token.location });
TokenKind::Identifier(name) => params.push(name.into()), match next_token.kind {
_ => return Err(format!("Expected identifier in param vector, found {:?}", 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)?; 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> { 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 { 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 { Ok(Node {
identity, identity,
kind: UntypedKind::Call { callee: Box::new(callee), args }, kind: UntypedKind::Call { callee: Box::new(callee), args: Box::new(args_node) },
ty: (), ty: (),
}) })
} }
+25 -25
View File
@@ -23,10 +23,10 @@ fn register_constants(env: &Environment) {
fn register_arithmetic(env: &Environment) { fn register_arithmetic(env: &Environment) {
// --- Add (+) --- // --- Add (+) ---
let add_ty = StaticType::FunctionOverloads(vec![ let add_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text }, Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
]); ]);
env.register_native("+", add_ty, |args| { env.register_native("+", add_ty, |args| {
if args.len() == 2 { if args.len() == 2 {
@@ -56,12 +56,12 @@ fn register_arithmetic(env: &Environment) {
// --- Subtract (-) --- // --- Subtract (-) ---
let sub_ty = StaticType::FunctionOverloads(vec![ let sub_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
Signature { params: vec![StaticType::Int], ret: StaticType::Int }, // Negation Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, // Negation
Signature { params: vec![StaticType::Float], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Int }, Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int },
Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime },
]); ]);
env.register_native("-", sub_ty, |args| { env.register_native("-", sub_ty, |args| {
if args.is_empty() { return Value::Void; } if args.is_empty() { return Value::Void; }
@@ -98,8 +98,8 @@ fn register_arithmetic(env: &Environment) {
// --- Multiply (*) --- // --- Multiply (*) ---
let mul_ty = StaticType::FunctionOverloads(vec![ let mul_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
]); ]);
env.register_native("*", mul_ty, |args| { env.register_native("*", mul_ty, |args| {
if args.len() == 2 { if args.len() == 2 {
@@ -122,8 +122,8 @@ fn register_arithmetic(env: &Environment) {
// --- Divide (/) --- // --- Divide (/) ---
let div_ty = StaticType::FunctionOverloads(vec![ let div_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float },
]); ]);
env.register_native("/", div_ty, |args| { env.register_native("/", div_ty, |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
@@ -134,7 +134,7 @@ fn register_arithmetic(env: &Environment) {
// --- Integer Divide (//) --- // --- Integer Divide (//) ---
let int_div_ty = StaticType::Function(Box::new( 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| { env.register_native("//", int_div_ty, |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
@@ -150,7 +150,7 @@ fn register_arithmetic(env: &Environment) {
// --- Modulus (%) --- // --- Modulus (%) ---
let mod_ty = StaticType::Function(Box::new( 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| { env.register_native("%", mod_ty, |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
@@ -165,9 +165,9 @@ fn register_arithmetic(env: &Environment) {
fn register_comparison(env: &Environment) { fn register_comparison(env: &Environment) {
let cmp_ty = StaticType::FunctionOverloads(vec![ let cmp_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool }, Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Bool },
Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool }, Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Bool },
Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Bool }, Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Bool },
]); ]);
// --- Greater Than (>) --- // --- Greater Than (>) ---
@@ -224,7 +224,7 @@ fn register_comparison(env: &Environment) {
// --- Equal (=) --- // --- Equal (=) ---
let eq_ty = StaticType::Function(Box::new( 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| { env.register_native("=", eq_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
@@ -265,8 +265,8 @@ fn register_comparison(env: &Environment) {
fn register_logic(env: &Environment) { fn register_logic(env: &Environment) {
// --- Not (not) --- // --- Not (not) ---
let not_ty = StaticType::FunctionOverloads(vec![ let not_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Bool], ret: StaticType::Bool }, Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool },
Signature { params: vec![StaticType::Int], ret: StaticType::Int }, Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int },
]); ]);
env.register_native("not", not_ty, |args| { env.register_native("not", not_ty, |args| {
if args.len() != 1 { return Value::Void; } if args.len() != 1 { return Value::Void; }
@@ -279,8 +279,8 @@ fn register_logic(env: &Environment) {
// --- And (and) --- // --- And (and) ---
let logic_op_ty = StaticType::FunctionOverloads(vec![ let logic_op_ty = StaticType::FunctionOverloads(vec![
Signature { params: vec![StaticType::Bool, StaticType::Bool], ret: StaticType::Bool }, Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool },
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("and", logic_op_ty.clone(), |args| { env.register_native("and", logic_op_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
@@ -313,7 +313,7 @@ fn register_logic(env: &Environment) {
// --- Shift Left (<<) --- // --- Shift Left (<<) ---
let shift_ty = StaticType::Function(Box::new( 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| { env.register_native("<<", shift_ty.clone(), |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 { return Value::Void; }
+1 -1
View File
@@ -4,7 +4,7 @@ use chrono::{NaiveDate, NaiveDateTime};
pub fn register(env: &Environment) { pub fn register(env: &Environment) {
let date_ty = StaticType::Function(Box::new(Signature { let date_ty = StaticType::Function(Box::new(Signature {
params: vec![StaticType::Text], params: StaticType::Tuple(vec![StaticType::Text]),
ret: StaticType::DateTime 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 { pub fn method(mut self, name: &str, params: Vec<StaticType>, ret: StaticType) -> Self {
let key = Keyword::intern(name); 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.fields.insert(key, sig);
self self
} }
+6 -18
View File
@@ -75,7 +75,7 @@ pub enum Value {
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Signature { pub struct Signature {
pub params: Vec<StaticType>, pub params: StaticType,
pub ret: StaticType, pub ret: StaticType,
} }
@@ -137,12 +137,7 @@ impl fmt::Display for StaticType {
write!(f, "}}") write!(f, "}}")
}, },
StaticType::Function(sig) => { StaticType::Function(sig) => {
write!(f, "fn(")?; write!(f, "fn({}) -> {}", sig.params, sig.ret)
for (i, p) in sig.params.iter().enumerate() {
if i > 0 { write!(f, " ")?; }
write!(f, "{}", p)?;
}
write!(f, ") -> {}", sig.ret)
}, },
StaticType::FunctionOverloads(sigs) => { StaticType::FunctionOverloads(sigs) => {
write!(f, "overloads({} variants)", sigs.len()) 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. /// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type.
pub fn resolve_call(&self, arg_types: &[StaticType]) -> Option<StaticType> { pub fn resolve_call(&self, args_ty: &StaticType) -> Option<StaticType> {
match self { match self {
StaticType::Any => Some(StaticType::Any), StaticType::Any => Some(StaticType::Any),
StaticType::Function(sig) => { StaticType::Function(sig) => {
if self.match_params(&sig.params, arg_types) { if sig.params.is_assignable_from(args_ty) {
Some(sig.ret.clone()) Some(sig.ret.clone())
} else { } else {
None None
@@ -193,20 +188,13 @@ impl StaticType {
} }
StaticType::FunctionOverloads(sigs) => { StaticType::FunctionOverloads(sigs) => {
sigs.iter() 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()) .map(|sig| sig.ret.clone())
} }
_ => None, _ => 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.) /// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.)
pub fn is_scalar_pure(&self) -> bool { pub fn is_scalar_pure(&self) -> bool {
match self { match self {
+56 -9
View File
@@ -102,6 +102,8 @@ macro_rules! dispatch_eval {
BoundKind::Nop => Ok(Value::Void), BoundKind::Nop => Ok(Value::Void),
BoundKind::Constant(v) => Ok(v.clone()), BoundKind::Constant(v) => Ok(v.clone()),
BoundKind::Parameter { .. } => Ok(Value::Void),
BoundKind::DefGlobal { global_index, value, .. } => { BoundKind::DefGlobal { global_index, value, .. } => {
let val = $self.$eval_method($($observer,)? value)?; let val = $self.$eval_method($($observer,)? value)?;
let idx = *global_index as usize; let idx = *global_index as usize;
@@ -161,7 +163,10 @@ macro_rules! dispatch_eval {
Ok(last) 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()); let mut captured = Vec::with_capacity(upvalues.len());
for addr in upvalues { for addr in upvalues {
captured.push($self.capture_upvalue(*addr)?); captured.push($self.capture_upvalue(*addr)?);
@@ -177,9 +182,28 @@ macro_rules! dispatch_eval {
BoundKind::TailCall { callee, args } => { BoundKind::TailCall { callee, args } => {
let func_val = $self.$eval_method($($observer,)? callee)?; let func_val = $self.$eval_method($($observer,)? callee)?;
let mut arg_vals = Vec::with_capacity(args.len());
for arg in args { // PERFORMANCE OPTIMIZATION: "Everything is a Tuple" Unification
arg_vals.push($self.$eval_method($($observer,)? arg)?); // 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 { match func_val {
@@ -192,9 +216,24 @@ macro_rules! dispatch_eval {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let mut func_val = $self.$eval_method($($observer,)? callee)?; let mut func_val = $self.$eval_method($($observer,)? callee)?;
let mut arg_vals = Vec::with_capacity(args.len()); // PERFORMANCE OPTIMIZATION: Same as in TailCall above.
for arg in args { // Short-circuiting the Tuple -> Value::List -> Vec conversion to save heap cycles.
arg_vals.push($self.$eval_method($($observer,)? arg)?); 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 { loop {
@@ -542,7 +581,11 @@ mod tests {
identity: id.clone(), identity: id.clone(),
ty: StaticType::Any, ty: StaticType::Any,
kind: BoundKind::Lambda { 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 upvalues: vec![Address::Local(0)], // Capture x
body: Rc::new(lambda_body), body: Rc::new(lambda_body),
}, },
@@ -559,7 +602,11 @@ mod tests {
ty: StaticType::Any, ty: StaticType::Any,
kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") }, 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) // return x (Local 0)
+88 -32
View File
@@ -1,7 +1,7 @@
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
use regex::Regex;
use std::fs; use std::fs;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use regex::Regex;
pub struct TestResult { pub struct TestResult {
pub name: String, pub name: String,
@@ -28,7 +28,7 @@ pub fn run_functional_tests() -> Vec<TestResult> {
if path.extension().is_some_and(|ext| ext == "myc") { if path.extension().is_some_and(|ext| ext == "myc") {
let content = fs::read_to_string(&path).unwrap(); let content = fs::read_to_string(&path).unwrap();
let name = path.file_name().unwrap().to_string_lossy().to_string(); let name = path.file_name().unwrap().to_string_lossy().to_string();
let expected_output = output_re let expected_output = output_re
.captures(&content) .captures(&content)
.map(|m| m.get(1).unwrap().as_str().trim().to_string()); .map(|m| m.get(1).unwrap().as_str().trim().to_string());
@@ -38,12 +38,24 @@ pub fn run_functional_tests() -> Vec<TestResult> {
Ok(val) => { Ok(val) => {
let val_str = format!("{}", val); let val_str = format!("{}", val);
if val_str == expected { 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 { } 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<BenchmarkResult> {
if path.extension().is_some_and(|ext| ext == "myc") { if path.extension().is_some_and(|ext| ext == "myc") {
let content = fs::read_to_string(&path).unwrap(); let content = fs::read_to_string(&path).unwrap();
let name = path.file_name().unwrap().to_string_lossy().to_string(); let name = path.file_name().unwrap().to_string_lossy().to_string();
let baseline_match = baseline_re.captures(&content); let baseline_match = baseline_re.captures(&content);
// Prepare: Compile and Link once outside the measurement loop // Prepare: Compile and Link once outside the measurement loop
let linked_node = match env.compile(&content).map(|c| env.link(c)) { let linked_node = match env.compile(&content).map(|c| env.link(c)) {
Ok(node) => node, Ok(node) => node,
@@ -85,13 +97,25 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
if update { if update {
let new_val = format_duration(median); let new_val = format_duration(median);
let updated_content = if let Some(m) = baseline_match { 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 { } else {
format!(";; Benchmark: {} format!(
{}", new_val, content) ";; Benchmark: {}
{}",
new_val, content
)
}; };
fs::write(&path, updated_content).unwrap(); 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 { } else if let Some(m) = baseline_match {
let baseline_str = m.get(1).unwrap().as_str(); let baseline_str = m.get(1).unwrap().as_str();
let baseline = parse_duration(baseline_str).unwrap(); let baseline = parse_duration(baseline_str).unwrap();
@@ -108,13 +132,29 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
} }
let diff = (median.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0; 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 threshold = if is_release { 0.15 } else { 0.5 };
let status = if median > baseline && diff > threshold { "FAILED" } else { "OK" }; let status = if median > baseline && diff > threshold {
"FAILED"
results.push(BenchmarkResult { name, median, baseline: Some(baseline), diff_pct: Some(diff * 100.0), status: status.to_string() }); } else {
"OK"
};
results.push(BenchmarkResult {
name,
median,
baseline: Some(baseline),
diff_pct: Some(diff * 100.0),
status: status.to_string(),
});
} else { } 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<BenchmarkResult> {
} }
fn format_duration(d: Duration) -> String { fn format_duration(d: Duration) -> String {
if d.as_nanos() < 1000 { format!("{}ns", d.as_nanos()) } if d.as_nanos() < 1000 {
else if d.as_micros() < 1000 { format!("{:.1}us", d.as_nanos() as f64 / 1000.0) } format!("{}ns", d.as_nanos())
else if d.as_millis() < 1000 { format!("{:.1}ms", d.as_micros() as f64 / 1000.0) } } else if d.as_micros() < 1000 {
else { format!("{:.1}s", d.as_millis() as f64 / 1000.0) } 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> { fn parse_duration(s: &str) -> Option<Duration> {
use std::sync::OnceLock; use std::sync::OnceLock;
static RE: OnceLock<Regex> = OnceLock::new(); static RE: OnceLock<Regex> = OnceLock::new();
let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap()); let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap());
let caps = re.captures(s)?; let caps = re.captures(s)?;
let val: f64 = caps[1].parse().ok()?; let val: f64 = caps[1].parse().ok()?;
let unit = &caps[2]; let unit = &caps[2];
@@ -156,29 +201,40 @@ mod tests {
for _ in 0..5 { for _ in 0..5 {
let results = run_benchmarks(false); let results = run_benchmarks(false);
let failures: Vec<_> = results.iter() let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect();
.filter(|r| r.status == "FAILED")
.collect();
if failures.is_empty() { if failures.is_empty() {
success = true; success = true;
break; break;
} }
last_failures = failures.iter() last_failures = failures
.map(|r| format!("{}: Median {:?}, Baseline {:?}, Diff {:.2}%", .iter()
r.name, r.median, r.baseline.unwrap_or_default(), r.diff_pct.unwrap_or(0.0))) .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<_>>() .collect::<Vec<_>>()
.join(" .join(
"); "
",
);
// Give the system a tiny bit of breath between retries // Give the system a tiny bit of breath between retries
std::thread::sleep(std::time::Duration::from_millis(50)); std::thread::sleep(std::time::Duration::from_millis(50));
} }
if !success { if !success {
panic!("Performance regression detected after 5 attempts: panic!(
{}", last_failures); "Performance regression detected after 5 attempts:
{}",
last_failures
);
} }
} }
} }