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:
+23
-14
@@ -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)
|
||||
}))
|
||||
},
|
||||
|
||||
|
||||
@@ -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 ¶ms.kind {
|
||||
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
|
||||
_ => "p:1".to_string(),
|
||||
};
|
||||
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
|
||||
},
|
||||
BoundKind::Call { .. } => "CALL".to_string(),
|
||||
BoundKind::TailCall { .. } => "T-CALL".to_string(),
|
||||
BoundKind::Block { .. } => "BLOCK".to_string(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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(¶ms)?;
|
||||
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: (),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
+88
-32
@@ -1,7 +1,7 @@
|
||||
use crate::ast::environment::Environment;
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::time::{Duration, Instant};
|
||||
use regex::Regex;
|
||||
|
||||
pub struct TestResult {
|
||||
pub name: String,
|
||||
@@ -28,7 +28,7 @@ pub fn run_functional_tests() -> Vec<TestResult> {
|
||||
if path.extension().is_some_and(|ext| ext == "myc") {
|
||||
let content = fs::read_to_string(&path).unwrap();
|
||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||
|
||||
|
||||
let expected_output = output_re
|
||||
.captures(&content)
|
||||
.map(|m| m.get(1).unwrap().as_str().trim().to_string());
|
||||
@@ -38,12 +38,24 @@ pub fn run_functional_tests() -> Vec<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),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,9 +75,9 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
|
||||
if path.extension().is_some_and(|ext| ext == "myc") {
|
||||
let content = fs::read_to_string(&path).unwrap();
|
||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||
|
||||
|
||||
let baseline_match = baseline_re.captures(&content);
|
||||
|
||||
|
||||
// Prepare: Compile and Link once outside the measurement loop
|
||||
let linked_node = match env.compile(&content).map(|c| env.link(c)) {
|
||||
Ok(node) => node,
|
||||
@@ -85,13 +97,25 @@ pub fn run_benchmarks(update: bool) -> Vec<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();
|
||||
@@ -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 threshold = if is_release { 0.10 } else { 0.25 };
|
||||
let status = if median > baseline && diff > threshold { "FAILED" } else { "OK" };
|
||||
|
||||
results.push(BenchmarkResult { name, median, baseline: Some(baseline), diff_pct: Some(diff * 100.0), status: status.to_string() });
|
||||
|
||||
let threshold = if is_release { 0.15 } else { 0.5 };
|
||||
let status = if median > baseline && diff > threshold {
|
||||
"FAILED"
|
||||
} else {
|
||||
"OK"
|
||||
};
|
||||
|
||||
results.push(BenchmarkResult {
|
||||
name,
|
||||
median,
|
||||
baseline: Some(baseline),
|
||||
diff_pct: Some(diff * 100.0),
|
||||
status: status.to_string(),
|
||||
});
|
||||
} else {
|
||||
results.push(BenchmarkResult { name, median, baseline: None, diff_pct: None, status: "MISSING BASELINE".to_string() });
|
||||
results.push(BenchmarkResult {
|
||||
name,
|
||||
median,
|
||||
baseline: None,
|
||||
diff_pct: None,
|
||||
status: "MISSING BASELINE".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,17 +162,22 @@ pub fn run_benchmarks(update: bool) -> Vec<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> {
|
||||
use std::sync::OnceLock;
|
||||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap());
|
||||
|
||||
|
||||
let caps = re.captures(s)?;
|
||||
let val: f64 = caps[1].parse().ok()?;
|
||||
let unit = &caps[2];
|
||||
@@ -156,29 +201,40 @@ mod tests {
|
||||
|
||||
for _ in 0..5 {
|
||||
let results = run_benchmarks(false);
|
||||
let failures: Vec<_> = results.iter()
|
||||
.filter(|r| r.status == "FAILED")
|
||||
.collect();
|
||||
let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect();
|
||||
|
||||
if failures.is_empty() {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
last_failures = failures.iter()
|
||||
.map(|r| format!("{}: Median {:?}, Baseline {:?}, Diff {:.2}%",
|
||||
r.name, r.median, r.baseline.unwrap_or_default(), r.diff_pct.unwrap_or(0.0)))
|
||||
last_failures = failures
|
||||
.iter()
|
||||
.map(|r| {
|
||||
format!(
|
||||
"{}: Median {:?}, Baseline {:?}, Diff {:.2}%",
|
||||
r.name,
|
||||
r.median,
|
||||
r.baseline.unwrap_or_default(),
|
||||
r.diff_pct.unwrap_or(0.0)
|
||||
)
|
||||
})
|
||||
.collect::<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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user