Refactor: Use Symbol for identifiers and macro hygiene
Introduces a `Symbol` struct to represent identifiers, incorporating macro hygiene context. Updates various parts of the AST compiler and environment to use `Symbol` instead of raw `Rc<str>` for identifiers, improving robustness for macro expansions. Also includes: - Adds a new example `macro_hygiene.myc`. - Updates `.gitignore`. - Refactors `MacroExpander` to use `ExpansionState` for cleaner template expansion. - Adjusts `VM::eval_observed` to ensure `after_eval` is called correctly. - Resets `Environment` for each test run and compilation/dumping in `main.rs`.
This commit is contained in:
+54
-27
@@ -1,7 +1,7 @@
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Symbol};
|
||||
use crate::ast::compiler::bound_nodes::{BoundKind, Address};
|
||||
use crate::ast::types::{Identity, StaticType, Value};
|
||||
|
||||
@@ -13,7 +13,7 @@ struct LocalInfo {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CompilerScope {
|
||||
locals: HashMap<String, LocalInfo>,
|
||||
locals: HashMap<Symbol, LocalInfo>,
|
||||
slot_count: u32,
|
||||
}
|
||||
|
||||
@@ -25,18 +25,18 @@ impl CompilerScope {
|
||||
}
|
||||
}
|
||||
|
||||
fn define(&mut self, name: &str, ty: StaticType) -> Result<u32, String> {
|
||||
if self.locals.contains_key(name) {
|
||||
return Err(format!("Variable '{}' is already defined in this scope level.", name));
|
||||
fn define(&mut self, sym: &Symbol, ty: StaticType) -> Result<u32, String> {
|
||||
if self.locals.contains_key(sym) {
|
||||
return Err(format!("Variable '{}' is already defined in this scope level.", sym.name));
|
||||
}
|
||||
let slot = self.slot_count;
|
||||
self.locals.insert(name.to_string(), LocalInfo { slot, ty });
|
||||
self.locals.insert(sym.clone(), LocalInfo { slot, ty });
|
||||
self.slot_count += 1;
|
||||
Ok(slot)
|
||||
}
|
||||
|
||||
fn resolve(&self, name: &str) -> Option<LocalInfo> {
|
||||
self.locals.get(name).cloned()
|
||||
fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> {
|
||||
self.locals.get(sym).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,18 +65,18 @@ impl FunctionCompiler {
|
||||
|
||||
pub struct Binder {
|
||||
functions: Vec<FunctionCompiler>,
|
||||
// Globals mapping: Name -> (Index, Type)
|
||||
globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>,
|
||||
// Globals mapping: Symbol -> (Index, Type)
|
||||
globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
|
||||
// Map of Declaration Identity -> List of Lambda Identities that capture it
|
||||
capture_map: HashMap<Identity, Vec<Identity>>,
|
||||
}
|
||||
|
||||
impl Binder {
|
||||
pub fn new(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>) -> Self {
|
||||
pub fn new(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>) -> Self {
|
||||
Self::with_boxed(globals, HashMap::new())
|
||||
}
|
||||
|
||||
fn with_boxed(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self {
|
||||
fn with_boxed(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self {
|
||||
let mut binder = Self {
|
||||
functions: Vec::new(),
|
||||
globals,
|
||||
@@ -86,7 +86,7 @@ impl Binder {
|
||||
binder
|
||||
}
|
||||
|
||||
pub fn bind_root(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> {
|
||||
pub fn bind_root(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> {
|
||||
let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
|
||||
let mut binder = Self::with_boxed(globals, captures);
|
||||
binder.bind(node)
|
||||
@@ -100,8 +100,8 @@ impl Binder {
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty))
|
||||
},
|
||||
|
||||
UntypedKind::Identifier(name) => {
|
||||
let (addr, ty) = self.resolve_variable(name)?;
|
||||
UntypedKind::Identifier(sym) => {
|
||||
let (addr, ty) = self.resolve_variable(sym)?;
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty))
|
||||
},
|
||||
|
||||
@@ -138,11 +138,11 @@ impl Binder {
|
||||
// 1. Pre-declare name to support recursion
|
||||
let slot_or_idx = if self.functions.len() == 1 {
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if globals.contains_key(name.as_ref()) {
|
||||
return Err(format!("Global variable '{}' is already defined.", name));
|
||||
if globals.contains_key(name) {
|
||||
return Err(format!("Global variable '{}' is already defined.", name.name));
|
||||
}
|
||||
let idx = globals.len() as u32;
|
||||
globals.insert(name.to_string(), (idx, StaticType::Any));
|
||||
globals.insert(name.clone(), (idx, StaticType::Any));
|
||||
idx
|
||||
} else {
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
@@ -157,7 +157,7 @@ impl Binder {
|
||||
if self.functions.len() == 1 {
|
||||
{
|
||||
let mut globals = self.globals.borrow_mut();
|
||||
if let Some(entry) = globals.get_mut(name.as_ref()) {
|
||||
if let Some(entry) = globals.get_mut(name) {
|
||||
entry.1 = ty.clone();
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ impl Binder {
|
||||
} else {
|
||||
{
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
if let Some(info) = current_fn.scope.locals.get_mut(name.as_ref()) {
|
||||
if let Some(info) = current_fn.scope.locals.get_mut(name) {
|
||||
info.ty = ty.clone();
|
||||
}
|
||||
}
|
||||
@@ -184,8 +184,8 @@ impl Binder {
|
||||
UntypedKind::Assign { target, value } => {
|
||||
let val_node = self.bind(value)?;
|
||||
|
||||
if let UntypedKind::Identifier(name) = &target.kind {
|
||||
let (addr, target_ty) = self.resolve_variable(name)?;
|
||||
if let UntypedKind::Identifier(sym) = &target.kind {
|
||||
let (addr, target_ty) = self.resolve_variable(sym)?;
|
||||
|
||||
// Type check: value must be compatible with target
|
||||
if target_ty != StaticType::Any && val_node.ty != StaticType::Any && target_ty != val_node.ty {
|
||||
@@ -322,17 +322,17 @@ impl Binder {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_variable(&mut self, name: &str) -> Result<(Address, StaticType), String> {
|
||||
fn resolve_variable(&mut self, sym: &Symbol) -> Result<(Address, StaticType), String> {
|
||||
let current_fn_idx = self.functions.len() - 1;
|
||||
|
||||
// 1. Try local in current function
|
||||
if let Some(info) = self.functions[current_fn_idx].scope.resolve(name) {
|
||||
if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) {
|
||||
return Ok((Address::Local(info.slot), info.ty));
|
||||
}
|
||||
|
||||
// 2. Try enclosing scopes (capture chain)
|
||||
for i in (0..current_fn_idx).rev() {
|
||||
if let Some(info) = self.functions[i].scope.resolve(name) {
|
||||
if let Some(info) = self.functions[i].scope.resolve(sym) {
|
||||
let mut addr = Address::Local(info.slot);
|
||||
let ty = info.ty.clone();
|
||||
|
||||
@@ -345,11 +345,20 @@ impl Binder {
|
||||
|
||||
// 3. Try Global
|
||||
let globals = self.globals.borrow();
|
||||
if let Some((idx, ty)) = globals.get(name) {
|
||||
if let Some((idx, ty)) = globals.get(sym) {
|
||||
return Ok((Address::Global(*idx), ty.clone()));
|
||||
}
|
||||
|
||||
Err(format!("Undefined variable '{}'", name))
|
||||
// 4. Global Fallback: If not found with context, try without context
|
||||
// (Allows macros to access global built-ins like *, +, etc.)
|
||||
if sym.context.is_some() {
|
||||
let fallback_sym = Symbol { name: sym.name.clone(), context: None };
|
||||
if let Some((idx, ty)) = globals.get(&fallback_sym) {
|
||||
return Ok((Address::Global(*idx), ty.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("Undefined variable '{}'", sym.name))
|
||||
}
|
||||
|
||||
fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node<BoundKind, StaticType> {
|
||||
@@ -426,4 +435,22 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("already defined"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repro_global_redefinition() {
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
|
||||
// First run: defines 'x'
|
||||
let source1 = "(def x 1)";
|
||||
let untyped1 = Parser::new(source1).unwrap().parse_expression().unwrap();
|
||||
assert!(Binder::bind_root(globals.clone(), &untyped1).is_ok());
|
||||
|
||||
// Second run: attempts to redefine 'x' in the same global environment
|
||||
let source2 = "(def x 2)";
|
||||
let untyped2 = Parser::new(source2).unwrap().parse_expression().unwrap();
|
||||
let result = Binder::bind_root(globals.clone(), &untyped2);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("already defined"));
|
||||
}
|
||||
}
|
||||
|
||||
+192
-49
@@ -1,17 +1,31 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Symbol};
|
||||
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>;
|
||||
}
|
||||
|
||||
/// Internal state for template expansion (Hygiene context + Parameter binding)
|
||||
struct ExpansionState<'a> {
|
||||
bindings: &'a HashMap<Rc<str>, Node<UntypedKind>>,
|
||||
/// The identity of the current expansion instance, used to "color" internal symbols.
|
||||
expansion_id: Identity,
|
||||
}
|
||||
|
||||
type MacroMap = HashMap<Rc<str>, (Vec<Rc<str>>, Node<UntypedKind>)>;
|
||||
|
||||
/// A registry for macro declarations.
|
||||
pub struct MacroRegistry {
|
||||
scopes: Vec<HashMap<Rc<str>, (Vec<Rc<str>>, Node<UntypedKind>)>>,
|
||||
scopes: Vec<MacroMap>,
|
||||
}
|
||||
|
||||
impl Default for MacroRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MacroRegistry {
|
||||
@@ -64,7 +78,8 @@ 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 } => {
|
||||
self.registry.define(name, params, *body);
|
||||
let p_names: Vec<Rc<str>> = params.into_iter().map(|s| s.name).collect();
|
||||
self.registry.define(name.name, p_names, *body);
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Nop,
|
||||
@@ -73,15 +88,14 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
}
|
||||
|
||||
UntypedKind::Call { callee, args } => {
|
||||
if let UntypedKind::Identifier(ref name) = callee.kind {
|
||||
if let Some((params, body)) = self.registry.lookup(name) {
|
||||
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(), name, params, expanded_args, body)?;
|
||||
let expanded = self.expand_call(node.identity.clone(), &sym.name, params, expanded_args, body)?;
|
||||
return self.expand_recursive(expanded);
|
||||
}
|
||||
}
|
||||
|
||||
let expanded_callee = self.expand_recursive(*callee)?;
|
||||
@@ -208,9 +222,15 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
bindings.insert(p, a);
|
||||
}
|
||||
|
||||
// AST-Authority: Substitution and Hygiene ONLY happen if there is a Template node.
|
||||
let expanded_body = if let UntypedKind::Template(inner) = body.kind {
|
||||
self.expand_template(*inner, &bindings)?
|
||||
let mut state = ExpansionState {
|
||||
bindings: &bindings,
|
||||
expansion_id: identity.clone(),
|
||||
};
|
||||
self.expand_template(*inner, &mut state)?
|
||||
} else {
|
||||
// Static AST fragment: No substitution, no hygiene.
|
||||
body
|
||||
};
|
||||
|
||||
@@ -219,10 +239,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
kind: UntypedKind::Call {
|
||||
callee: Box::new(Node {
|
||||
identity: identity.clone(),
|
||||
kind: UntypedKind::Identifier(Rc::from(name)),
|
||||
kind: UntypedKind::Identifier(Symbol::from(name)),
|
||||
ty: (),
|
||||
}),
|
||||
args: bindings.iter().map(|(_, v)| v.clone()).collect(),
|
||||
args: bindings.into_values().collect(),
|
||||
},
|
||||
ty: (),
|
||||
};
|
||||
@@ -237,25 +257,62 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_template(&self, node: Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Node<UntypedKind>, String> {
|
||||
fn expand_template(&self, node: Node<UntypedKind>, state: &mut ExpansionState) -> Result<Node<UntypedKind>, String> {
|
||||
match node.kind {
|
||||
UntypedKind::Identifier(mut sym) => {
|
||||
// Inside a template, all internal identifiers are colored.
|
||||
sym.context = Some(state.expansion_id.clone());
|
||||
Ok(Node { identity: node.identity, kind: UntypedKind::Identifier(sym), ty: () })
|
||||
}
|
||||
|
||||
UntypedKind::Placeholder(inner) => {
|
||||
let val = self.evaluator.evaluate(&inner, bindings)?;
|
||||
// Break out of template for substitution/evaluation
|
||||
if let UntypedKind::Identifier(ref sym) = inner.kind
|
||||
&& let Some(arg) = state.bindings.get(&sym.name) {
|
||||
return Ok(arg.clone());
|
||||
}
|
||||
let val = self.evaluator.evaluate(&inner, state.bindings)?;
|
||||
Ok(self.value_to_node(val, node.identity))
|
||||
}
|
||||
|
||||
UntypedKind::Splice(_) => {
|
||||
Err("Splice node found outside of a container context".to_string())
|
||||
UntypedKind::Splice(inner) => {
|
||||
// Splicing is also a form of substitution
|
||||
if let UntypedKind::Identifier(ref sym) = inner.kind
|
||||
&& let Some(arg) = state.bindings.get(&sym.name) {
|
||||
return Ok(arg.clone());
|
||||
}
|
||||
let val = self.evaluator.evaluate(&inner, state.bindings)?;
|
||||
Ok(self.value_to_node(val, node.identity))
|
||||
}
|
||||
|
||||
UntypedKind::Def { mut name, value } => {
|
||||
name.context = Some(state.expansion_id.clone());
|
||||
let val = self.expand_template(*value, state)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Def { name, value: Box::new(val) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Assign { target, value } => {
|
||||
let target = self.expand_template(*target, state)?;
|
||||
let value = self.expand_template(*value, state)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
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)?;
|
||||
let val = self.evaluator.evaluate(inner, state.bindings)?;
|
||||
self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?;
|
||||
} else {
|
||||
new_elements.push(self.expand_template(e, bindings)?);
|
||||
new_elements.push(self.expand_template(e, state)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
@@ -269,10 +326,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
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)?;
|
||||
let val = self.evaluator.evaluate(inner, state.bindings)?;
|
||||
self.handle_splice_value(val, node.identity.clone(), &mut new_exprs)?;
|
||||
} else {
|
||||
new_exprs.push(self.expand_template(e, bindings)?);
|
||||
new_exprs.push(self.expand_template(e, state)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
@@ -285,7 +342,7 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
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)?));
|
||||
new_entries.push((self.expand_template(k, state)?, self.expand_template(v, state)?));
|
||||
}
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
@@ -295,14 +352,14 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
}
|
||||
|
||||
UntypedKind::Call { callee, args } => {
|
||||
let expanded_callee = self.expand_template(*callee, bindings)?;
|
||||
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, bindings)?;
|
||||
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, bindings)?);
|
||||
expanded_args.push(self.expand_template(arg, state)?);
|
||||
}
|
||||
}
|
||||
Ok(Node {
|
||||
@@ -313,10 +370,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
}
|
||||
|
||||
UntypedKind::If { cond, then_br, else_br } => {
|
||||
let cond = self.expand_template(*cond, bindings)?;
|
||||
let then_br = self.expand_template(*then_br, bindings)?;
|
||||
let cond = self.expand_template(*cond, state)?;
|
||||
let then_br = self.expand_template(*then_br, state)?;
|
||||
let else_br = if let Some(e) = else_br {
|
||||
Some(Box::new(self.expand_template(*e, bindings)?))
|
||||
Some(Box::new(self.expand_template(*e, state)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -327,6 +384,18 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
})
|
||||
}
|
||||
|
||||
UntypedKind::Lambda { mut params, body } => {
|
||||
for p in &mut params {
|
||||
p.context = Some(state.expansion_id.clone());
|
||||
}
|
||||
let body = self.expand_template(Node::clone(&body), state)?;
|
||||
Ok(Node {
|
||||
identity: node.identity,
|
||||
kind: UntypedKind::Lambda { params, body: Rc::new(body) },
|
||||
ty: (),
|
||||
})
|
||||
}
|
||||
|
||||
_ => Ok(node),
|
||||
}
|
||||
}
|
||||
@@ -369,28 +438,51 @@ impl<E: MacroEvaluator> MacroExpander<E> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::cell::RefCell;
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::types::Value;
|
||||
use crate::ast::types::{Value, StaticType, Object};
|
||||
use crate::ast::compiler::Binder;
|
||||
|
||||
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())));
|
||||
}
|
||||
if let UntypedKind::Identifier(ref sym) = node.kind
|
||||
&& let Some(arg) = bindings.get(&sym.name) {
|
||||
return Ok(Value::Object(Rc::new(arg.clone()) as Rc<dyn Object>));
|
||||
}
|
||||
// For real expressions, we would need Binder + VM here.
|
||||
Err(format!("SimpleEvaluator cannot evaluate complex expression: {:?}", node.kind))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_expansion_unless() {
|
||||
fn test_macro_expansion_hygiene() {
|
||||
let source = "
|
||||
(do
|
||||
(macro unless [c b] `(if ~c void ~b))
|
||||
(unless true 42))
|
||||
(macro m [] `(def y 10))
|
||||
(m))
|
||||
";
|
||||
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::Expansion { expanded: result, call } = &exprs[1].kind {
|
||||
if let UntypedKind::Def { name, .. } = &result.kind {
|
||||
assert_eq!(name.context, Some(call.identity.clone()));
|
||||
assert_eq!(name.name.as_ref(), "y");
|
||||
} else { panic!("Expected Def result, got {:?}", result.kind); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro_expansion_unless() {
|
||||
let source = "
|
||||
(do
|
||||
(macro unless [c b] `(if ~c void ~b))
|
||||
(unless false 42))
|
||||
";
|
||||
let mut parser = Parser::new(source).unwrap();
|
||||
let untyped = parser.parse_expression().unwrap();
|
||||
@@ -399,28 +491,19 @@ mod tests {
|
||||
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_eq!(*b, false);
|
||||
} else { panic!("Expected false, 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,11 +526,71 @@ mod tests {
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repro_macro_global_lookup() {
|
||||
let source = "
|
||||
(do
|
||||
(macro square [x] `(* ~x ~x))
|
||||
(square 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();
|
||||
|
||||
let mut global_names = HashMap::new();
|
||||
global_names.insert(Symbol::from("*"), (0, StaticType::Any));
|
||||
let globals = Rc::new(RefCell::new(global_names));
|
||||
|
||||
let result = Binder::bind_root(globals, &expanded);
|
||||
assert!(result.is_ok(), "Should find global '*' Error: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repro_macro_hygiene_success() {
|
||||
let source = "
|
||||
(do
|
||||
(def y 1)
|
||||
(macro m [] `(def y 10))
|
||||
(m)
|
||||
y)
|
||||
";
|
||||
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();
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let bound = Binder::bind_root(globals, &expanded);
|
||||
assert!(bound.is_ok(), "Hygiene failed: {:?}", bound.err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_repro_macro_hygiene_clash_fixed() {
|
||||
let source = "
|
||||
(do
|
||||
(def y 1)
|
||||
(macro m1 [x] `(do (def y 10) (assign y 4)))
|
||||
(m1 8)
|
||||
y)
|
||||
";
|
||||
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();
|
||||
|
||||
let globals = Rc::new(RefCell::new(HashMap::new()));
|
||||
let bound = Binder::bind_root(globals, &expanded);
|
||||
assert!(bound.is_ok(), "Explicit Hygiene with Backticks failed: {:?}", bound.err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::rc::Rc;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Symbol};
|
||||
use crate::ast::types::Identity;
|
||||
|
||||
/// Analyzes the AST to find which lambdas capture which variable declarations.
|
||||
@@ -22,15 +21,15 @@ impl UpvalueAnalyzer {
|
||||
|
||||
fn visit(
|
||||
node: &Node<UntypedKind>,
|
||||
scopes: &mut Vec<HashMap<Rc<str>, Identity>>,
|
||||
scopes: &mut Vec<HashMap<Symbol, Identity>>,
|
||||
capture_map: &mut HashMap<Identity, HashSet<Identity>>,
|
||||
current_lambda: Option<Identity>
|
||||
) {
|
||||
match &node.kind {
|
||||
UntypedKind::Identifier(name) => {
|
||||
UntypedKind::Identifier(sym) => {
|
||||
// Resolve name in scope stack (from inner to outer)
|
||||
for (depth, scope) in scopes.iter().rev().enumerate() {
|
||||
if let Some(decl_id) = scope.get(name) {
|
||||
if let Some(decl_id) = scope.get(sym) {
|
||||
if depth > 0 {
|
||||
// Captured from an outer scope!
|
||||
if let Some(lambda_id) = ¤t_lambda {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use crate::ast::types::{Value, StaticType, Object};
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Symbol};
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::compiler::binder::Binder;
|
||||
use crate::ast::compiler::BoundKind;
|
||||
@@ -13,29 +13,26 @@ 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)>>>,
|
||||
pub global_names: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
|
||||
pub global_values: Rc<RefCell<Vec<Value>>>,
|
||||
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_names: Rc<RefCell<HashMap<Symbol, (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) {
|
||||
if let UntypedKind::Identifier(sym) = &node.kind
|
||||
&& let Some(arg_node) = bindings.get(&sym.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)
|
||||
@@ -76,7 +73,7 @@ impl Environment {
|
||||
let mut values = self.global_values.borrow_mut();
|
||||
|
||||
let idx = values.len() as u32;
|
||||
names.insert(name.to_string(), (idx, ty));
|
||||
names.insert(Symbol::from(name), (idx, ty));
|
||||
values.push(Value::Function(Rc::new(func)));
|
||||
}
|
||||
|
||||
|
||||
+26
-5
@@ -3,6 +3,27 @@ use std::fmt::Debug;
|
||||
use std::any::Any;
|
||||
use crate::ast::types::{Identity, Value, Object};
|
||||
|
||||
/// A name with an optional context for macro hygiene.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Symbol {
|
||||
pub name: Rc<str>,
|
||||
/// Points to the identity of the Expansion node if this symbol
|
||||
/// was created/referenced inside a macro expansion.
|
||||
pub context: Option<Identity>,
|
||||
}
|
||||
|
||||
impl From<Rc<str>> for Symbol {
|
||||
fn from(name: Rc<str>) -> Self {
|
||||
Self { name, context: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Symbol {
|
||||
fn from(name: &str) -> Self {
|
||||
Self { name: Rc::from(name), context: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic AST Node wrapper to preserve identity and metadata
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node<K, T = ()> {
|
||||
@@ -36,14 +57,14 @@ impl Clone for Box<dyn CustomNode> {
|
||||
pub enum UntypedKind {
|
||||
Nop,
|
||||
Constant(Value),
|
||||
Identifier(Rc<str>),
|
||||
Identifier(Symbol),
|
||||
If {
|
||||
cond: Box<Node<UntypedKind>>,
|
||||
then_br: Box<Node<UntypedKind>>,
|
||||
else_br: Option<Box<Node<UntypedKind>>>,
|
||||
},
|
||||
Def {
|
||||
name: Rc<str>,
|
||||
name: Symbol,
|
||||
value: Box<Node<UntypedKind>>,
|
||||
},
|
||||
Assign {
|
||||
@@ -51,7 +72,7 @@ pub enum UntypedKind {
|
||||
value: Box<Node<UntypedKind>>,
|
||||
},
|
||||
Lambda {
|
||||
params: Vec<Rc<str>>,
|
||||
params: Vec<Symbol>,
|
||||
body: Rc<Node<UntypedKind>>,
|
||||
},
|
||||
Call {
|
||||
@@ -69,8 +90,8 @@ pub enum UntypedKind {
|
||||
},
|
||||
/// A macro declaration that can be expanded at compile time.
|
||||
MacroDecl {
|
||||
name: Rc<str>,
|
||||
params: Vec<Rc<str>>,
|
||||
name: Symbol,
|
||||
params: Vec<Symbol>,
|
||||
body: Box<Node<UntypedKind>>,
|
||||
},
|
||||
/// A template for AST nodes, allowing for substitutions. (Quasiquote)
|
||||
|
||||
+9
-9
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
||||
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword};
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::nodes::{Node, UntypedKind, Symbol};
|
||||
|
||||
pub struct Parser<'a> {
|
||||
lexer: Lexer<'a>,
|
||||
@@ -95,7 +95,7 @@ impl<'a> Parser<'a> {
|
||||
"true" => UntypedKind::Constant(Value::Bool(true)),
|
||||
"false" => UntypedKind::Constant(Value::Bool(false)),
|
||||
"void" => UntypedKind::Constant(Value::Void),
|
||||
_ => UntypedKind::Identifier(id),
|
||||
_ => UntypedKind::Identifier(id.into()),
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)),
|
||||
@@ -114,8 +114,8 @@ impl<'a> Parser<'a> {
|
||||
|
||||
let head = self.parse_expression()?;
|
||||
|
||||
let result = if let UntypedKind::Identifier(name) = &head.kind {
|
||||
match name.as_ref() {
|
||||
let result = if let UntypedKind::Identifier(ref sym) = head.kind {
|
||||
match sym.name.as_ref() {
|
||||
"if" => self.parse_if(identity),
|
||||
"fn" => self.parse_fn(identity),
|
||||
"def" => self.parse_def(identity),
|
||||
@@ -151,7 +151,7 @@ impl<'a> Parser<'a> {
|
||||
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
||||
let name_node = self.parse_expression()?;
|
||||
let name = match name_node.kind {
|
||||
UntypedKind::Identifier(s) => s,
|
||||
UntypedKind::Identifier(sym) => sym,
|
||||
_ => return Err("Expected identifier for def name".to_string()),
|
||||
};
|
||||
|
||||
@@ -205,7 +205,7 @@ impl<'a> Parser<'a> {
|
||||
fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
||||
let name_node = self.parse_expression()?;
|
||||
let name = match name_node.kind {
|
||||
UntypedKind::Identifier(s) => s,
|
||||
UntypedKind::Identifier(sym) => sym,
|
||||
_ => return Err("Expected identifier for macro name".to_string()),
|
||||
};
|
||||
let params = self.parse_param_vector()?;
|
||||
@@ -217,7 +217,7 @@ impl<'a> Parser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_param_vector(&mut self) -> Result<Vec<Rc<str>>, String> {
|
||||
fn parse_param_vector(&mut self) -> Result<Vec<Symbol>, String> {
|
||||
if *self.peek() != TokenKind::LeftBracket {
|
||||
return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek()));
|
||||
}
|
||||
@@ -227,7 +227,7 @@ impl<'a> Parser<'a> {
|
||||
while *self.peek() != TokenKind::RightBracket {
|
||||
let token = self.advance()?;
|
||||
match token.kind {
|
||||
TokenKind::Identifier(name) => params.push(name),
|
||||
TokenKind::Identifier(name) => params.push(name.into()),
|
||||
_ => return Err(format!("Expected identifier in param vector, found {:?}", token.kind)),
|
||||
}
|
||||
}
|
||||
@@ -313,7 +313,7 @@ impl<'a> Parser<'a> {
|
||||
fn make_id_node(&self, name: &str, identity: Identity) -> Node<UntypedKind> {
|
||||
Node {
|
||||
identity,
|
||||
kind: UntypedKind::Identifier(Rc::from(name)),
|
||||
kind: UntypedKind::Identifier(Symbol::from(name)),
|
||||
ty: (),
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,10 +20,10 @@ pub struct BenchmarkResult {
|
||||
pub fn run_functional_tests() -> Vec<TestResult> {
|
||||
let mut results = Vec::new();
|
||||
let entries = fs::read_dir("examples").unwrap();
|
||||
let env = Environment::new();
|
||||
let output_re = Regex::new(r";; Output: (.*)").unwrap();
|
||||
|
||||
for entry in entries.filter_map(|e| e.ok()) {
|
||||
let env = Environment::new(); // Fresh environment per test file
|
||||
let path = entry.path();
|
||||
if path.extension().is_some_and(|ext| ext == "myc") {
|
||||
let content = fs::read_to_string(&path).unwrap();
|
||||
|
||||
+11
-6
@@ -200,7 +200,7 @@ macro_rules! dispatch_eval {
|
||||
|
||||
loop {
|
||||
match func_val {
|
||||
Value::Function(f) => return Ok(f(arg_vals)),
|
||||
Value::Function(f) => break Ok(f(arg_vals)),
|
||||
Value::Object(obj) => {
|
||||
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
||||
let old_stack_top = $self.stack.len();
|
||||
@@ -225,14 +225,13 @@ macro_rules! dispatch_eval {
|
||||
arg_vals = next_args;
|
||||
continue;
|
||||
},
|
||||
Ok(val) => return Ok(val),
|
||||
Err(e) => return Err(e),
|
||||
res => break res,
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Object is not a closure: {}", obj.type_name()));
|
||||
break Err(format!("Object is not a closure: {}", obj.type_name()));
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Attempt to call non-function: {}", func_val)),
|
||||
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -315,7 +314,13 @@ impl VM {
|
||||
/// The observed path for debugging.
|
||||
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
|
||||
observer.before_eval(self, node);
|
||||
let result = dispatch_eval!(self, node, eval_observed, observer);
|
||||
|
||||
// Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?)
|
||||
let wrapper = |vm: &mut Self, obs: &mut O| -> Result<Value, String> {
|
||||
dispatch_eval!(vm, node, eval_observed, obs)
|
||||
};
|
||||
|
||||
let result = wrapper(self, observer);
|
||||
observer.after_eval(self, node, &result);
|
||||
result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user