feat: Implement AST macros and templates
This commit introduces support for AST macros and templates, enabling users to define and use custom, reusable components within the visual DSL. Key changes include: - A new `MacroRegistry` to manage macro definitions. - A `MacroExpander` to process macro calls and expand templates. - A `MacroEvaluator` trait for evaluating expressions during expansion. - The `Expansion` node in `BoundKind` to preserve the original macro call and its expanded form for debugging. - Updates to the `Binder`, `Dumper`, and `UpvalueAnalyzer` to handle the new macro constructs. - New examples demonstrating various macro functionalities like `unless`, splicing, and nested macros.
This commit is contained in:
@@ -300,6 +300,19 @@ impl Binder {
|
||||
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Map { entries: bound_entries }, ty))
|
||||
},
|
||||
|
||||
UntypedKind::Expansion { call, expanded } => {
|
||||
let bound_expanded = self.bind(expanded)?;
|
||||
let ty = bound_expanded.ty.clone();
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Expansion {
|
||||
original_call: Rc::from(call.as_ref().clone()),
|
||||
bound_expanded: Box::new(bound_expanded),
|
||||
}, ty))
|
||||
},
|
||||
|
||||
UntypedKind::Template(_) | UntypedKind::Placeholder(_) | UntypedKind::Splice(_) | UntypedKind::MacroDecl { .. } => {
|
||||
Err(format!("Macro construct {:?} found in Binder. Macros must be expanded before binding.", node.kind))
|
||||
}
|
||||
|
||||
UntypedKind::Extension(_) => {
|
||||
Err("Custom extensions not supported in Binder yet".to_string())
|
||||
|
||||
@@ -70,6 +70,13 @@ pub enum BoundKind {
|
||||
Map {
|
||||
entries: Vec<(Node<BoundKind, StaticType>, Node<BoundKind, StaticType>)>,
|
||||
},
|
||||
/// An expanded macro call, preserving the original call for debugging and UI.
|
||||
Expansion {
|
||||
/// 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, StaticType>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl BoundKind {
|
||||
@@ -88,6 +95,7 @@ impl BoundKind {
|
||||
BoundKind::Block { .. } => "BLOCK".to_string(),
|
||||
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
||||
BoundKind::Map { entries } => format!("MAP({})", entries.len()),
|
||||
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,13 @@ impl Dumper {
|
||||
}
|
||||
self.indent -= 1;
|
||||
}
|
||||
|
||||
BoundKind::Expansion { original_call, bound_expanded } => {
|
||||
self.log(&format!("Expansion (Original: {:?})", original_call.kind), node);
|
||||
self.indent += 1;
|
||||
self.visit(bound_expanded);
|
||||
self.indent -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,453 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::types::{Value, Identity};
|
||||
|
||||
/// Trait for evaluating expressions during macro expansion.
|
||||
/// Similar to TMacroEvaluatorProc in the Delphi implementation.
|
||||
pub trait MacroEvaluator {
|
||||
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String>;
|
||||
}
|
||||
|
||||
/// A registry for macro declarations.
|
||||
pub struct MacroRegistry {
|
||||
scopes: Vec<HashMap<Rc<str>, (Vec<Rc<str>>, Node<UntypedKind>)>>,
|
||||
}
|
||||
|
||||
impl MacroRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scopes: vec![HashMap::new()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self) {
|
||||
self.scopes.push(HashMap::new());
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) {
|
||||
if self.scopes.len() > 1 {
|
||||
self.scopes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn define(&mut self, name: Rc<str>, params: Vec<Rc<str>>, body: Node<UntypedKind>) {
|
||||
if let Some(current) = self.scopes.last_mut() {
|
||||
current.insert(name, (params, body));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, name: &str) -> Option<(Vec<Rc<str>>, Node<UntypedKind>)> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
if let Some(m) = scope.get(name) {
|
||||
return Some(m.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MacroExpander<E: MacroEvaluator> {
|
||||
registry: MacroRegistry,
|
||||
evaluator: E,
|
||||
}
|
||||
|
||||
impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
pub fn new(registry: MacroRegistry, evaluator: E) -> Self {
|
||||
Self { registry, evaluator }
|
||||
}
|
||||
|
||||
pub fn expand(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
|
||||
self.expand_recursive(node)
|
||||
}
|
||||
|
||||
fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
|
||||
match node.kind {
|
||||
UntypedKind::MacroDecl { name, params, body } => {
|
||||
self.registry.define(name, params, *body);
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Nop,
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Call { callee, args } => {
|
||||
if let UntypedKind::Identifier(ref name) = callee.kind {
|
||||
if let Some((params, body)) = self.registry.lookup(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(), name, params, expanded_args, 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)?);
|
||||
}
|
||||
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Call {
|
||||
callee: Box::new(expanded_callee),
|
||||
args: expanded_args,
|
||||
},
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Block { exprs } => {
|
||||
self.registry.push();
|
||||
let mut expanded_exprs = Vec::new();
|
||||
for expr in exprs {
|
||||
expanded_exprs.push(self.expand_recursive(expr)?);
|
||||
}
|
||||
self.registry.pop();
|
||||
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Block { exprs: expanded_exprs },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Lambda { params, body } => {
|
||||
self.registry.push();
|
||||
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) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::If { cond, then_br, else_br } => {
|
||||
let cond = self.expand_recursive(*cond)?;
|
||||
let then_br = self.expand_recursive(*then_br)?;
|
||||
let else_br = if let Some(e) = else_br {
|
||||
Some(Box::new(self.expand_recursive(*e)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Def { name, value } => {
|
||||
let value = self.expand_recursive(*value)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Def { name, value: Box::new(value) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Assign { target, value } => {
|
||||
let target = self.expand_recursive(*target)?;
|
||||
let value = self.expand_recursive(*value)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Tuple { elements } => {
|
||||
let mut expanded_elements = Vec::new();
|
||||
for e in elements {
|
||||
expanded_elements.push(self.expand_recursive(e)?);
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Tuple { elements: expanded_elements },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Map { entries } => {
|
||||
let mut expanded_entries = Vec::new();
|
||||
for (k, v) in entries {
|
||||
expanded_entries.push((self.expand_recursive(k)?, self.expand_recursive(v)?));
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Map { entries: expanded_entries },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Expansion { call, expanded } => {
|
||||
let expanded = self.expand_recursive(*expanded)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Expansion { call, expanded: Box::new(expanded) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
_ => Ok(node),
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_call(&self, identity: Identity, name: &str, params: Vec<Rc<str>>, args: Vec<Node<UntypedKind>>, body: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
|
||||
if params.len() != args.len() {
|
||||
return Err(format!("Macro {} expects {} arguments, but got {}", name, params.len(), args.len()));
|
||||
}
|
||||
|
||||
let mut bindings = HashMap::new();
|
||||
for (p, a) in params.into_iter().zip(args.into_iter()) {
|
||||
bindings.insert(p, a);
|
||||
}
|
||||
|
||||
let expanded_body = if let UntypedKind::Template(inner) = body.kind {
|
||||
self.expand_template(*inner, &bindings)?
|
||||
} else {
|
||||
body
|
||||
};
|
||||
|
||||
let original_call = Node {
|
||||
identity: identity.clone(),
|
||||
kind: UntypedKind::Call {
|
||||
callee: Box::new(Node {
|
||||
identity: identity.clone(),
|
||||
kind: UntypedKind::Identifier(Rc::from(name)),
|
||||
ty: (),
|
||||
}),
|
||||
args: bindings.iter().map(|(_, v)| v.clone()).collect(),
|
||||
},
|
||||
ty: (),
|
||||
};
|
||||
|
||||
Ok(Node {
|
||||
identity,
|
||||
kind: UntypedKind::Expansion {
|
||||
call: Box::new(original_call),
|
||||
expanded: Box::new(expanded_body),
|
||||
},
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_template(&self, node: Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Node<UntypedKind>, String> {
|
||||
match node.kind {
|
||||
UntypedKind::Placeholder(inner) => {
|
||||
let val = self.evaluator.evaluate(&inner, bindings)?;
|
||||
Ok(self.value_to_node(val, node.identity))
|
||||
}
|
||||
|
||||
UntypedKind::Splice(_) => {
|
||||
Err("Splice node found outside of a container context".to_string())
|
||||
}
|
||||
|
||||
UntypedKind::Tuple { elements } => {
|
||||
let mut new_elements = Vec::new();
|
||||
for e in elements {
|
||||
if let UntypedKind::Splice(ref inner) = e.kind {
|
||||
let val = self.evaluator.evaluate(inner, bindings)?;
|
||||
self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?;
|
||||
} else {
|
||||
new_elements.push(self.expand_template(e, bindings)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Tuple { elements: new_elements },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Block { exprs } => {
|
||||
let mut new_exprs = Vec::new();
|
||||
for e in exprs {
|
||||
if let UntypedKind::Splice(ref inner) = e.kind {
|
||||
let val = self.evaluator.evaluate(inner, bindings)?;
|
||||
self.handle_splice_value(val, node.identity.clone(), &mut new_exprs)?;
|
||||
} else {
|
||||
new_exprs.push(self.expand_template(e, bindings)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Block { exprs: new_exprs },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Map { entries } => {
|
||||
let mut new_entries = Vec::new();
|
||||
for (k, v) in entries {
|
||||
new_entries.push((self.expand_template(k, bindings)?, self.expand_template(v, bindings)?));
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Map { entries: new_entries },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Call { callee, args } => {
|
||||
let expanded_callee = self.expand_template(*callee, bindings)?;
|
||||
let mut expanded_args = Vec::new();
|
||||
for arg in args {
|
||||
if let UntypedKind::Splice(ref inner) = arg.kind {
|
||||
let val = self.evaluator.evaluate(inner, bindings)?;
|
||||
self.handle_splice_value(val, node.identity.clone(), &mut expanded_args)?;
|
||||
} else {
|
||||
expanded_args.push(self.expand_template(arg, bindings)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: expanded_args },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::If { cond, then_br, else_br } => {
|
||||
let cond = self.expand_template(*cond, bindings)?;
|
||||
let then_br = self.expand_template(*then_br, bindings)?;
|
||||
let else_br = if let Some(e) = else_br {
|
||||
Some(Box::new(self.expand_template(*e, bindings)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
_ => Ok(node),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_splice_value(&self, val: Value, _identity: Identity, target: &mut Vec<Node<UntypedKind>>) -> Result<(), String> {
|
||||
match val {
|
||||
Value::Object(obj) => {
|
||||
if let Some(node) = obj.as_any().downcast_ref::<Node<UntypedKind>>() {
|
||||
match &node.kind {
|
||||
UntypedKind::Tuple { elements } => {
|
||||
for e in elements { target.push(e.clone()); }
|
||||
return Ok(());
|
||||
}
|
||||
UntypedKind::Block { exprs } => {
|
||||
for e in exprs { target.push(e.clone()); }
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Err(format!("Strict Splicing: Expected Tuple or Block node, but found {}", obj.type_name()))
|
||||
}
|
||||
_ => Err(format!("Strict Splicing: Expected sequence (Tuple/Block Node), but found Value: {}", val)),
|
||||
}
|
||||
}
|
||||
|
||||
fn value_to_node(&self, val: Value, identity: Identity) -> Node<UntypedKind> {
|
||||
match val {
|
||||
Value::Object(obj) => {
|
||||
if let Some(node) = obj.as_any().downcast_ref::<Node<UntypedKind>>() {
|
||||
return node.clone();
|
||||
}
|
||||
Node { identity, kind: UntypedKind::Constant(Value::Object(obj)), ty: () }
|
||||
}
|
||||
_ => Node { identity, kind: UntypedKind::Constant(val), ty: () }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::types::Value;
|
||||
|
||||
struct SimpleEvaluator;
|
||||
impl MacroEvaluator for SimpleEvaluator {
|
||||
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
|
||||
if let UntypedKind::Identifier(ref name) = node.kind {
|
||||
if let Some(arg) = bindings.get(name) {
|
||||
return Ok(Value::Object(Rc::new(arg.clone())));
|
||||
}
|
||||
}
|
||||
// For real expressions, we would need Binder + VM here.
|
||||
Err(format!("SimpleEvaluator cannot evaluate complex expression: {:?}", node.kind))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_expansion_unless() {
|
||||
let source = "
|
||||
(do
|
||||
(macro unless [c b] `(if ~c void ~b))
|
||||
(unless true 42))
|
||||
";
|
||||
let mut parser = Parser::new(source).unwrap();
|
||||
let untyped = parser.parse_expression().unwrap();
|
||||
|
||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||
let expanded = expander.expand(untyped).unwrap();
|
||||
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
||||
assert_eq!(exprs.len(), 2);
|
||||
assert!(matches!(exprs[0].kind, UntypedKind::Nop));
|
||||
|
||||
if let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind {
|
||||
if let UntypedKind::If { cond, then_br, else_br } = &result.kind {
|
||||
if let UntypedKind::Constant(Value::Bool(b)) = &cond.kind {
|
||||
assert_eq!(*b, true);
|
||||
} else { panic!("Expected true, got {:?}", cond.kind); }
|
||||
assert!(matches!(then_br.kind, UntypedKind::Constant(Value::Void)));
|
||||
if let Some(eb) = else_br {
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
|
||||
assert_eq!(*n, 42);
|
||||
} else { panic!("Expected 42, got {:?}", eb.kind); }
|
||||
} else { panic!("Else branch missing"); }
|
||||
} else {
|
||||
panic!("Expansion result should be an IF, got {:?}", result.kind);
|
||||
}
|
||||
} else {
|
||||
panic!("Second expression should be an Expansion, got {:?}", exprs[1].kind);
|
||||
}
|
||||
} else {
|
||||
panic!("Root should be a Block");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_expansion_splice() {
|
||||
let source = "
|
||||
(do
|
||||
(macro wrap [items] `[0 ~@items 4])
|
||||
(def t (wrap [1 2 3])))
|
||||
";
|
||||
let mut parser = Parser::new(source).unwrap();
|
||||
let untyped = parser.parse_expression().unwrap();
|
||||
|
||||
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
|
||||
let expanded = expander.expand(untyped).unwrap();
|
||||
|
||||
if let UntypedKind::Block { exprs } = &expanded.kind {
|
||||
if let UntypedKind::Def { value, .. } = &exprs[1].kind {
|
||||
if let UntypedKind::Expansion { expanded: result, .. } = &value.kind {
|
||||
if let UntypedKind::Tuple { elements } = &result.kind {
|
||||
assert_eq!(elements.len(), 5);
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[0].kind { assert_eq!(*n, 0); }
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[1].kind { assert_eq!(*n, 1); }
|
||||
if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); }
|
||||
} else { panic!("Expected Tuple result, got {:?}", result.kind); }
|
||||
} else { panic!("Expected Expansion for t, got {:?}", value.kind); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ pub mod bound_nodes;
|
||||
pub mod tco;
|
||||
pub mod upvalues;
|
||||
pub mod dumper;
|
||||
pub mod macros;
|
||||
|
||||
pub use binder::*;
|
||||
pub use bound_nodes::*;
|
||||
pub use tco::*;
|
||||
pub use upvalues::*;
|
||||
pub use dumper::*;
|
||||
pub use macros::*;
|
||||
|
||||
@@ -93,6 +93,12 @@ impl UpvalueAnalyzer {
|
||||
Self::visit(v, scopes, capture_map, current_lambda.clone());
|
||||
}
|
||||
}
|
||||
UntypedKind::Expansion { call: _, expanded } => {
|
||||
Self::visit(expanded, scopes, capture_map, current_lambda);
|
||||
}
|
||||
UntypedKind::MacroDecl { body, .. } | UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => {
|
||||
Self::visit(body, scopes, capture_map, current_lambda);
|
||||
}
|
||||
UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user