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:
Michael Schimmel
2026-02-18 14:32:09 +01:00
parent 73ddd644c1
commit 76586c0903
12 changed files with 363 additions and 123 deletions
-1
View File
@@ -1,3 +1,2 @@
/target /target
Delphi Delphi
gemini.md
+7
View File
@@ -0,0 +1,7 @@
;; Output: 5
(do
(def y 1)
(macro m1 [x] `(do (def y 10) (assign y 4)))
(m1 8)
(+ y (m1 8))
)
+40
View File
@@ -0,0 +1,40 @@
## Rust-Portierung des Delphi-AST-Compilers
* Dieses Repository enthält den Rust-Port des Delphi-Compilers.
* Das Ziel ist, die alte Delphi-Codebase nach Rust zu portieren.
* Die alte Delphi-Codebase ist nicht zu verändern.
Wichtig: zentraler Einstiegspunkt ist
@Delphi/Myc.Ast.Environment.pas
und die dort referenzierten Units.
### Design
* Der AST soll 1st class citizen sein. Das Skript ist nur eine mögliche Art, ihn darzustellen.
* Ein geplante Darstellung des AST ist vollständige grafische Visualisierung.
* Es wird eine DSL für Finanzanalyse.
* Aufgrund der geforderten Visualisierbarkeit muss der untyped AST (das, was der User bearbeiten kann) möglichst simpel sein. Komplexität muss vom System übernommen werden.
* Closures sind Key-Feature.
### Concurrency
* Die Root-Scopes sind die Grenze für Multithreading. Nichts verlässt den Root-Scope und alles innerhalb des Scope ist Single-Threaded.
* Globals sind nach dem Bootstrapping immutable.
* Das das Skript single-threaded ist, kann auf atomare Operationen (Mutex, Arc) verzichtet werden!
### Spezielle Datentypen
* Serien sind "unendliche" Queues mit maximaler Länge (Lookback). Serie[0] ist das zuletzt gepushte Item.
* Streams sind virtuelle Konfigurationen aus Serien, die in der Lage sind einen neuen Item-Record zu propagieren.
* Pipes sind Streams mit einer weiteren Funktion: Sie können einen Stream als Input akzeptieren und einen anderen Stream als Output produzieren.
## Portierungsregeln
* Wir wollen die Sprachfeatures von Rust nutzen.
* Der Code soll aber so gut wie möglich nach Rust-Style-Guidelines geschrieben werden.
* Dokumentation nach Rust-Regeln.
## Interaktion
* Ich kann Rust noch nicht so gut. Erkläre mir, was du machst und welche Konzepte du nutzt. Ich will was dazulernen.
* Wenn clippy etwas vorschlägt, von dem du glaubst, dass es nicht stabil ist, dann überprüfe die aktuelle Version. Wahrscheinlich ist das Feature mittlerweile im trunk. Wir gehen immer davon aus, dass clippy recht hat.
+54 -27
View File
@@ -1,7 +1,7 @@
use std::collections::{HashMap, BTreeMap}; use std::collections::{HashMap, BTreeMap};
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; 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::compiler::bound_nodes::{BoundKind, Address};
use crate::ast::types::{Identity, StaticType, Value}; use crate::ast::types::{Identity, StaticType, Value};
@@ -13,7 +13,7 @@ struct LocalInfo {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct CompilerScope { struct CompilerScope {
locals: HashMap<String, LocalInfo>, locals: HashMap<Symbol, LocalInfo>,
slot_count: u32, slot_count: u32,
} }
@@ -25,18 +25,18 @@ impl CompilerScope {
} }
} }
fn define(&mut self, name: &str, ty: StaticType) -> Result<u32, String> { fn define(&mut self, sym: &Symbol, ty: StaticType) -> Result<u32, String> {
if self.locals.contains_key(name) { if self.locals.contains_key(sym) {
return Err(format!("Variable '{}' is already defined in this scope level.", name)); return Err(format!("Variable '{}' is already defined in this scope level.", sym.name));
} }
let slot = self.slot_count; 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; self.slot_count += 1;
Ok(slot) Ok(slot)
} }
fn resolve(&self, name: &str) -> Option<LocalInfo> { fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> {
self.locals.get(name).cloned() self.locals.get(sym).cloned()
} }
} }
@@ -65,18 +65,18 @@ impl FunctionCompiler {
pub struct Binder { pub struct Binder {
functions: Vec<FunctionCompiler>, functions: Vec<FunctionCompiler>,
// Globals mapping: Name -> (Index, Type) // Globals mapping: Symbol -> (Index, Type)
globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
// Map of Declaration Identity -> List of Lambda Identities that capture it // Map of Declaration Identity -> List of Lambda Identities that capture it
capture_map: HashMap<Identity, Vec<Identity>>, capture_map: HashMap<Identity, Vec<Identity>>,
} }
impl Binder { 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()) 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 { let mut binder = Self {
functions: Vec::new(), functions: Vec::new(),
globals, globals,
@@ -86,7 +86,7 @@ impl Binder {
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 captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
let mut binder = Self::with_boxed(globals, captures); let mut binder = Self::with_boxed(globals, captures);
binder.bind(node) binder.bind(node)
@@ -100,8 +100,8 @@ impl Binder {
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty)) Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty))
}, },
UntypedKind::Identifier(name) => { UntypedKind::Identifier(sym) => {
let (addr, ty) = self.resolve_variable(name)?; let (addr, ty) = self.resolve_variable(sym)?;
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty)) Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty))
}, },
@@ -138,11 +138,11 @@ impl Binder {
// 1. Pre-declare name to support recursion // 1. Pre-declare name to support recursion
let slot_or_idx = if self.functions.len() == 1 { let slot_or_idx = if self.functions.len() == 1 {
let mut globals = self.globals.borrow_mut(); let mut globals = self.globals.borrow_mut();
if globals.contains_key(name.as_ref()) { if globals.contains_key(name) {
return Err(format!("Global variable '{}' is already defined.", name)); return Err(format!("Global variable '{}' is already defined.", name.name));
} }
let idx = globals.len() as u32; let idx = globals.len() as u32;
globals.insert(name.to_string(), (idx, StaticType::Any)); globals.insert(name.clone(), (idx, StaticType::Any));
idx idx
} else { } else {
let current_fn = self.functions.last_mut().unwrap(); let current_fn = self.functions.last_mut().unwrap();
@@ -157,7 +157,7 @@ impl Binder {
if self.functions.len() == 1 { if self.functions.len() == 1 {
{ {
let mut globals = self.globals.borrow_mut(); 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(); entry.1 = ty.clone();
} }
} }
@@ -168,7 +168,7 @@ impl Binder {
} else { } else {
{ {
let current_fn = self.functions.last_mut().unwrap(); 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(); info.ty = ty.clone();
} }
} }
@@ -184,8 +184,8 @@ impl Binder {
UntypedKind::Assign { target, value } => { UntypedKind::Assign { target, value } => {
let val_node = self.bind(value)?; let val_node = self.bind(value)?;
if let UntypedKind::Identifier(name) = &target.kind { if let UntypedKind::Identifier(sym) = &target.kind {
let (addr, target_ty) = self.resolve_variable(name)?; let (addr, target_ty) = self.resolve_variable(sym)?;
// Type check: value must be compatible with target // Type check: value must be compatible with target
if target_ty != StaticType::Any && val_node.ty != StaticType::Any && target_ty != val_node.ty { 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; let current_fn_idx = self.functions.len() - 1;
// 1. Try local in current function // 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)); return Ok((Address::Local(info.slot), info.ty));
} }
// 2. Try enclosing scopes (capture chain) // 2. Try enclosing scopes (capture chain)
for i in (0..current_fn_idx).rev() { 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 mut addr = Address::Local(info.slot);
let ty = info.ty.clone(); let ty = info.ty.clone();
@@ -345,11 +345,20 @@ impl Binder {
// 3. Try Global // 3. Try Global
let globals = self.globals.borrow(); 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())); 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> { 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.is_err());
assert!(result.unwrap_err().contains("already defined")); 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"));
}
} }
+191 -48
View File
@@ -1,17 +1,31 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::ast::nodes::{Node, UntypedKind}; use crate::ast::nodes::{Node, UntypedKind, Symbol};
use crate::ast::types::{Value, Identity}; use crate::ast::types::{Value, Identity};
/// Trait for evaluating expressions during macro expansion. /// Trait for evaluating expressions during macro expansion.
/// Similar to TMacroEvaluatorProc in the Delphi implementation.
pub trait MacroEvaluator { pub trait MacroEvaluator {
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String>; 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. /// A registry for macro declarations.
pub struct MacroRegistry { 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 { impl MacroRegistry {
@@ -64,7 +78,8 @@ impl<E: MacroEvaluator> MacroExpander<E> {
fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> { fn expand_recursive(&mut self, node: Node<UntypedKind>) -> Result<Node<UntypedKind>, String> {
match node.kind { match node.kind {
UntypedKind::MacroDecl { name, params, body } => { 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 { Ok(Node {
identity: node.identity, identity: node.identity,
kind: UntypedKind::Nop, kind: UntypedKind::Nop,
@@ -73,15 +88,14 @@ impl<E: MacroEvaluator> MacroExpander<E> {
} }
UntypedKind::Call { callee, args } => { UntypedKind::Call { callee, args } => {
if let UntypedKind::Identifier(ref name) = callee.kind { if let UntypedKind::Identifier(ref sym) = callee.kind
if let Some((params, body)) = self.registry.lookup(name) { && let Some((params, body)) = self.registry.lookup(&sym.name) {
let mut expanded_args = Vec::new(); let mut expanded_args = Vec::new();
for arg in args { for arg in args {
expanded_args.push(self.expand_recursive(arg)?); 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); return self.expand_recursive(expanded);
}
} }
let expanded_callee = self.expand_recursive(*callee)?; let expanded_callee = self.expand_recursive(*callee)?;
@@ -208,9 +222,15 @@ impl<E: MacroEvaluator> MacroExpander<E> {
bindings.insert(p, a); 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 { 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 { } else {
// Static AST fragment: No substitution, no hygiene.
body body
}; };
@@ -219,10 +239,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
kind: UntypedKind::Call { kind: UntypedKind::Call {
callee: Box::new(Node { callee: Box::new(Node {
identity: identity.clone(), identity: identity.clone(),
kind: UntypedKind::Identifier(Rc::from(name)), kind: UntypedKind::Identifier(Symbol::from(name)),
ty: (), ty: (),
}), }),
args: bindings.iter().map(|(_, v)| v.clone()).collect(), args: bindings.into_values().collect(),
}, },
ty: (), 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 { 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) => { 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)) Ok(self.value_to_node(val, node.identity))
} }
UntypedKind::Splice(_) => { UntypedKind::Splice(inner) => {
Err("Splice node found outside of a container context".to_string()) // 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 } => { UntypedKind::Tuple { elements } => {
let mut new_elements = Vec::new(); let mut new_elements = Vec::new();
for e in elements { for e in elements {
if let UntypedKind::Splice(ref inner) = e.kind { 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)?; self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?;
} else { } else {
new_elements.push(self.expand_template(e, bindings)?); new_elements.push(self.expand_template(e, state)?);
} }
} }
Ok(Node { Ok(Node {
@@ -269,10 +326,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
let mut new_exprs = Vec::new(); let mut new_exprs = Vec::new();
for e in exprs { for e in exprs {
if let UntypedKind::Splice(ref inner) = e.kind { 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)?; self.handle_splice_value(val, node.identity.clone(), &mut new_exprs)?;
} else { } else {
new_exprs.push(self.expand_template(e, bindings)?); new_exprs.push(self.expand_template(e, state)?);
} }
} }
Ok(Node { Ok(Node {
@@ -285,7 +342,7 @@ impl<E: MacroEvaluator> MacroExpander<E> {
UntypedKind::Map { entries } => { UntypedKind::Map { entries } => {
let mut new_entries = Vec::new(); let mut new_entries = Vec::new();
for (k, v) in entries { 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 { Ok(Node {
identity: node.identity, identity: node.identity,
@@ -295,14 +352,14 @@ impl<E: MacroEvaluator> MacroExpander<E> {
} }
UntypedKind::Call { callee, args } => { 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(); let mut expanded_args = Vec::new();
for arg in args { for arg in args {
if let UntypedKind::Splice(ref inner) = arg.kind { 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)?; self.handle_splice_value(val, node.identity.clone(), &mut expanded_args)?;
} else { } else {
expanded_args.push(self.expand_template(arg, bindings)?); expanded_args.push(self.expand_template(arg, state)?);
} }
} }
Ok(Node { Ok(Node {
@@ -313,10 +370,10 @@ impl<E: MacroEvaluator> MacroExpander<E> {
} }
UntypedKind::If { cond, then_br, else_br } => { UntypedKind::If { cond, then_br, else_br } => {
let cond = self.expand_template(*cond, bindings)?; let cond = self.expand_template(*cond, state)?;
let then_br = self.expand_template(*then_br, bindings)?; let then_br = self.expand_template(*then_br, state)?;
let else_br = if let Some(e) = else_br { 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 { } else {
None 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), _ => Ok(node),
} }
} }
@@ -369,28 +438,28 @@ impl<E: MacroEvaluator> MacroExpander<E> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::cell::RefCell;
use crate::ast::parser::Parser; use crate::ast::parser::Parser;
use crate::ast::types::Value; use crate::ast::types::{Value, StaticType, Object};
use crate::ast::compiler::Binder;
struct SimpleEvaluator; struct SimpleEvaluator;
impl MacroEvaluator for SimpleEvaluator { impl MacroEvaluator for SimpleEvaluator {
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> { 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 UntypedKind::Identifier(ref sym) = node.kind
if let Some(arg) = bindings.get(name) { && let Some(arg) = bindings.get(&sym.name) {
return Ok(Value::Object(Rc::new(arg.clone()))); 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)) Err(format!("SimpleEvaluator cannot evaluate complex expression: {:?}", node.kind))
} }
} }
#[test] #[test]
fn test_macro_expansion_unless() { fn test_macro_expansion_hygiene() {
let source = " let source = "
(do (do
(macro unless [c b] `(if ~c void ~b)) (macro m [] `(def y 10))
(unless true 42)) (m))
"; ";
let mut parser = Parser::new(source).unwrap(); let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap(); let untyped = parser.parse_expression().unwrap();
@@ -399,28 +468,42 @@ mod tests {
let expanded = expander.expand(untyped).unwrap(); let expanded = expander.expand(untyped).unwrap();
if let UntypedKind::Block { exprs } = &expanded.kind { if let UntypedKind::Block { exprs } = &expanded.kind {
assert_eq!(exprs.len(), 2); if let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind {
assert!(matches!(exprs[0].kind, UntypedKind::Nop)); 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();
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 { call: _, expanded: result } = &exprs[1].kind { if let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind {
if let UntypedKind::If { cond, then_br, else_br } = &result.kind { if let UntypedKind::If { cond, then_br, else_br } = &result.kind {
if let UntypedKind::Constant(Value::Bool(b)) = &cond.kind { if let UntypedKind::Constant(Value::Bool(b)) = &cond.kind {
assert_eq!(*b, true); assert_eq!(*b, false);
} else { panic!("Expected true, got {:?}", cond.kind); } } else { panic!("Expected false, got {:?}", cond.kind); }
assert!(matches!(then_br.kind, UntypedKind::Constant(Value::Void))); assert!(matches!(then_br.kind, UntypedKind::Constant(Value::Void)));
if let Some(eb) = else_br { if let Some(eb) = else_br {
if let UntypedKind::Constant(Value::Int(n)) = &eb.kind { if let UntypedKind::Constant(Value::Int(n)) = &eb.kind {
assert_eq!(*n, 42); assert_eq!(*n, 42);
} else { panic!("Expected 42, got {:?}", eb.kind); } } else { panic!("Expected 42, got {:?}", eb.kind); }
} else { panic!("Else branch missing"); } } 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 { if let UntypedKind::Tuple { elements } = &result.kind {
assert_eq!(elements.len(), 5); 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[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); } 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());
}
} }
+4 -5
View File
@@ -1,6 +1,5 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::rc::Rc; use crate::ast::nodes::{Node, UntypedKind, Symbol};
use crate::ast::nodes::{Node, UntypedKind};
use crate::ast::types::Identity; use crate::ast::types::Identity;
/// Analyzes the AST to find which lambdas capture which variable declarations. /// Analyzes the AST to find which lambdas capture which variable declarations.
@@ -22,15 +21,15 @@ impl UpvalueAnalyzer {
fn visit( fn visit(
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
scopes: &mut Vec<HashMap<Rc<str>, Identity>>, scopes: &mut Vec<HashMap<Symbol, Identity>>,
capture_map: &mut HashMap<Identity, HashSet<Identity>>, capture_map: &mut HashMap<Identity, HashSet<Identity>>,
current_lambda: Option<Identity> current_lambda: Option<Identity>
) { ) {
match &node.kind { match &node.kind {
UntypedKind::Identifier(name) => { UntypedKind::Identifier(sym) => {
// Resolve name in scope stack (from inner to outer) // Resolve name in scope stack (from inner to outer)
for (depth, scope) in scopes.iter().rev().enumerate() { 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 { if depth > 0 {
// Captured from an outer scope! // Captured from an outer scope!
if let Some(lambda_id) = &current_lambda { if let Some(lambda_id) = &current_lambda {
+6 -9
View File
@@ -2,7 +2,7 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use crate::ast::types::{Value, StaticType, Object}; 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::parser::Parser;
use crate::ast::compiler::binder::Binder; use crate::ast::compiler::binder::Binder;
use crate::ast::compiler::BoundKind; use crate::ast::compiler::BoundKind;
@@ -13,29 +13,26 @@ use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator}; use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator};
pub struct Environment { 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 global_values: Rc<RefCell<Vec<Value>>>,
pub debug_mode: bool, pub debug_mode: bool,
} }
/// Evaluator used during macro expansion to allow compile-time logic. /// Evaluator used during macro expansion to allow compile-time logic.
struct RuntimeMacroEvaluator { struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, global_names: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
global_values: Rc<RefCell<Vec<Value>>>, global_values: Rc<RefCell<Vec<Value>>>,
} }
impl MacroEvaluator for RuntimeMacroEvaluator { impl MacroEvaluator for RuntimeMacroEvaluator {
fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> { fn evaluate(&self, node: &Node<UntypedKind>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>) -> Result<Value, String> {
// 1. Check if it's a simple parameter substitution // 1. Check if it's a simple parameter substitution
if let UntypedKind::Identifier(name) = &node.kind { if let UntypedKind::Identifier(sym) = &node.kind
if let Some(arg_node) = bindings.get(name) { && let Some(arg_node) = bindings.get(&sym.name) {
return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>)); return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>));
}
} }
// 2. Full evaluation for complex compile-time expressions // 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 bound_ast = Binder::bind_root(self.global_names.clone(), node)?;
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
vm.run(&bound_ast) vm.run(&bound_ast)
@@ -76,7 +73,7 @@ impl Environment {
let mut values = self.global_values.borrow_mut(); let mut values = self.global_values.borrow_mut();
let idx = values.len() as u32; 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))); values.push(Value::Function(Rc::new(func)));
} }
+26 -5
View File
@@ -3,6 +3,27 @@ use std::fmt::Debug;
use std::any::Any; use std::any::Any;
use crate::ast::types::{Identity, Value, Object}; 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 /// A generic AST Node wrapper to preserve identity and metadata
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Node<K, T = ()> { pub struct Node<K, T = ()> {
@@ -36,14 +57,14 @@ impl Clone for Box<dyn CustomNode> {
pub enum UntypedKind { pub enum UntypedKind {
Nop, Nop,
Constant(Value), Constant(Value),
Identifier(Rc<str>), Identifier(Symbol),
If { If {
cond: Box<Node<UntypedKind>>, cond: Box<Node<UntypedKind>>,
then_br: Box<Node<UntypedKind>>, then_br: Box<Node<UntypedKind>>,
else_br: Option<Box<Node<UntypedKind>>>, else_br: Option<Box<Node<UntypedKind>>>,
}, },
Def { Def {
name: Rc<str>, name: Symbol,
value: Box<Node<UntypedKind>>, value: Box<Node<UntypedKind>>,
}, },
Assign { Assign {
@@ -51,7 +72,7 @@ pub enum UntypedKind {
value: Box<Node<UntypedKind>>, value: Box<Node<UntypedKind>>,
}, },
Lambda { Lambda {
params: Vec<Rc<str>>, params: Vec<Symbol>,
body: Rc<Node<UntypedKind>>, body: Rc<Node<UntypedKind>>,
}, },
Call { Call {
@@ -69,8 +90,8 @@ pub enum UntypedKind {
}, },
/// A macro declaration that can be expanded at compile time. /// A macro declaration that can be expanded at compile time.
MacroDecl { MacroDecl {
name: Rc<str>, name: Symbol,
params: Vec<Rc<str>>, params: Vec<Symbol>,
body: Box<Node<UntypedKind>>, body: Box<Node<UntypedKind>>,
}, },
/// A template for AST nodes, allowing for substitutions. (Quasiquote) /// A template for AST nodes, allowing for substitutions. (Quasiquote)
+9 -9
View File
@@ -1,7 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::lexer::{Lexer, Token, TokenKind};
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; 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> { pub struct Parser<'a> {
lexer: Lexer<'a>, lexer: Lexer<'a>,
@@ -95,7 +95,7 @@ impl<'a> Parser<'a> {
"true" => UntypedKind::Constant(Value::Bool(true)), "true" => UntypedKind::Constant(Value::Bool(true)),
"false" => UntypedKind::Constant(Value::Bool(false)), "false" => UntypedKind::Constant(Value::Bool(false)),
"void" => UntypedKind::Constant(Value::Void), "void" => UntypedKind::Constant(Value::Void),
_ => UntypedKind::Identifier(id), _ => UntypedKind::Identifier(id.into()),
} }
} }
_ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)), _ => 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 head = self.parse_expression()?;
let result = if let UntypedKind::Identifier(name) = &head.kind { let result = if let UntypedKind::Identifier(ref sym) = head.kind {
match name.as_ref() { match sym.name.as_ref() {
"if" => self.parse_if(identity), "if" => self.parse_if(identity),
"fn" => self.parse_fn(identity), "fn" => self.parse_fn(identity),
"def" => self.parse_def(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> { fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let name_node = self.parse_expression()?; let name_node = self.parse_expression()?;
let name = match name_node.kind { let name = match name_node.kind {
UntypedKind::Identifier(s) => s, UntypedKind::Identifier(sym) => sym,
_ => return Err("Expected identifier for def name".to_string()), _ => 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> { fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let name_node = self.parse_expression()?; let name_node = self.parse_expression()?;
let name = match name_node.kind { let name = match name_node.kind {
UntypedKind::Identifier(s) => s, UntypedKind::Identifier(sym) => sym,
_ => return Err("Expected identifier for macro name".to_string()), _ => return Err("Expected identifier for macro name".to_string()),
}; };
let params = self.parse_param_vector()?; 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 { if *self.peek() != TokenKind::LeftBracket {
return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek()));
} }
@@ -227,7 +227,7 @@ impl<'a> Parser<'a> {
while *self.peek() != TokenKind::RightBracket { while *self.peek() != TokenKind::RightBracket {
let token = self.advance()?; let token = self.advance()?;
match token.kind { 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)), _ => 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> { fn make_id_node(&self, name: &str, identity: Identity) -> Node<UntypedKind> {
Node { Node {
identity, identity,
kind: UntypedKind::Identifier(Rc::from(name)), kind: UntypedKind::Identifier(Symbol::from(name)),
ty: (), ty: (),
} }
} }
+1 -1
View File
@@ -20,10 +20,10 @@ pub struct BenchmarkResult {
pub fn run_functional_tests() -> Vec<TestResult> { pub fn run_functional_tests() -> Vec<TestResult> {
let mut results = Vec::new(); let mut results = Vec::new();
let entries = fs::read_dir("examples").unwrap(); let entries = fs::read_dir("examples").unwrap();
let env = Environment::new();
let output_re = Regex::new(r";; Output: (.*)").unwrap(); let output_re = Regex::new(r";; Output: (.*)").unwrap();
for entry in entries.filter_map(|e| e.ok()) { for entry in entries.filter_map(|e| e.ok()) {
let env = Environment::new(); // Fresh environment per test file
let path = entry.path(); let path = entry.path();
if path.extension().is_some_and(|ext| ext == "myc") { if path.extension().is_some_and(|ext| ext == "myc") {
let content = fs::read_to_string(&path).unwrap(); let content = fs::read_to_string(&path).unwrap();
+11 -6
View File
@@ -200,7 +200,7 @@ macro_rules! dispatch_eval {
loop { loop {
match func_val { match func_val {
Value::Function(f) => return Ok(f(arg_vals)), Value::Function(f) => break Ok(f(arg_vals)),
Value::Object(obj) => { Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() { if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let old_stack_top = $self.stack.len(); let old_stack_top = $self.stack.len();
@@ -225,14 +225,13 @@ macro_rules! dispatch_eval {
arg_vals = next_args; arg_vals = next_args;
continue; continue;
}, },
Ok(val) => return Ok(val), res => break res,
Err(e) => return Err(e),
} }
} else { } 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. /// The observed path for debugging.
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &Node<BoundKind, StaticType>) -> Result<Value, String> { fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
observer.before_eval(self, node); 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); observer.after_eval(self, node, &result);
result result
} }
+13 -11
View File
@@ -87,6 +87,9 @@ impl CompilerApp {
let start_total = std::time::Instant::now(); let start_total = std::time::Instant::now();
self.trace_logs.clear(); self.trace_logs.clear();
// Reset environment for a fresh run
self.env = Environment::new();
let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration, std::time::Duration, std::time::Duration), String> { let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration, std::time::Duration, std::time::Duration), String> {
let start_compile = std::time::Instant::now(); let start_compile = std::time::Instant::now();
let compiled = self.env.compile(&self.source_code)?; let compiled = self.env.compile(&self.source_code)?;
@@ -110,11 +113,10 @@ impl CompilerApp {
} }
for line in &mut logs { for line in &mut logs {
if line.len() > 255 { if line.len() > 255
if let Some((idx, _)) = line.char_indices().nth(255) { && let Some((idx, _)) = line.char_indices().nth(255) {
line.truncate(idx); line.truncate(idx);
line.push_str("..."); line.push_str("...");
}
} }
} }
@@ -154,6 +156,7 @@ impl CompilerApp {
} }
fn dump_ast(&mut self) { fn dump_ast(&mut self) {
self.env = Environment::new();
match self.env.dump_ast(&self.source_code) { match self.env.dump_ast(&self.source_code) {
Ok(dump) => { Ok(dump) => {
self.output_log = format!( self.output_log = format!(
@@ -367,6 +370,7 @@ impl eframe::App for CompilerApp {
// Log Output Area // Log Output Area
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.id_salt("output_log_scroll") .id_salt("output_log_scroll")
.auto_shrink([false, true]) // Don't shrink horizontally, fill width
.show(ui, |ui| { .show(ui, |ui| {
match self.active_tab { match self.active_tab {
AppTab::Output => { AppTab::Output => {
@@ -379,14 +383,12 @@ impl eframe::App for CompilerApp {
); );
}, },
AppTab::Trace => { AppTab::Trace => {
egui::ScrollArea::vertical() ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
.id_salt("trace_log_scroll") ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
.show(ui, |ui| { for line in &self.trace_logs {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.label(line);
for line in &self.trace_logs { }
ui.label(line); });
}
});
} }
} }
}); });