78dff07930
The `TailCall` variant has been merged into `Call` by adding an `is_tail` boolean field. This simplifies the AST by reducing the number of node variants and makes it easier to handle tail call optimization. The `tco.rs` module is responsible for setting this flag when a call occurs in tail position.
124 lines
4.8 KiB
Rust
124 lines
4.8 KiB
Rust
use std::rc::Rc;
|
|
use crate::ast::nodes::Node;
|
|
use crate::ast::compiler::bound_nodes::BoundKind;
|
|
|
|
pub struct TCO;
|
|
|
|
impl TCO {
|
|
pub fn optimize<T: Clone>(node: Node<BoundKind<T>, T>) -> Node<BoundKind<T>, T> {
|
|
// Top-level starts NOT in tail position usually, unless the script result is returned immediately
|
|
// which implies tail position for the script body if viewed as a function.
|
|
// Let's assume standard execution context (not tail call) for the script root.
|
|
Self::transform(node, false)
|
|
}
|
|
|
|
fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
|
|
match node.kind {
|
|
BoundKind::Call { callee, args, .. } => {
|
|
let new_callee = Box::new(Self::transform(*callee, false));
|
|
let new_args = Box::new(Self::transform(*args, false));
|
|
|
|
Node {
|
|
kind: BoundKind::Call {
|
|
callee: new_callee,
|
|
args: new_args,
|
|
is_tail: is_tail_position,
|
|
},
|
|
..node
|
|
}
|
|
},
|
|
|
|
BoundKind::If { cond, then_br, else_br } => {
|
|
let new_cond = Box::new(Self::transform(*cond, false));
|
|
let new_then = Box::new(Self::transform(*then_br, is_tail_position));
|
|
let new_else = else_br.map(|e| Box::new(Self::transform(*e, is_tail_position)));
|
|
|
|
Node {
|
|
kind: BoundKind::If {
|
|
cond: new_cond,
|
|
then_br: new_then,
|
|
else_br: new_else,
|
|
},
|
|
..node
|
|
}
|
|
},
|
|
|
|
BoundKind::Block { exprs } => {
|
|
if exprs.is_empty() {
|
|
return Node {
|
|
kind: BoundKind::Block { exprs },
|
|
..node
|
|
};
|
|
}
|
|
|
|
let last_idx = exprs.len() - 1;
|
|
let mut new_exprs = Vec::with_capacity(exprs.len());
|
|
|
|
for (i, expr) in exprs.into_iter().enumerate() {
|
|
let is_last = i == last_idx;
|
|
// Only the last expression inherits the tail context
|
|
new_exprs.push(Self::transform(expr, is_tail_position && is_last));
|
|
}
|
|
|
|
Node {
|
|
kind: BoundKind::Block { exprs: new_exprs },
|
|
..node
|
|
}
|
|
},
|
|
|
|
BoundKind::Lambda { params, upvalues, body, positional_count } => {
|
|
// 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 {
|
|
params: params.clone(),
|
|
upvalues,
|
|
body: new_body,
|
|
positional_count,
|
|
},
|
|
..node
|
|
}
|
|
},
|
|
|
|
BoundKind::Set { addr, value } => {
|
|
Node {
|
|
kind: BoundKind::Set { addr, value: Box::new(Self::transform(*value, false)) },
|
|
..node
|
|
}
|
|
},
|
|
BoundKind::DefLocal { name, slot, value, captured_by } => {
|
|
Node {
|
|
kind: BoundKind::DefLocal { name, slot, value: Box::new(Self::transform(*value, false)), captured_by },
|
|
..node
|
|
}
|
|
},
|
|
BoundKind::DefGlobal { name, global_index, value } => {
|
|
Node {
|
|
kind: BoundKind::DefGlobal { name, global_index, value: Box::new(Self::transform(*value, false)) },
|
|
..node
|
|
}
|
|
},
|
|
BoundKind::Record { fields } => {
|
|
let new_fields = fields.into_iter().map(|(k, v)| {
|
|
(Self::transform(k, false), Self::transform(v, false))
|
|
}).collect();
|
|
Node {
|
|
kind: BoundKind::Record { fields: new_fields },
|
|
..node
|
|
}
|
|
},
|
|
BoundKind::Tuple { elements } => {
|
|
let new_elements = elements.into_iter().map(|e| Self::transform(e, false)).collect();
|
|
Node {
|
|
kind: BoundKind::Tuple { elements: new_elements },
|
|
..node
|
|
}
|
|
},
|
|
|
|
// Atoms and variables don't change
|
|
_ => node,
|
|
}
|
|
}
|
|
}
|