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:
+73
-36
@@ -1,14 +1,16 @@
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use crate::ast::types::{Value, StaticType};
|
||||
use crate::ast::types::{Value, StaticType, Object};
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::compiler::binder::Binder;
|
||||
use crate::ast::compiler::BoundKind;
|
||||
use crate::ast::vm::{VM, TracingObserver};
|
||||
|
||||
use crate::ast::compiler::tco::TCO;
|
||||
|
||||
use crate::ast::compiler::dumper::Dumper;
|
||||
use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator};
|
||||
|
||||
pub struct Environment {
|
||||
pub global_names: Rc<RefCell<HashMap<String, (u32, StaticType)>>>,
|
||||
@@ -16,6 +18,30 @@ pub struct Environment {
|
||||
pub debug_mode: bool,
|
||||
}
|
||||
|
||||
/// Evaluator used during macro expansion to allow compile-time logic.
|
||||
struct RuntimeMacroEvaluator {
|
||||
global_names: Rc<RefCell<HashMap<String, (u32, StaticType)>>>,
|
||||
global_values: Rc<RefCell<Vec<Value>>>,
|
||||
}
|
||||
|
||||
impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
|
||||
// 1. Check if it's a simple parameter substitution
|
||||
if let UntypedKind::Identifier(name) = &node.kind {
|
||||
if let Some(arg_node) = bindings.get(name) {
|
||||
return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Full evaluation for complex compile-time expressions
|
||||
// Note: This evaluator currently doesn't see macro parameters in complex expressions
|
||||
// unless they are explicitly substituted. For now, we support global-only logic.
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), node)?;
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(&bound_ast)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Environment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -37,6 +63,14 @@ impl Environment {
|
||||
self.debug_mode = enabled;
|
||||
}
|
||||
|
||||
fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> {
|
||||
let evaluator = RuntimeMacroEvaluator {
|
||||
global_names: self.global_names.clone(),
|
||||
global_values: self.global_values.clone(),
|
||||
};
|
||||
MacroExpander::new(MacroRegistry::new(), evaluator)
|
||||
}
|
||||
|
||||
pub fn register_native(&self, name: &str, ty: StaticType, func: impl Fn(Vec<Value>) -> Value + 'static) {
|
||||
let mut names = self.global_names.borrow_mut();
|
||||
let mut values = self.global_values.borrow_mut();
|
||||
@@ -114,13 +148,38 @@ impl Environment {
|
||||
}
|
||||
|
||||
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
||||
let compiled = self.compile(source)?;
|
||||
let linked = self.link(compiled);
|
||||
Ok(Dumper::dump(&linked))
|
||||
}
|
||||
|
||||
/// Frontend: Parse -> Expand Macros -> Bind & Type Check
|
||||
pub fn compile(&self, source: &str) -> Result<Node<BoundKind, StaticType>, String> {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), &untyped_ast)?;
|
||||
let optimized_ast = TCO::optimize(bound_ast);
|
||||
|
||||
Ok(Dumper::dump(&optimized_ast))
|
||||
|
||||
// 2. Check for trailing tokens
|
||||
if !parser.at_eof() {
|
||||
return Err("Unexpected trailing expressions in script. Use (do ...) for sequences.".to_string());
|
||||
}
|
||||
|
||||
// 3. Expand Macros
|
||||
let expanded_ast = self.get_expander().expand(untyped_ast)?;
|
||||
|
||||
// 4. Bind & Type Check
|
||||
Binder::bind_root(self.global_names.clone(), &expanded_ast)
|
||||
}
|
||||
|
||||
/// Backend: Optimization (TCO, etc.)
|
||||
pub fn link(&self, node: Node<BoundKind, StaticType>) -> Node<BoundKind, StaticType> {
|
||||
TCO::optimize(node)
|
||||
}
|
||||
|
||||
/// Runtime: Execute the linked AST in the VM
|
||||
pub fn run(&self, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(node)
|
||||
}
|
||||
|
||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||
@@ -131,42 +190,20 @@ impl Environment {
|
||||
}
|
||||
res
|
||||
} else {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
|
||||
// 2. Check for trailing tokens
|
||||
if !parser.at_eof() {
|
||||
return Err("Unexpected trailing expressions in script. Use (do ...) for sequences.".to_string());
|
||||
}
|
||||
|
||||
// 3. Bind & Type Check
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), &untyped_ast)?;
|
||||
|
||||
// 4. Optimize
|
||||
let bound_ast = TCO::optimize(bound_ast);
|
||||
|
||||
// 5. Execute
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(&bound_ast)
|
||||
let compiled = self.compile(source)?;
|
||||
let linked = self.link(compiled);
|
||||
self.run(&linked)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
let compiled = self.compile(source)?;
|
||||
let linked = self.link(compiled);
|
||||
|
||||
// 2. Bind
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), &untyped_ast)?;
|
||||
|
||||
// 3. Optimize (TCO)
|
||||
let bound_ast = TCO::optimize(bound_ast);
|
||||
|
||||
// 4. Execute with TracingObserver
|
||||
// Execute with TracingObserver
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
let mut observer = TracingObserver::new();
|
||||
let result = vm.run_with_observer(&mut observer, &bound_ast);
|
||||
let result = vm.run_with_observer(&mut observer, &linked);
|
||||
|
||||
Ok((result, observer.logs))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user