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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user