Formatting

This commit is contained in:
Michael Schimmel
2026-02-22 02:35:06 +01:00
parent 2123f1d279
commit 329b885c4b
25 changed files with 8053 additions and 6400 deletions
+501 -412
View File
@@ -1,412 +1,501 @@
use std::collections::HashMap; use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode};
use std::rc::Rc; use crate::ast::nodes::{Node, Symbol, UntypedKind};
use std::cell::RefCell; use crate::ast::types::{Identity, StaticType};
use crate::ast::nodes::{Node, UntypedKind, Symbol}; use std::cell::RefCell;
use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode}; use std::collections::HashMap;
use crate::ast::types::{Identity, StaticType}; use std::rc::Rc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct LocalInfo { struct LocalInfo {
slot: u32, slot: u32,
// Note: Binder doesn't strictly need the type anymore, // Note: Binder doesn't strictly need the type anymore,
// but it might be useful for built-ins during resolution. // but it might be useful for built-ins during resolution.
// For now we keep it as Any or Unknown. // For now we keep it as Any or Unknown.
_ty: StaticType, _ty: StaticType,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct CompilerScope { struct CompilerScope {
locals: HashMap<Symbol, LocalInfo>, locals: HashMap<Symbol, LocalInfo>,
slot_count: u32, slot_count: u32,
} }
impl CompilerScope { impl CompilerScope {
fn new() -> Self { fn new() -> Self {
Self { Self {
locals: HashMap::new(), locals: HashMap::new(),
slot_count: 0, slot_count: 0,
} }
} }
fn define(&mut self, sym: &Symbol) -> Result<u32, String> { fn define(&mut self, sym: &Symbol) -> Result<u32, String> {
if self.locals.contains_key(sym) { if self.locals.contains_key(sym) {
return Err(format!("Variable '{}' is already defined in this scope level.", sym.name)); return Err(format!(
} "Variable '{}' is already defined in this scope level.",
let slot = self.slot_count; sym.name
self.locals.insert(sym.clone(), LocalInfo { slot, _ty: StaticType::Any }); ));
self.slot_count += 1; }
Ok(slot) let slot = self.slot_count;
} self.locals.insert(
sym.clone(),
fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> { LocalInfo {
self.locals.get(sym).cloned() slot,
} _ty: StaticType::Any,
} },
);
struct FunctionCompiler { self.slot_count += 1;
scope: CompilerScope, Ok(slot)
upvalues: Vec<Address>, }
}
fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> {
impl FunctionCompiler { self.locals.get(sym).cloned()
fn new() -> Self { }
Self { }
scope: CompilerScope::new(),
upvalues: Vec::new(), struct FunctionCompiler {
} scope: CompilerScope,
} upvalues: Vec<Address>,
}
fn add_upvalue(&mut self, addr: Address) -> u32 {
if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { impl FunctionCompiler {
return idx as u32; fn new() -> Self {
} Self {
let idx = self.upvalues.len() as u32; scope: CompilerScope::new(),
self.upvalues.push(addr); upvalues: Vec::new(),
idx }
} }
}
fn add_upvalue(&mut self, addr: Address) -> u32 {
pub struct Binder { if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
functions: Vec<FunctionCompiler>, return idx as u32;
// Globals mapping: Symbol -> Index }
globals: Rc<RefCell<HashMap<Symbol, u32>>>, let idx = self.upvalues.len() as u32;
// Map of Declaration Identity -> List of Lambda Identities that capture it self.upvalues.push(addr);
capture_map: HashMap<Identity, Vec<Identity>>, idx
} }
}
impl Binder {
pub fn new(globals: Rc<RefCell<HashMap<Symbol, u32>>>) -> Self { pub struct Binder {
Self::with_boxed(globals, HashMap::new()) functions: Vec<FunctionCompiler>,
} // Globals mapping: Symbol -> Index
globals: Rc<RefCell<HashMap<Symbol, u32>>>,
fn with_boxed(globals: Rc<RefCell<HashMap<Symbol, u32>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self { // Map of Declaration Identity -> List of Lambda Identities that capture it
let mut binder = Self { capture_map: HashMap<Identity, Vec<Identity>>,
functions: Vec::new(), }
globals,
capture_map: captures, impl Binder {
}; pub fn new(globals: Rc<RefCell<HashMap<Symbol, u32>>>) -> Self {
binder.functions.push(FunctionCompiler::new()); Self::with_boxed(globals, HashMap::new())
binder }
}
fn with_boxed(
pub fn bind_root(globals: Rc<RefCell<HashMap<Symbol, u32>>>, node: &Node<UntypedKind>) -> Result<BoundNode, String> { globals: Rc<RefCell<HashMap<Symbol, u32>>>,
let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); captures: HashMap<Identity, Vec<Identity>>,
let mut binder = Self::with_boxed(globals, captures); ) -> Self {
binder.bind(node) let mut binder = Self {
} functions: Vec::new(),
globals,
pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> { capture_map: captures,
match &node.kind { };
UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)), binder.functions.push(FunctionCompiler::new());
UntypedKind::Constant(v) => { binder
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()))) }
},
pub fn bind_root(
UntypedKind::Identifier(sym) => { globals: Rc<RefCell<HashMap<Symbol, u32>>>,
let addr = self.resolve_variable(sym)?; node: &Node<UntypedKind>,
Ok(self.make_node(node.identity.clone(), BoundKind::Get { ) -> Result<BoundNode, String> {
addr, let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
name: sym.clone() let mut binder = Self::with_boxed(globals, captures);
})) binder.bind(node)
}, }
UntypedKind::Parameter(sym) => { pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
// Parameters are ONLY allowed if we are inside a function (Binder stack > 1) match &node.kind {
if self.functions.len() <= 1 { UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)),
return Err(format!("Parameter '{}' is not allowed in root scope.", sym.name)); UntypedKind::Constant(v) => {
} Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone())))
let current_fn = self.functions.last_mut().unwrap(); }
let slot = current_fn.scope.define(sym)?;
Ok(self.make_node(node.identity.clone(), BoundKind::Parameter { UntypedKind::Identifier(sym) => {
name: sym.clone(), let addr = self.resolve_variable(sym)?;
slot Ok(self.make_node(
})) node.identity.clone(),
}, BoundKind::Get {
addr,
UntypedKind::If { cond, then_br, else_br } => { name: sym.clone(),
let cond = self.bind(cond)?; },
let then_br = self.bind(then_br)?; ))
let mut else_br_bound = None; }
if let Some(e) = else_br {
else_br_bound = Some(Box::new(self.bind(e)?)); UntypedKind::Parameter(sym) => {
} // Parameters are ONLY allowed if we are inside a function (Binder stack > 1)
if self.functions.len() <= 1 {
Ok(self.make_node(node.identity.clone(), BoundKind::If { return Err(format!(
cond: Box::new(cond), "Parameter '{}' is not allowed in root scope.",
then_br: Box::new(then_br), sym.name
else_br: else_br_bound ));
})) }
}, let current_fn = self.functions.last_mut().unwrap();
let slot = current_fn.scope.define(sym)?;
UntypedKind::Def { name, value } => { Ok(self.make_node(
// 1. Pre-declare name to support recursion node.identity.clone(),
let slot_or_idx = if self.functions.len() == 1 { BoundKind::Parameter {
let mut globals = self.globals.borrow_mut(); name: sym.clone(),
if globals.contains_key(name) { slot,
return Err(format!("Global variable '{}' is already defined.", name.name)); },
} ))
let idx = globals.len() as u32; }
globals.insert(name.clone(), idx);
idx UntypedKind::If {
} else { cond,
let current_fn = self.functions.last_mut().unwrap(); then_br,
current_fn.scope.define(name)? else_br,
}; } => {
let cond = self.bind(cond)?;
// 2. Bind Value (now 'name' is visible) let then_br = self.bind(then_br)?;
let val_node = self.bind(value)?; let mut else_br_bound = None;
if let Some(e) = else_br {
// 3. Return Node else_br_bound = Some(Box::new(self.bind(e)?));
if self.functions.len() == 1 { }
Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal {
name: name.clone(), Ok(self.make_node(
global_index: slot_or_idx, node.identity.clone(),
value: Box::new(val_node) BoundKind::If {
})) cond: Box::new(cond),
} else { then_br: Box::new(then_br),
let captured_by = self.capture_map.get(&node.identity).cloned().unwrap_or_default(); else_br: else_br_bound,
Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal { },
name: name.clone(), ))
slot: slot_or_idx, }
value: Box::new(val_node) ,
captured_by UntypedKind::Def { name, value } => {
})) // 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) {
UntypedKind::Assign { target, value } => { return Err(format!(
let val_node = self.bind(value)?; "Global variable '{}' is already defined.",
name.name
if let UntypedKind::Identifier(sym) = &target.kind { ));
let addr = self.resolve_variable(sym)?; }
Ok(self.make_node(node.identity.clone(), BoundKind::Set { let idx = globals.len() as u32;
addr, globals.insert(name.clone(), idx);
value: Box::new(val_node) idx
})) } else {
} else { let current_fn = self.functions.last_mut().unwrap();
Err("Assignment target must be an identifier".to_string()) current_fn.scope.define(name)?
} };
},
// 2. Bind Value (now 'name' is visible)
UntypedKind::Lambda { params, body } => { let val_node = self.bind(value)?;
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new()); // 3. Return Node
if self.functions.len() == 1 {
// 1. Bind the parameter pattern/tuple Ok(self.make_node(
let params_bound = self.bind(params)?; node.identity.clone(),
BoundKind::DefGlobal {
// 2. Bind the body name: name.clone(),
let body_bound = self.bind(body)?; global_index: slot_or_idx,
value: Box::new(val_node),
let compiled_fn = self.functions.pop().unwrap(); },
))
// 3. Static optimization: check if parameters are purely positional } else {
let positional_count = match &params_bound.kind { let captured_by = self
BoundKind::Tuple { elements } => { .capture_map
let mut count = 0; .get(&node.identity)
let mut all_params = true; .cloned()
for e in elements { .unwrap_or_default();
if matches!(e.kind, BoundKind::Parameter { .. }) { Ok(self.make_node(
count += 1; node.identity.clone(),
} else { BoundKind::DefLocal {
all_params = false; name: name.clone(),
break; slot: slot_or_idx,
} value: Box::new(val_node),
} captured_by,
if all_params { Some(count) } else { None } },
} ))
BoundKind::Parameter { .. } => Some(1), }
_ => None, }
};
UntypedKind::Assign { target, value } => {
Ok(self.make_node(identity, BoundKind::Lambda { let val_node = self.bind(value)?;
params: Rc::new(params_bound),
upvalues: compiled_fn.upvalues, if let UntypedKind::Identifier(sym) = &target.kind {
body: Rc::new(body_bound), let addr = self.resolve_variable(sym)?;
positional_count, Ok(self.make_node(
})) node.identity.clone(),
}, BoundKind::Set {
addr,
UntypedKind::Call { callee, args } => { value: Box::new(val_node),
let callee = self.bind(callee)?; },
let args = self.bind(args)?; ))
} else {
Ok(self.make_node(node.identity.clone(), BoundKind::Call { Err("Assignment target must be an identifier".to_string())
callee: Box::new(callee), }
args: Box::new(args) }
}))
}, UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
UntypedKind::Block { exprs } => { self.functions.push(FunctionCompiler::new());
let mut bound_exprs = Vec::new();
for expr in exprs { // 1. Bind the parameter pattern/tuple
bound_exprs.push(self.bind(expr)?); let params_bound = self.bind(params)?;
}
Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs })) // 2. Bind the body
}, let body_bound = self.bind(body)?;
UntypedKind::Tuple { elements } => { let compiled_fn = self.functions.pop().unwrap();
let mut bound_elems = Vec::new();
for e in elements { // 3. Static optimization: check if parameters are purely positional
bound_elems.push(self.bind(e)?); let positional_count = match &params_bound.kind {
} BoundKind::Tuple { elements } => {
Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems })) let mut count = 0;
}, let mut all_params = true;
for e in elements {
UntypedKind::Record { fields } => { if matches!(e.kind, BoundKind::Parameter { .. }) {
let mut bound_fields = Vec::new(); count += 1;
for (k, v) in fields { } else {
bound_fields.push((self.bind(k)?, self.bind(v)?)); all_params = false;
} break;
Ok(self.make_node(node.identity.clone(), BoundKind::Record { fields: bound_fields })) }
}, }
if all_params { Some(count) } else { None }
UntypedKind::Expansion { call, expanded } => { }
let bound_expanded = self.bind(expanded)?; BoundKind::Parameter { .. } => Some(1),
Ok(self.make_node(node.identity.clone(), BoundKind::Expansion { _ => None,
original_call: Rc::from(call.as_ref().clone()), };
bound_expanded: Box::new(bound_expanded),
})) Ok(self.make_node(
}, identity,
BoundKind::Lambda {
UntypedKind::Template(_) | UntypedKind::Placeholder(_) | UntypedKind::Splice(_) | UntypedKind::MacroDecl { .. } => { params: Rc::new(params_bound),
Err(format!("Macro construct {:?} found in Binder. Macros must be expanded before binding.", node.kind)) upvalues: compiled_fn.upvalues,
} body: Rc::new(body_bound),
positional_count,
UntypedKind::Extension(_) => { },
// Future: Delegate to extension binder ))
Err("Custom extensions not supported in Binder yet".to_string()) }
}
} UntypedKind::Call { callee, args } => {
} let callee = self.bind(callee)?;
let args = self.bind(args)?;
fn resolve_variable(&mut self, sym: &Symbol) -> Result<Address, String> {
let current_fn_idx = self.functions.len() - 1; Ok(self.make_node(
node.identity.clone(),
// 1. Try local in current function BoundKind::Call {
if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) { callee: Box::new(callee),
return Ok(Address::Local(info.slot)); args: Box::new(args),
} },
))
// 2. Try enclosing scopes (capture chain) }
for i in (0..current_fn_idx).rev() {
if let Some(info) = self.functions[i].scope.resolve(sym) { UntypedKind::Block { exprs } => {
let mut addr = Address::Local(info.slot); let mut bound_exprs = Vec::new();
for expr in exprs {
for k in (i + 1)..=current_fn_idx { bound_exprs.push(self.bind(expr)?);
addr = Address::Upvalue(self.functions[k].add_upvalue(addr)); }
} Ok(self.make_node(
return Ok(addr); node.identity.clone(),
} BoundKind::Block { exprs: bound_exprs },
} ))
}
// 3. Try Global
let globals = self.globals.borrow(); UntypedKind::Tuple { elements } => {
if let Some(idx) = globals.get(sym) { let mut bound_elems = Vec::new();
return Ok(Address::Global(*idx)); for e in elements {
} bound_elems.push(self.bind(e)?);
}
// 4. Global Fallback Ok(self.make_node(
if sym.context.is_some() { node.identity.clone(),
let fallback_sym = Symbol { name: sym.name.clone(), context: None }; BoundKind::Tuple {
if let Some(idx) = globals.get(&fallback_sym) { elements: bound_elems,
return Ok(Address::Global(*idx)); },
} ))
} }
Err(format!("Undefined variable '{}'", sym.name)) UntypedKind::Record { fields } => {
} let mut bound_fields = Vec::new();
for (k, v) in fields {
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { bound_fields.push((self.bind(k)?, self.bind(v)?));
Node { identity, kind, ty: () } }
} Ok(self.make_node(
} node.identity.clone(),
BoundKind::Record {
#[cfg(test)] fields: bound_fields,
mod tests { },
use super::*; ))
use crate::ast::parser::Parser; }
#[test] UntypedKind::Expansion { call, expanded } => {
fn test_upvalue_capture_sets_is_boxed() { let bound_expanded = self.bind(expanded)?;
// Wrap in a lambda to ensure 'x' is a local variable, not a global Ok(self.make_node(
let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))"; node.identity.clone(),
let mut parser = Parser::new(source).unwrap(); BoundKind::Expansion {
let untyped = parser.parse_expression().unwrap(); original_call: Rc::from(call.as_ref().clone()),
bound_expanded: Box::new(bound_expanded),
let globals = Rc::new(RefCell::new(HashMap::new())); },
let bound = Binder::bind_root(globals, &untyped).unwrap(); ))
}
// Structure: Lambda -> Block -> [ DefLocal(x), DefLocal(f), Get(x) ]
if let BoundKind::Lambda { body, .. } = &bound.kind { UntypedKind::Template(_)
if let BoundKind::Block { exprs } = &body.kind { | UntypedKind::Placeholder(_)
let x_decl = &exprs[0]; | UntypedKind::Splice(_)
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind { | UntypedKind::MacroDecl { .. } => Err(format!(
assert!(!captured_by.is_empty(), "Variable 'x' should have capturers because it is used in lambda 'f'"); "Macro construct {:?} found in Binder. Macros must be expanded before binding.",
} else { node.kind
panic!("First expression in block should be DefLocal, got {:?}", x_decl.kind); )),
}
} else { UntypedKind::Extension(_) => {
panic!("Lambda body should be a Block, got {:?}", body.kind); // Future: Delegate to extension binder
} Err("Custom extensions not supported in Binder yet".to_string())
} else { }
panic!("Root should be a Lambda, got {:?}", bound.kind); }
} }
}
fn resolve_variable(&mut self, sym: &Symbol) -> Result<Address, String> {
#[test] let current_fn_idx = self.functions.len() - 1;
fn test_no_capture_not_boxed() {
let source = "(fn [] (do (def x 10) x))"; // 1. Try local in current function
let mut parser = Parser::new(source).unwrap(); if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) {
let untyped = parser.parse_expression().unwrap(); return Ok(Address::Local(info.slot));
}
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap(); // 2. Try enclosing scopes (capture chain)
for i in (0..current_fn_idx).rev() {
if let BoundKind::Lambda { body, .. } = &bound.kind { if let Some(info) = self.functions[i].scope.resolve(sym) {
if let BoundKind::Block { exprs } = &body.kind { let mut addr = Address::Local(info.slot);
let x_decl = &exprs[0];
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind { for k in (i + 1)..=current_fn_idx {
assert!(captured_by.is_empty(), "Variable 'x' should NOT have any capturers"); addr = Address::Upvalue(self.functions[k].add_upvalue(addr));
} else { }
panic!("First expression should be DefLocal"); return Ok(addr);
} }
} else { }
panic!("Lambda body should be a Block");
} // 3. Try Global
} else { let globals = self.globals.borrow();
panic!("Root should be a Lambda"); if let Some(idx) = globals.get(sym) {
} return Ok(Address::Global(*idx));
} }
#[test] // 4. Global Fallback
fn test_redefinition_error() { if sym.context.is_some() {
let source = "(do (def x 1) (def x 2))"; let fallback_sym = Symbol {
let mut parser = Parser::new(source).unwrap(); name: sym.name.clone(),
let untyped = parser.parse_expression().unwrap(); context: None,
};
let globals = Rc::new(RefCell::new(HashMap::new())); if let Some(idx) = globals.get(&fallback_sym) {
let result = Binder::bind_root(globals, &untyped); return Ok(Address::Global(*idx));
}
assert!(result.is_err()); }
assert!(result.unwrap_err().contains("already defined"));
} Err(format!("Undefined variable '{}'", sym.name))
}
#[test]
fn test_repro_global_redefinition() { fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
let globals = Rc::new(RefCell::new(HashMap::new())); Node {
identity,
// First run: defines 'x' kind,
let source1 = "(def x 1)"; ty: (),
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)"; #[cfg(test)]
let untyped2 = Parser::new(source2).unwrap().parse_expression().unwrap(); mod tests {
let result = Binder::bind_root(globals.clone(), &untyped2); use super::*;
use crate::ast::parser::Parser;
assert!(result.is_err());
assert!(result.unwrap_err().contains("already defined")); #[test]
} fn test_upvalue_capture_sets_is_boxed() {
} // Wrap in a lambda to ensure 'x' is a local variable, not a global
let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap();
// Structure: Lambda -> Block -> [ DefLocal(x), DefLocal(f), Get(x) ]
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0];
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind {
assert!(
!captured_by.is_empty(),
"Variable 'x' should have capturers because it is used in lambda 'f'"
);
} else {
panic!(
"First expression in block should be DefLocal, got {:?}",
x_decl.kind
);
}
} else {
panic!("Lambda body should be a Block, got {:?}", body.kind);
}
} else {
panic!("Root should be a Lambda, got {:?}", bound.kind);
}
}
#[test]
fn test_no_capture_not_boxed() {
let source = "(fn [] (do (def x 10) x))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0];
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind {
assert!(
captured_by.is_empty(),
"Variable 'x' should NOT have any capturers"
);
} else {
panic!("First expression should be DefLocal");
}
} else {
panic!("Lambda body should be a Block");
}
} else {
panic!("Root should be a Lambda");
}
}
#[test]
fn test_redefinition_error() {
let source = "(do (def x 1) (def x 2))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let result = Binder::bind_root(globals, &untyped);
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"));
}
}
+253 -194
View File
@@ -1,194 +1,253 @@
use std::rc::Rc; use crate::ast::nodes::{Node, Symbol};
use crate::ast::types::{Value, StaticType, Identity}; use crate::ast::types::{Identity, StaticType, Value};
use crate::ast::nodes::{Node, Symbol}; use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Address { pub enum Address {
Local(u32), // Stack-Slot index (relative to frame base) Local(u32), // Stack-Slot index (relative to frame base)
Upvalue(u32), // Index in the closure's upvalue array Upvalue(u32), // Index in the closure's upvalue array
Global(u32), // Index in the global environment vector Global(u32), // Index in the global environment vector
} }
/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.) /// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.)
pub trait BoundExtension<T>: std::fmt::Debug { pub trait BoundExtension<T>: std::fmt::Debug {
fn clone_box(&self) -> Box<dyn BoundExtension<T>>; fn clone_box(&self) -> Box<dyn BoundExtension<T>>;
fn display_name(&self) -> String; fn display_name(&self) -> String;
} }
impl<T> Clone for Box<dyn BoundExtension<T>> { impl<T> Clone for Box<dyn BoundExtension<T>> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.clone_box() self.clone_box()
} }
} }
/// Type alias for a node that has been bound. Defaults to untyped (T = ()). /// Type alias for a node that has been bound. Defaults to untyped (T = ()).
pub type BoundNode<T = ()> = Node<BoundKind<T>, T>; pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked. /// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>; pub type TypedNode = BoundNode<StaticType>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BoundKind<T = ()> { pub enum BoundKind<T = ()> {
Nop, Nop,
Constant(Value), Constant(Value),
/// A positional parameter in a Lambda's parameter list. /// A positional parameter in a Lambda's parameter list.
Parameter { Parameter {
name: Symbol, name: Symbol,
slot: u32, slot: u32,
}, },
// Variable Access (Resolved) // Variable Access (Resolved)
Get { Get {
addr: Address, addr: Address,
name: Symbol, name: Symbol,
}, },
// Variable Update (Assignment) // Variable Update (Assignment)
Set { Set {
addr: Address, addr: Address,
value: Box<BoundNode<T>>, value: Box<BoundNode<T>>,
}, },
// Variable Declaration (Local) // Variable Declaration (Local)
DefLocal { DefLocal {
name: Symbol, name: Symbol,
slot: u32, slot: u32,
value: Box<BoundNode<T>>, value: Box<BoundNode<T>>,
captured_by: Vec<Identity>, captured_by: Vec<Identity>,
}, },
If { If {
cond: Box<BoundNode<T>>, cond: Box<BoundNode<T>>,
then_br: Box<BoundNode<T>>, then_br: Box<BoundNode<T>>,
else_br: Option<Box<BoundNode<T>>>, else_br: Option<Box<BoundNode<T>>>,
}, },
// Global Definition (Locals are implicit by stack position) // Global Definition (Locals are implicit by stack position)
DefGlobal { DefGlobal {
name: Symbol, name: Symbol,
global_index: u32, global_index: u32,
value: Box<BoundNode<T>>, value: Box<BoundNode<T>>,
}, },
Lambda { Lambda {
params: Rc<BoundNode<T>>, params: Rc<BoundNode<T>>,
// The list of variables captured from enclosing scopes // The list of variables captured from enclosing scopes
upvalues: Vec<Address>, upvalues: Vec<Address>,
body: Rc<BoundNode<T>>, body: Rc<BoundNode<T>>,
/// Static optimization: number of positional parameters if the pattern is flat. /// Static optimization: number of positional parameters if the pattern is flat.
positional_count: Option<u32>, positional_count: Option<u32>,
}, },
Call { Call {
callee: Box<BoundNode<T>>, callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>, args: Box<BoundNode<T>>,
}, },
Block { Block {
exprs: Vec<BoundNode<T>>, exprs: Vec<BoundNode<T>>,
}, },
Tuple { Tuple {
elements: Vec<BoundNode<T>>, elements: Vec<BoundNode<T>>,
}, },
Record { Record {
fields: Vec<RecordField<T>>, fields: Vec<RecordField<T>>,
}, },
/// An expanded macro call, preserving the original call for debugging and UI. /// An expanded macro call, preserving the original call for debugging and UI.
Expansion { Expansion {
/// The original call from the untyped AST. /// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>, original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST. /// The result of binding the expanded AST.
bound_expanded: Box<BoundNode<T>>, bound_expanded: Box<BoundNode<T>>,
}, },
/// DSL-specific extension slot /// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>), Extension(Box<dyn BoundExtension<T>>),
} }
impl<T> PartialEq for BoundKind<T> impl<T> PartialEq for BoundKind<T>
where T: PartialEq { where
fn eq(&self, other: &Self) -> bool { T: PartialEq,
match (self, other) { {
(BoundKind::Nop, BoundKind::Nop) => true, fn eq(&self, other: &Self) -> bool {
(BoundKind::Constant(a), BoundKind::Constant(b)) => a == b, match (self, other) {
(BoundKind::Parameter { name: na, slot: sa }, BoundKind::Parameter { name: nb, slot: sb }) => { (BoundKind::Nop, BoundKind::Nop) => true,
na == nb && sa == sb (BoundKind::Constant(a), BoundKind::Constant(b)) => a == b,
} (
(BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => { BoundKind::Parameter { name: na, slot: sa },
aa == ab && na == nb BoundKind::Parameter { name: nb, slot: sb },
} ) => na == nb && sa == sb,
(BoundKind::Set { addr: aa, value: va }, BoundKind::Set { addr: ab, value: vb }) => { (BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => {
aa == ab && va == vb aa == ab && na == nb
} }
(BoundKind::DefLocal { name: na, slot: sa, value: va, captured_by: ca }, (
BoundKind::DefLocal { name: nb, slot: sb, value: vb, captured_by: cb }) => { BoundKind::Set {
na == nb && sa == sb && va == vb && ca == cb addr: aa,
} value: va,
(BoundKind::If { cond: ca, then_br: ta, else_br: ea }, },
BoundKind::If { cond: cb, then_br: tb, else_br: eb }) => { BoundKind::Set {
ca == cb && ta == tb && ea == eb addr: ab,
} value: vb,
(BoundKind::DefGlobal { name: na, global_index: ga, value: va }, },
BoundKind::DefGlobal { name: nb, global_index: gb, value: vb }) => { ) => aa == ab && va == vb,
na == nb && ga == gb && va == vb (
} BoundKind::DefLocal {
(BoundKind::Lambda { params: pa, upvalues: ua, body: ba, positional_count: pca }, name: na,
BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => { slot: sa,
pa == pb && ua == ub && ba == bb && pca == pcb value: va,
} captured_by: ca,
(BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => { },
ca == cb && aa == ab BoundKind::DefLocal {
} name: nb,
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { slot: sb,
ea == eb value: vb,
} captured_by: cb,
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => { },
ea == eb ) => na == nb && sa == sb && va == vb && ca == cb,
} (
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => { BoundKind::If {
fa == fb cond: ca,
} then_br: ta,
(BoundKind::Expansion { original_call: oa, bound_expanded: ba }, else_br: ea,
BoundKind::Expansion { original_call: ob, bound_expanded: bb }) => { },
Rc::ptr_eq(oa, ob) && ba == bb BoundKind::If {
} cond: cb,
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions then_br: tb,
_ => false, else_br: eb,
} },
} ) => ca == cb && ta == tb && ea == eb,
} (
BoundKind::DefGlobal {
/// A single field in a Record literal (Key-Value pair) name: na,
pub type RecordField<T> = (BoundNode<T>, BoundNode<T>); global_index: ga,
value: va,
impl<T> BoundKind<T> { },
pub fn display_name(&self) -> String { BoundKind::DefGlobal {
match self { name: nb,
BoundKind::Nop => "NOP".to_string(), global_index: gb,
BoundKind::Constant(v) => format!("CONST({})", v), value: vb,
BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot), },
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), ) => na == nb && ga == gb && va == vb,
BoundKind::Set { addr, .. } => format!("SET({:?})", addr), (
BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot), BoundKind::Lambda {
BoundKind::If { .. } => "IF".to_string(), params: pa,
BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), upvalues: ua,
BoundKind::Lambda { params, upvalues, .. } => { body: ba,
let p_str = match &params.kind { positional_count: pca,
BoundKind::Tuple { elements } => format!("p:{}", elements.len()), },
_ => "p:1".to_string(), BoundKind::Lambda {
}; params: pb,
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) upvalues: ub,
}, body: bb,
BoundKind::Call { .. } => "CALL".to_string(), positional_count: pcb,
BoundKind::Block { .. } => "BLOCK".to_string(), },
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), ) => pa == pb && ua == ub && ba == bb && pca == pcb,
BoundKind::Record { fields } => format!("RECORD({})", fields.len()), (
BoundKind::Expansion { .. } => "EXPANSION".to_string(), BoundKind::Call {
BoundKind::Extension(ext) => ext.display_name(), callee: ca,
} args: aa,
} },
} BoundKind::Call {
callee: cb,
args: ab,
},
) => ca == cb && aa == ab,
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => fa == fb,
(
BoundKind::Expansion {
original_call: oa,
bound_expanded: ba,
},
BoundKind::Expansion {
original_call: ob,
bound_expanded: bb,
},
) => Rc::ptr_eq(oa, ob) && ba == bb,
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
_ => false,
}
}
}
/// A single field in a Record literal (Key-Value pair)
pub type RecordField<T> = (BoundNode<T>, BoundNode<T>);
impl<T> BoundKind<T> {
pub fn display_name(&self) -> String {
match self {
BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v),
BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot),
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::DefLocal { name, slot, .. } => {
format!("DEF_LOCAL({}, Slot:{})", name.name, slot)
}
BoundKind::If { .. } => "IF".to_string(),
BoundKind::DefGlobal {
name, global_index, ..
} => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
BoundKind::Lambda {
params, upvalues, ..
} => {
let p_str = match &params.kind {
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
_ => "p:1".to_string(),
};
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
}
BoundKind::Call { .. } => "CALL".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(),
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
}
}
}
+236 -195
View File
@@ -1,195 +1,236 @@
use crate::ast::nodes::Node; use crate::ast::compiler::bound_nodes::BoundKind;
use crate::ast::compiler::bound_nodes::BoundKind; use crate::ast::nodes::Node;
use crate::ast::types::Value; use crate::ast::types::Value;
use crate::ast::vm::Closure; use crate::ast::vm::Closure;
use std::fmt::Debug; use std::fmt::Debug;
/// Human-readable AST dumper for the bound AST. /// Human-readable AST dumper for the bound AST.
pub struct Dumper { pub struct Dumper {
output: String, output: String,
indent: usize, indent: usize,
} }
impl Dumper { impl Dumper {
/// Produces a formatted string representation of the given bound AST node and its children. /// Produces a formatted string representation of the given bound AST node and its children.
pub fn dump<T: Debug>(node: &Node<BoundKind<T>, T>) -> String { pub fn dump<T: Debug>(node: &Node<BoundKind<T>, T>) -> String {
let mut dumper = Self { let mut dumper = Self {
output: String::new(), output: String::new(),
indent: 0, indent: 0,
}; };
dumper.visit(node); dumper.visit(node);
dumper.output dumper.output
} }
fn write_indent(&mut self) { fn write_indent(&mut self) {
for _ in 0..self.indent { for _ in 0..self.indent {
self.output.push_str(" "); self.output.push_str(" ");
} }
} }
fn log<T: Debug>(&mut self, label: &str, node: &Node<BoundKind<T>, T>) { fn log<T: Debug>(&mut self, label: &str, node: &Node<BoundKind<T>, T>) {
self.write_indent(); self.write_indent();
self.output.push_str(label); self.output.push_str(label);
self.output.push_str(&format!(" <Metadata: {:?}>\n", node.ty)); self.output
} .push_str(&format!(" <Metadata: {:?}>\n", node.ty));
}
fn visit<T: Debug>(&mut self, node: &Node<BoundKind<T>, T>) {
match &node.kind { fn visit<T: Debug>(&mut self, node: &Node<BoundKind<T>, T>) {
BoundKind::Nop => self.log("Nop", node), match &node.kind {
BoundKind::Constant(v) => { BoundKind::Nop => self.log("Nop", node),
self.log(&format!("Constant: {}", v), node); BoundKind::Constant(v) => {
// Introspect Closure AST if possible self.log(&format!("Constant: {}", v), node);
if let Value::Object(obj) = v // Introspect Closure AST if possible
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>() if let Value::Object(obj) = v
{ && let Some(closure) = obj.as_any().downcast_ref::<Closure>()
self.indent += 1; {
self.write_indent(); self.indent += 1;
self.output.push_str("--- Specialized Body ---\n"); self.write_indent();
// We need to cast the inner TypedNode to the generic T required by visit. self.output.push_str("--- Specialized Body ---\n");
// Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType), // We need to cast the inner TypedNode to the generic T required by visit.
// we can only fully dump if T is StaticType. // Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType),
// However, we can hack it by creating a new Dumper for the inner AST string. // we can only fully dump if T is StaticType.
// However, we can hack it by creating a new Dumper for the inner AST string.
// We can't call self.visit because types mismatch if T != StaticType.
// So we just recursively dump to string and append. // We can't call self.visit because types mismatch if T != StaticType.
let inner_dump = Dumper::dump(&closure.function_node); // So we just recursively dump to string and append.
for line in inner_dump.lines() { let inner_dump = Dumper::dump(&closure.function_node);
self.write_indent(); for line in inner_dump.lines() {
self.output.push_str(line); self.write_indent();
self.output.push('\n'); self.output.push_str(line);
} self.output.push('\n');
self.indent -= 1; }
} self.indent -= 1;
}, }
BoundKind::Get { addr, name } => self.log(&format!("Get: {} ({:?})", name.name, addr), node), }
BoundKind::Get { addr, name } => {
BoundKind::Set { addr, value } => { self.log(&format!("Get: {} ({:?})", name.name, addr), node)
self.log(&format!("Set: {:?}", addr), node); }
self.indent += 1;
self.visit(value); BoundKind::Set { addr, value } => {
self.indent -= 1; self.log(&format!("Set: {:?}", addr), node);
} self.indent += 1;
self.visit(value);
BoundKind::DefLocal { name, slot, value, captured_by } => { self.indent -= 1;
let capture_info = if captured_by.is_empty() { }
String::from("not captured")
} else { BoundKind::DefLocal {
format!("captured by {} lambdas", captured_by.len()) name,
}; slot,
self.log(&format!("DefLocal (Name: '{}', Slot: {}, {})", name.name, slot, capture_info), node); value,
captured_by,
self.indent += 1; } => {
if !captured_by.is_empty() { let capture_info = if captured_by.is_empty() {
for capturer in captured_by { String::from("not captured")
self.write_indent(); } else {
self.output.push_str(&format!("- Capturer: Lambda at line {}, col {}\n", format!("captured by {} lambdas", captured_by.len())
capturer.location.line, capturer.location.col)); };
} self.log(
} &format!(
self.visit(value); "DefLocal (Name: '{}', Slot: {}, {})",
self.indent -= 1; name.name, slot, capture_info
} ),
node,
BoundKind::DefGlobal { name, global_index, value } => { );
self.log(&format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index), node);
self.indent += 1; self.indent += 1;
self.visit(value); if !captured_by.is_empty() {
self.indent -= 1; for capturer in captured_by {
} self.write_indent();
self.output.push_str(&format!(
BoundKind::If { cond, then_br, else_br } => { "- Capturer: Lambda at line {}, col {}\n",
self.log("If", node); capturer.location.line, capturer.location.col
self.indent += 1; ));
}
self.write_indent(); }
self.output.push_str("Condition:\n"); self.visit(value);
self.visit(cond); self.indent -= 1;
}
self.write_indent();
self.output.push_str("Then:\n"); BoundKind::DefGlobal {
self.visit(then_br); name,
global_index,
if let Some(e) = else_br { value,
self.write_indent(); } => {
self.output.push_str("Else:\n"); self.log(
self.visit(e); &format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index),
} node,
self.indent -= 1; );
} self.indent += 1;
self.visit(value);
BoundKind::Lambda { params, upvalues, body, .. } => { self.indent -= 1;
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node); }
self.indent += 1;
BoundKind::If {
self.write_indent(); cond,
self.output.push_str("Parameters:\n"); then_br,
self.visit(params); else_br,
} => {
if !upvalues.is_empty() { self.log("If", node);
self.write_indent(); self.indent += 1;
self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
} self.write_indent();
self.visit(body); self.output.push_str("Condition:\n");
self.indent -= 1; self.visit(cond);
}
self.write_indent();
BoundKind::Parameter { name, slot } => { self.output.push_str("Then:\n");
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node); self.visit(then_br);
}
if let Some(e) = else_br {
BoundKind::Call { callee, args } => { self.write_indent();
self.log("Call", node); self.output.push_str("Else:\n");
self.indent += 1; self.visit(e);
}
self.write_indent(); self.indent -= 1;
self.output.push_str("Callee:\n"); }
self.visit(callee);
BoundKind::Lambda {
self.write_indent(); params,
self.output.push_str("Arguments:\n"); upvalues,
self.visit(args); body,
..
self.indent -= 1; } => {
} self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1;
BoundKind::Block { exprs } => {
self.log("Block", node); self.write_indent();
self.indent += 1; self.output.push_str("Parameters:\n");
for expr in exprs { self.visit(params);
self.visit(expr);
} if !upvalues.is_empty() {
self.indent -= 1; self.write_indent();
} self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
}
BoundKind::Tuple { elements } => { self.visit(body);
self.log("Tuple", node); self.indent -= 1;
self.indent += 1; }
for el in elements {
self.visit(el); BoundKind::Parameter { name, slot } => {
} self.log(
self.indent -= 1; &format!("Parameter (Name: '{}', Slot: {})", name.name, slot),
} node,
);
BoundKind::Record { fields } => { }
self.log("Record", node);
self.indent += 1; BoundKind::Call { callee, args } => {
for (k, v) in fields { self.log("Call", node);
self.visit(k); self.indent += 1;
self.visit(v);
} self.write_indent();
self.indent -= 1; self.output.push_str("Callee:\n");
} self.visit(callee);
BoundKind::Expansion { original_call, bound_expanded } => { self.write_indent();
self.log(&format!("Expansion (Original: {:?})", original_call.kind), node); self.output.push_str("Arguments:\n");
self.indent += 1; self.visit(args);
self.visit(bound_expanded);
self.indent -= 1; self.indent -= 1;
} }
BoundKind::Extension(ext) => { BoundKind::Block { exprs } => {
self.log(&ext.display_name(), node); self.log("Block", node);
} self.indent += 1;
} for expr in exprs {
} self.visit(expr);
} }
self.indent -= 1;
}
BoundKind::Tuple { elements } => {
self.log("Tuple", node);
self.indent += 1;
for el in elements {
self.visit(el);
}
self.indent -= 1;
}
BoundKind::Record { fields } => {
self.log("Record", node);
self.indent += 1;
for (k, v) in fields {
self.visit(k);
self.visit(v);
}
self.indent -= 1;
}
BoundKind::Expansion {
original_call,
bound_expanded,
} => {
self.log(
&format!("Expansion (Original: {:?})", original_call.kind),
node,
);
self.indent += 1;
self.visit(bound_expanded);
self.indent -= 1;
}
BoundKind::Extension(ext) => {
self.log(&ext.display_name(), node);
}
}
}
}
+95 -85
View File
@@ -1,85 +1,95 @@
use std::collections::HashMap; use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode};
use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode}; use std::collections::HashMap;
/// A pass that collects all global function definitions (lambdas) into a registry. /// A pass that collects all global function definitions (lambdas) into a registry.
/// This allows the Specializer to retrieve the original AST of a function for monomorphization. /// This allows the Specializer to retrieve the original AST of a function for monomorphization.
pub struct LambdaCollector<'a, T> { pub struct LambdaCollector<'a, T> {
registry: &'a mut HashMap<u32, BoundNode<T>>, registry: &'a mut HashMap<u32, BoundNode<T>>,
} }
impl<'a, T: Clone> LambdaCollector<'a, T> { impl<'a, T: Clone> LambdaCollector<'a, T> {
/// Performs a full traversal of the AST and populates the provided registry. /// Performs a full traversal of the AST and populates the provided registry.
pub fn collect(node: &BoundNode<T>, registry: &'a mut HashMap<u32, BoundNode<T>>) { pub fn collect(node: &BoundNode<T>, registry: &'a mut HashMap<u32, BoundNode<T>>) {
let mut collector = Self { registry }; let mut collector = Self { registry };
collector.visit(node); collector.visit(node);
} }
fn visit(&mut self, node: &BoundNode<T>) { fn visit(&mut self, node: &BoundNode<T>) {
match &node.kind { match &node.kind {
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
for expr in exprs { for expr in exprs {
self.visit(expr); self.visit(expr);
} }
} }
BoundKind::DefGlobal { global_index, value, .. } => { BoundKind::DefGlobal {
// Register the full Lambda node as the template. global_index,
if let BoundKind::Lambda { .. } = &value.kind { value,
self.registry.insert(*global_index, (*value).as_ref().clone()); ..
} } => {
self.visit(value); // Register the full Lambda node as the template.
} if let BoundKind::Lambda { .. } = &value.kind {
self.registry
BoundKind::Set { addr, value } => { .insert(*global_index, (*value).as_ref().clone());
// Also track assignments to globals if they hold lambdas. }
if let Address::Global(global_index) = addr self.visit(value);
&& let BoundKind::Lambda { .. } = &value.kind }
{
self.registry.insert(*global_index, (*value).as_ref().clone()); BoundKind::Set { addr, value } => {
} // Also track assignments to globals if they hold lambdas.
self.visit(value); if let Address::Global(global_index) = addr
} && let BoundKind::Lambda { .. } = &value.kind
{
BoundKind::If { cond, then_br, else_br } => { self.registry
self.visit(cond); .insert(*global_index, (*value).as_ref().clone());
self.visit(then_br); }
if let Some(e) = else_br { self.visit(value);
self.visit(e); }
}
} BoundKind::If {
cond,
BoundKind::Lambda { params, body, .. } => { then_br,
self.visit(params); else_br,
self.visit(body); } => {
} self.visit(cond);
self.visit(then_br);
BoundKind::DefLocal { value, .. } => { if let Some(e) = else_br {
self.visit(value); self.visit(e);
} }
}
BoundKind::Call { callee, args } => {
self.visit(callee); BoundKind::Lambda { params, body, .. } => {
self.visit(args); self.visit(params);
} self.visit(body);
}
BoundKind::Tuple { elements } => {
for el in elements { BoundKind::DefLocal { value, .. } => {
self.visit(el); self.visit(value);
} }
}
BoundKind::Call { callee, args } => {
BoundKind::Record { fields } => { self.visit(callee);
for (k, v) in fields { self.visit(args);
self.visit(k); }
self.visit(v);
} BoundKind::Tuple { elements } => {
} for el in elements {
self.visit(el);
BoundKind::Expansion { bound_expanded, .. } => { }
self.visit(bound_expanded); }
}
BoundKind::Record { fields } => {
_ => {} // Leaf nodes for (k, v) in fields {
} self.visit(k);
} self.visit(v);
} }
}
BoundKind::Expansion { bound_expanded, .. } => {
self.visit(bound_expanded);
}
_ => {} // Leaf nodes
}
}
}
+765 -609
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -1,20 +1,20 @@
pub mod binder; pub mod binder;
pub mod bound_nodes; pub mod bound_nodes;
pub mod tco; pub mod dumper;
pub mod upvalues; pub mod lambda_collector;
pub mod dumper; pub mod macros;
pub mod macros; pub mod optimizer;
pub mod type_checker; pub mod specializer;
pub mod specializer; pub mod tco;
pub mod optimizer; pub mod type_checker;
pub mod lambda_collector; pub mod upvalues;
pub use binder::*; pub use binder::*;
pub use bound_nodes::*; pub use bound_nodes::*;
pub use tco::*; pub use dumper::*;
pub use upvalues::*; pub use macros::*;
pub use dumper::*; pub use optimizer::*;
pub use macros::*; pub use specializer::*;
pub use type_checker::*; pub use tco::*;
pub use specializer::*; pub use type_checker::*;
pub use optimizer::*; pub use upvalues::*;
File diff suppressed because it is too large Load Diff
+313 -232
View File
@@ -1,232 +1,313 @@
use std::collections::HashMap; use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode};
use std::rc::Rc; use crate::ast::nodes::Node;
use std::cell::RefCell; use crate::ast::types::{Signature, StaticType, Value};
use crate::ast::types::{StaticType, Value, Signature}; use std::cell::RefCell;
use crate::ast::compiler::bound_nodes::{BoundKind, Address, TypedNode, BoundNode}; use std::collections::HashMap;
use crate::ast::nodes::Node; use std::rc::Rc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MonoCacheKey { pub struct MonoCacheKey {
pub address: Address, pub address: Address,
pub arg_types: Vec<StaticType>, pub arg_types: Vec<StaticType>,
} }
pub type CompileFunc = Rc<dyn Fn(BoundNode, &[StaticType]) -> Result<(Value, StaticType), String>>; pub type CompileFunc = Rc<dyn Fn(BoundNode, &[StaticType]) -> Result<(Value, StaticType), String>>;
pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, StaticType)>>; pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, StaticType)>>;
pub trait FunctionRegistry { pub trait FunctionRegistry {
fn resolve(&self, addr: Address) -> Option<BoundNode>; fn resolve(&self, addr: Address) -> Option<BoundNode>;
} }
pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>; pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>;
pub struct Specializer { pub struct Specializer {
pub cache: Rc<RefCell<MonoCache>>, pub cache: Rc<RefCell<MonoCache>>,
registry: Option<Rc<dyn FunctionRegistry>>, registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>, compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>, rtl_lookup: Option<RtlLookupFunc>,
} }
impl Specializer { impl Specializer {
pub fn new( pub fn new(
registry: Option<Rc<dyn FunctionRegistry>>, registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>, compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>, rtl_lookup: Option<RtlLookupFunc>,
cache: Option<Rc<RefCell<MonoCache>>>, cache: Option<Rc<RefCell<MonoCache>>>,
) -> Self { ) -> Self {
Self { Self {
cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))), cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))),
registry, registry,
compiler, compiler,
rtl_lookup, rtl_lookup,
} }
} }
pub fn specialize(&self, node: TypedNode) -> TypedNode { pub fn specialize(&self, node: TypedNode) -> TypedNode {
self.visit_node(node) self.visit_node(node)
} }
fn visit_node(&self, node: TypedNode) -> TypedNode { fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind { let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone()); let (new_callee, new_args, ret_ty) =
(BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty) self.specialize_call_logic(*callee, *args, node.ty.clone());
}, (
BoundKind::Call {
// Recursive traversal for other nodes callee: Box::new(new_callee),
BoundKind::If { cond, then_br, else_br } => { args: Box::new(new_args),
let cond = Box::new(self.visit_node(*cond)); },
let then_br = Box::new(self.visit_node(*then_br)); ret_ty,
let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); )
(BoundKind::If { cond, then_br, else_br }, node.ty) }
},
BoundKind::Block { exprs } => { // Recursive traversal for other nodes
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); BoundKind::If {
(BoundKind::Block { exprs }, node.ty) cond,
}, then_br,
BoundKind::Lambda { params, upvalues, body, positional_count } => { else_br,
let params = Rc::new(self.visit_node(params.as_ref().clone())); } => {
let body = Rc::new(self.visit_node((*body).clone())); let cond = Box::new(self.visit_node(*cond));
(BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty) let then_br = Box::new(self.visit_node(*then_br));
}, let else_br = else_br.map(|e| Box::new(self.visit_node(*e)));
BoundKind::DefLocal { name, slot, value, captured_by } => { (
let value = Box::new(self.visit_node(*value)); BoundKind::If {
(BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) cond,
}, then_br,
BoundKind::DefGlobal { name, global_index, value } => { else_br,
let value = Box::new(self.visit_node(*value)); },
(BoundKind::DefGlobal { name, global_index, value }, node.ty) node.ty,
}, )
BoundKind::Set { addr, value } => { }
let value = Box::new(self.visit_node(*value)); BoundKind::Block { exprs } => {
(BoundKind::Set { addr, value }, node.ty) let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
}, (BoundKind::Block { exprs }, node.ty)
BoundKind::Tuple { elements } => { }
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect(); BoundKind::Lambda {
(BoundKind::Tuple { elements }, node.ty) params,
}, upvalues,
BoundKind::Record { fields } => { body,
let fields = fields.into_iter().map(|(k, v)| (self.visit_node(k), self.visit_node(v))).collect(); positional_count,
(BoundKind::Record { fields }, node.ty) } => {
}, let params = Rc::new(self.visit_node(params.as_ref().clone()));
BoundKind::Expansion { original_call, bound_expanded } => { let body = Rc::new(self.visit_node((*body).clone()));
let bound_expanded = Box::new(self.visit_node(*bound_expanded)); (
(BoundKind::Expansion { original_call, bound_expanded }, node.ty) BoundKind::Lambda {
}, params,
upvalues,
// Leaf nodes or uninteresting nodes body,
k => (k, node.ty), positional_count,
}; },
node.ty,
Node { )
identity: node.identity, }
kind: new_kind, BoundKind::DefLocal {
ty: new_ty, name,
} slot,
} value,
captured_by,
fn specialize_call_logic(&self, callee: TypedNode, args: TypedNode, original_ty: StaticType) -> (TypedNode, TypedNode, StaticType) { } => {
// 1. Specialize children first let value = Box::new(self.visit_node(*value));
let new_callee = self.visit_node(callee); (
let new_args = self.visit_node(args); BoundKind::DefLocal {
name,
// 2. Check if this call is a candidate (Callee is Get(Address)) slot,
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { value,
*addr captured_by,
} else { },
// Not a direct call to a named function/variable node.ty,
return (new_callee, new_args, original_ty); )
}; }
BoundKind::DefGlobal {
// 3. Check if all argument types are statically known name,
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty { global_index,
elements.clone() value,
} else { } => {
vec![new_args.ty.clone()] let value = Box::new(self.visit_node(*value));
}; (
BoundKind::DefGlobal {
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { name,
// Cannot specialize with unknown types global_index,
return (new_callee, new_args, original_ty); value,
} },
node.ty,
// --- Optimization Candidate --- )
let key = MonoCacheKey { address, arg_types: arg_types.clone() }; }
BoundKind::Set { addr, value } => {
// 4. Check Cache let value = Box::new(self.visit_node(*value));
if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { (BoundKind::Set { addr, value }, node.ty)
// Cache Hit! Replace Callee with Constant(Function) }
let specialized_callee = Node { BoundKind::Tuple { elements } => {
identity: new_callee.identity.clone(), let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
kind: BoundKind::Constant(val.clone()), (BoundKind::Tuple { elements }, node.ty)
ty: StaticType::Function(Box::new(Signature { }
params: StaticType::Tuple(arg_types), BoundKind::Record { fields } => {
ret: ret_ty.clone(), let fields = fields
})), .into_iter()
}; .map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
return (specialized_callee, new_args, ret_ty.clone()); .collect();
} (BoundKind::Record { fields }, node.ty)
}
// 5. Check RTL (Host Functions) BoundKind::Expansion {
if let Some(rtl_lookup) = &self.rtl_lookup original_call,
&& let BoundKind::Get { name, .. } = &new_callee.kind bound_expanded,
&& let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types) } => {
{ let bound_expanded = Box::new(self.visit_node(*bound_expanded));
// Cache Hit (RTL) (
self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone())); BoundKind::Expansion {
original_call,
let specialized_callee = Node { bound_expanded,
identity: new_callee.identity.clone(), },
kind: BoundKind::Constant(val.clone()), node.ty,
ty: StaticType::Function(Box::new(Signature { )
params: StaticType::Tuple(arg_types), }
ret: ret_ty.clone(),
})), // Leaf nodes or uninteresting nodes
}; k => (k, node.ty),
return (specialized_callee, new_args, ret_ty); };
}
Node {
// 6. Resolve Function Definition identity: node.identity,
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) { kind: new_kind,
// Check constraints (no closures with state) ty: new_ty,
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind { }
if !upvalues.is_empty() { }
return (new_callee, new_args, original_ty);
} fn specialize_call_logic(
} else { &self,
return (new_callee, new_args, original_ty); callee: TypedNode,
} args: TypedNode,
original_ty: StaticType,
// 7. Compile Specialization (User Code) ) -> (TypedNode, TypedNode, StaticType) {
if let Some(compiler) = &self.compiler { // 1. Specialize children first
match compiler(func_node, &arg_types) { let new_callee = self.visit_node(callee);
Ok((compiled_val, ret_ty)) => { let new_args = self.visit_node(args);
let res_val: Value = compiled_val;
let res_ty: StaticType = ret_ty; // 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind {
// Store in cache *addr
self.cache.borrow_mut().insert(key, (res_val.clone(), res_ty.clone())); } else {
// Not a direct call to a named function/variable
// PERFORMANCE: Flatten the argument tuple to match the specialized signature. return (new_callee, new_args, original_ty);
let flat_elements = self.flatten_tuple(new_args.clone()); };
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect();
let flattened_args = Node { // 3. Check if all argument types are statically known
identity: new_args.identity.clone(), let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty {
kind: BoundKind::Tuple { elements: flat_elements }, elements.clone()
ty: StaticType::Tuple(flat_types), } else {
}; vec![new_args.ty.clone()]
};
let specialized_callee = Node {
identity: new_callee.identity.clone(), if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
kind: BoundKind::Constant(res_val), // Cannot specialize with unknown types
ty: StaticType::Function(Box::new(Signature { return (new_callee, new_args, original_ty);
params: flattened_args.ty.clone(), }
ret: res_ty.clone(),
})), // --- Optimization Candidate ---
}; let key = MonoCacheKey {
return (specialized_callee, flattened_args, res_ty); address,
}, arg_types: arg_types.clone(),
Err(_) => { };
// Fallback on error
} // 4. Check Cache
} if let Some((val, ret_ty)) = self.cache.borrow().get(&key) {
} // Cache Hit! Replace Callee with Constant(Function)
} let specialized_callee = Node {
identity: new_callee.identity.clone(),
// Fallback: Dynamic Call kind: BoundKind::Constant(val.clone()),
(new_callee, new_args, original_ty) ty: StaticType::Function(Box::new(Signature {
} params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> { })),
match node.kind { };
BoundKind::Tuple { elements } => { return (specialized_callee, new_args, ret_ty.clone());
let mut flat = Vec::new(); }
for el in elements {
flat.extend(self.flatten_tuple(el)); // 5. Check RTL (Host Functions)
} if let Some(rtl_lookup) = &self.rtl_lookup
flat && let BoundKind::Get { name, .. } = &new_callee.kind
} && let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types)
_ => vec![node], {
} // Cache Hit (RTL)
} self.cache
} .borrow_mut()
.insert(key.clone(), (val.clone(), ret_ty.clone()));
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty);
}
// 6. Resolve Function Definition
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) {
// Check constraints (no closures with state)
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind {
if !upvalues.is_empty() {
return (new_callee, new_args, original_ty);
}
} else {
return (new_callee, new_args, original_ty);
}
// 7. Compile Specialization (User Code)
if let Some(compiler) = &self.compiler {
match compiler(func_node, &arg_types) {
Ok((compiled_val, ret_ty)) => {
let res_val: Value = compiled_val;
let res_ty: StaticType = ret_ty;
// Store in cache
self.cache
.borrow_mut()
.insert(key, (res_val.clone(), res_ty.clone()));
// PERFORMANCE: Flatten the argument tuple to match the specialized signature.
let flat_elements = self.flatten_tuple(new_args.clone());
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect();
let flattened_args = Node {
identity: new_args.identity.clone(),
kind: BoundKind::Tuple {
elements: flat_elements,
},
ty: StaticType::Tuple(flat_types),
};
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: flattened_args.ty.clone(),
ret: res_ty.clone(),
})),
};
return (specialized_callee, flattened_args, res_ty);
}
Err(_) => {
// Fallback on error
}
}
}
}
// Fallback: Dynamic Call
(new_callee, new_args, original_ty)
}
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> {
match node.kind {
BoundKind::Tuple { elements } => {
let mut flat = Vec::new();
for el in elements {
flat.extend(self.flatten_tuple(el));
}
flat
}
_ => vec![node],
}
}
}
+167 -131
View File
@@ -1,131 +1,167 @@
use std::rc::Rc; use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; use crate::ast::types::StaticType;
use crate::ast::types::StaticType; use std::rc::Rc;
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Clone)] #[derive(Clone)]
pub struct RuntimeMetadata { pub struct RuntimeMetadata {
pub ty: StaticType, pub ty: StaticType,
pub is_tail: bool, pub is_tail: bool,
/// The original, high-level typed node. Perfect for Debuggers and Optimizers. /// The original, high-level typed node. Perfect for Debuggers and Optimizers.
pub original: Rc<TypedNode>, pub original: Rc<TypedNode>,
} }
impl Debug for RuntimeMetadata { impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata") f.debug_struct("Metadata")
.field("ty", &self.ty) .field("ty", &self.ty)
.field("is_tail", &self.is_tail) .field("is_tail", &self.is_tail)
.finish() .finish()
} }
} }
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. /// The ExecNode is the AST used by the VM. It carries TCO flags and links to source.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>; pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
pub struct TCO; pub struct TCO;
impl TCO { impl TCO {
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. /// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
pub fn optimize(node: TypedNode) -> ExecNode { pub fn optimize(node: TypedNode) -> ExecNode {
Self::transform(Rc::new(node), true) Self::transform(Rc::new(node), true)
} }
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode { fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc; let node = &*node_rc;
let new_kind = match &node.kind { let new_kind = match &node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => BoundKind::Call {
BoundKind::Call { callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)),
callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)), args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
args: Box::new(Self::transform(Rc::new((**args).clone()), false)), },
}
}, BoundKind::If {
cond,
BoundKind::If { cond, then_br, else_br } => { then_br,
BoundKind::If { else_br,
cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)), } => BoundKind::If {
then_br: Box::new(Self::transform(Rc::new((**then_br).clone()), is_tail_position)), cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)),
else_br: else_br.as_ref().map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))), then_br: Box::new(Self::transform(
} Rc::new((**then_br).clone()),
}, is_tail_position,
)),
BoundKind::Block { exprs } => { else_br: else_br
if exprs.is_empty() { .as_ref()
BoundKind::Block { exprs: vec![] } .map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))),
} else { },
let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len()); BoundKind::Block { exprs } => {
if exprs.is_empty() {
for (i, expr) in exprs.iter().enumerate() { BoundKind::Block { exprs: vec![] }
let is_last = i == last_idx; } else {
new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last)); let last_idx = exprs.len() - 1;
} let mut new_exprs = Vec::with_capacity(exprs.len());
BoundKind::Block { exprs: new_exprs }
} for (i, expr) in exprs.iter().enumerate() {
}, let is_last = i == last_idx;
new_exprs.push(Self::transform(
BoundKind::Lambda { params, upvalues, body, positional_count } => { Rc::new(expr.clone()),
BoundKind::Lambda { is_tail_position && is_last,
params: Rc::new(Self::transform(params.clone(), false)), ));
upvalues: upvalues.clone(), }
body: Rc::new(Self::transform(body.clone(), true)), BoundKind::Block { exprs: new_exprs }
positional_count: *positional_count, }
} }
},
BoundKind::Lambda {
BoundKind::Set { addr, value } => { params,
BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) } upvalues,
}, body,
BoundKind::DefLocal { name, slot, value, captured_by } => { positional_count,
BoundKind::DefLocal { } => BoundKind::Lambda {
name: name.clone(), params: Rc::new(Self::transform(params.clone(), false)),
slot: *slot, upvalues: upvalues.clone(),
value: Box::new(Self::transform(Rc::new((**value).clone()), false)), body: Rc::new(Self::transform(body.clone(), true)),
captured_by: captured_by.clone() positional_count: *positional_count,
} },
},
BoundKind::DefGlobal { name, global_index, value } => { BoundKind::Set { addr, value } => BoundKind::Set {
BoundKind::DefGlobal { addr: *addr,
name: name.clone(), value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
global_index: *global_index, },
value: Box::new(Self::transform(Rc::new((**value).clone()), false)) BoundKind::DefLocal {
} name,
}, slot,
BoundKind::Record { fields } => { value,
let new_fields = fields.iter().map(|(k, v)| { captured_by,
(Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false)) } => BoundKind::DefLocal {
}).collect(); name: name.clone(),
BoundKind::Record { fields: new_fields } slot: *slot,
}, value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
BoundKind::Tuple { elements } => { captured_by: captured_by.clone(),
let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect(); },
BoundKind::Tuple { elements: new_elements } BoundKind::DefGlobal {
}, name,
BoundKind::Parameter { name, slot } => BoundKind::Parameter { name: name.clone(), slot: *slot }, global_index,
BoundKind::Constant(v) => BoundKind::Constant(v.clone()), value,
BoundKind::Get { addr, name } => BoundKind::Get { addr: *addr, name: name.clone() }, } => BoundKind::DefGlobal {
BoundKind::Nop => BoundKind::Nop, name: name.clone(),
BoundKind::Expansion { original_call, bound_expanded } => { global_index: *global_index,
BoundKind::Expansion { value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
original_call: original_call.clone(), },
bound_expanded: Box::new(Self::transform(Rc::new((**bound_expanded).clone()), is_tail_position)) BoundKind::Record { fields } => {
} let new_fields = fields
} .iter()
BoundKind::Extension(_) => { .map(|(k, v)| {
BoundKind::Nop (
} Self::transform(Rc::new(k.clone()), false),
}; Self::transform(Rc::new(v.clone()), false),
)
Node { })
identity: node.identity.clone(), .collect();
kind: new_kind, BoundKind::Record { fields: new_fields }
ty: RuntimeMetadata { }
ty: node.ty.clone(), BoundKind::Tuple { elements } => {
is_tail: is_tail_position, let new_elements = elements
original: node_rc, .iter()
}, .map(|e| Self::transform(Rc::new(e.clone()), false))
} .collect();
} BoundKind::Tuple {
} elements: new_elements,
}
}
BoundKind::Parameter { name, slot } => BoundKind::Parameter {
name: name.clone(),
slot: *slot,
},
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get {
addr: *addr,
name: name.clone(),
},
BoundKind::Nop => BoundKind::Nop,
BoundKind::Expansion {
original_call,
bound_expanded,
} => BoundKind::Expansion {
original_call: original_call.clone(),
bound_expanded: Box::new(Self::transform(
Rc::new((**bound_expanded).clone()),
is_tail_position,
)),
},
BoundKind::Extension(_) => BoundKind::Nop,
};
Node {
identity: node.identity.clone(),
kind: new_kind,
ty: RuntimeMetadata {
ty: node.ty.clone(),
is_tail: is_tail_position,
original: node_rc,
},
}
}
}
File diff suppressed because it is too large Load Diff
+132 -121
View File
@@ -1,121 +1,132 @@
use std::collections::{HashMap, HashSet}; use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::types::Identity;
use crate::ast::types::Identity; use std::collections::{HashMap, HashSet};
/// Analyzes the AST to find which lambdas capture which variable declarations. /// Analyzes the AST to find which lambdas capture which variable declarations.
/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it. /// Returns a map: Declaration Identity -> List of Lambda Identities that capture it.
pub struct UpvalueAnalyzer; pub struct UpvalueAnalyzer;
impl UpvalueAnalyzer { impl UpvalueAnalyzer {
pub fn analyze(root: &Node<UntypedKind>) -> HashMap<Identity, Vec<Identity>> { pub fn analyze(root: &Node<UntypedKind>) -> HashMap<Identity, Vec<Identity>> {
let mut capture_map: HashMap<Identity, HashSet<Identity>> = HashMap::new(); let mut capture_map: HashMap<Identity, HashSet<Identity>> = HashMap::new();
let mut scopes = vec![HashMap::new()]; // Root scope let mut scopes = vec![HashMap::new()]; // Root scope
Self::visit(root, &mut scopes, &mut capture_map, None); Self::visit(root, &mut scopes, &mut capture_map, None);
// Convert HashSet back to sorted Vec for the final result // Convert HashSet back to sorted Vec for the final result
capture_map.into_iter() capture_map
.map(|(decl, lambdas)| (decl, lambdas.into_iter().collect())) .into_iter()
.collect() .map(|(decl, lambdas)| (decl, lambdas.into_iter().collect()))
} .collect()
}
fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
match &node.kind { fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
UntypedKind::Parameter(sym) => { match &node.kind {
scope.insert(sym.clone(), node.identity.clone()); UntypedKind::Parameter(sym) => {
} scope.insert(sym.clone(), node.identity.clone());
UntypedKind::Tuple { elements } => { }
for el in elements { UntypedKind::Tuple { elements } => {
Self::visit_params(el, scope); for el in elements {
} Self::visit_params(el, scope);
} }
_ => {} // Ignore other nodes in parameter patterns for now }
} _ => {} // Ignore other nodes in parameter patterns for now
} }
}
fn visit(
node: &Node<UntypedKind>, fn visit(
scopes: &mut Vec<HashMap<Symbol, Identity>>, node: &Node<UntypedKind>,
capture_map: &mut HashMap<Identity, HashSet<Identity>>, scopes: &mut Vec<HashMap<Symbol, Identity>>,
current_lambda: Option<Identity> capture_map: &mut HashMap<Identity, HashSet<Identity>>,
) { current_lambda: Option<Identity>,
match &node.kind { ) {
UntypedKind::Identifier(sym) => { match &node.kind {
// Resolve name in scope stack (from inner to outer) UntypedKind::Identifier(sym) => {
for (depth, scope) in scopes.iter().rev().enumerate() { // Resolve name in scope stack (from inner to outer)
if let Some(decl_id) = scope.get(sym) { for (depth, scope) in scopes.iter().rev().enumerate() {
if depth > 0 { if let Some(decl_id) = scope.get(sym) {
// Captured from an outer scope! if depth > 0 {
if let Some(lambda_id) = &current_lambda { // Captured from an outer scope!
capture_map.entry(decl_id.clone()) if let Some(lambda_id) = &current_lambda {
.or_default() capture_map
.insert(lambda_id.clone()); .entry(decl_id.clone())
} .or_default()
} .insert(lambda_id.clone());
break; }
} }
} break;
} }
UntypedKind::Def { name, value } => { }
Self::visit(value, scopes, capture_map, current_lambda.clone()); }
if let Some(current) = scopes.last_mut() { UntypedKind::Def { name, value } => {
current.insert(name.clone(), node.identity.clone()); Self::visit(value, scopes, capture_map, current_lambda.clone());
} if let Some(current) = scopes.last_mut() {
} current.insert(name.clone(), node.identity.clone());
UntypedKind::Lambda { params, body } => { }
let mut new_scope = HashMap::new(); }
Self::visit_params(params, &mut new_scope); UntypedKind::Lambda { params, body } => {
let mut new_scope = HashMap::new();
scopes.push(new_scope); Self::visit_params(params, &mut new_scope);
// The current node is the lambda causing captures in its body
Self::visit(body, scopes, capture_map, Some(node.identity.clone())); scopes.push(new_scope);
scopes.pop(); // The current node is the lambda causing captures in its body
} Self::visit(body, scopes, capture_map, Some(node.identity.clone()));
UntypedKind::MacroDecl { params, body, .. } => { scopes.pop();
let mut new_scope = HashMap::new(); }
Self::visit_params(params, &mut new_scope); UntypedKind::MacroDecl { params, body, .. } => {
scopes.push(new_scope); let mut new_scope = HashMap::new();
Self::visit(body, scopes, capture_map, current_lambda); Self::visit_params(params, &mut new_scope);
scopes.pop(); scopes.push(new_scope);
} Self::visit(body, scopes, capture_map, current_lambda);
UntypedKind::If { cond, then_br, else_br } => { scopes.pop();
Self::visit(cond, scopes, capture_map, current_lambda.clone()); }
Self::visit(then_br, scopes, capture_map, current_lambda.clone()); UntypedKind::If {
if let Some(e) = else_br { cond,
Self::visit(e, scopes, capture_map, current_lambda.clone()); then_br,
} else_br,
} } => {
UntypedKind::Assign { target, value } => { Self::visit(cond, scopes, capture_map, current_lambda.clone());
Self::visit(target, scopes, capture_map, current_lambda.clone()); Self::visit(then_br, scopes, capture_map, current_lambda.clone());
Self::visit(value, scopes, capture_map, current_lambda.clone()); if let Some(e) = else_br {
} Self::visit(e, scopes, capture_map, current_lambda.clone());
UntypedKind::Call { callee, args } => { }
Self::visit(callee, scopes, capture_map, current_lambda.clone()); }
Self::visit(args, scopes, capture_map, current_lambda.clone()); UntypedKind::Assign { target, value } => {
} Self::visit(target, scopes, capture_map, current_lambda.clone());
UntypedKind::Block { exprs } => { Self::visit(value, scopes, capture_map, current_lambda.clone());
for expr in exprs { }
Self::visit(expr, scopes, capture_map, current_lambda.clone()); UntypedKind::Call { callee, args } => {
} Self::visit(callee, scopes, capture_map, current_lambda.clone());
} Self::visit(args, scopes, capture_map, current_lambda.clone());
UntypedKind::Tuple { elements } => { }
for el in elements { UntypedKind::Block { exprs } => {
Self::visit(el, scopes, capture_map, current_lambda.clone()); for expr in exprs {
} Self::visit(expr, scopes, capture_map, current_lambda.clone());
} }
UntypedKind::Record { fields } => { }
for (k, v) in fields { UntypedKind::Tuple { elements } => {
Self::visit(k, scopes, capture_map, current_lambda.clone()); for el in elements {
Self::visit(v, scopes, capture_map, current_lambda.clone()); Self::visit(el, scopes, capture_map, current_lambda.clone());
} }
} }
UntypedKind::Expansion { call: _, expanded } => { UntypedKind::Record { fields } => {
Self::visit(expanded, scopes, capture_map, current_lambda); for (k, v) in fields {
} Self::visit(k, scopes, capture_map, current_lambda.clone());
UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => { Self::visit(v, scopes, capture_map, current_lambda.clone());
Self::visit(body, scopes, capture_map, current_lambda); }
} }
UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) | UntypedKind::Parameter(_) => {} UntypedKind::Expansion { call: _, expanded } => {
} Self::visit(expanded, scopes, capture_map, current_lambda);
} }
} UntypedKind::Template(body)
| UntypedKind::Placeholder(body)
| UntypedKind::Splice(body) => {
Self::visit(body, scopes, capture_map, current_lambda);
}
UntypedKind::Nop
| UntypedKind::Constant(_)
| UntypedKind::Extension(_)
| UntypedKind::Parameter(_) => {}
}
}
}
+355 -355
View File
@@ -1,355 +1,355 @@
use crate::ast::compiler::binder::Binder; use crate::ast::compiler::binder::Binder;
use crate::ast::compiler::{TypeChecker, TypedNode}; use crate::ast::compiler::{TypeChecker, TypedNode};
use crate::ast::nodes::{Node, Symbol, UntypedKind}; use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::parser::Parser; use crate::ast::parser::Parser;
use crate::ast::types::{Object, StaticType, Value}; use crate::ast::types::{Object, StaticType, Value};
use crate::ast::vm::{TracingObserver, VM}; use crate::ast::vm::{TracingObserver, VM};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::ast::compiler::bound_nodes::{Address, BoundNode}; use crate::ast::compiler::bound_nodes::{Address, BoundNode};
use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
use crate::ast::compiler::optimizer::Optimizer; use crate::ast::compiler::optimizer::Optimizer;
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer};
use crate::ast::compiler::tco::{TCO, ExecNode}; use crate::ast::compiler::tco::{ExecNode, TCO};
use crate::ast::rtl; use crate::ast::rtl;
use crate::ast::rtl::intrinsics; use crate::ast::rtl::intrinsics;
pub struct Environment { pub struct Environment {
pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>, pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
pub global_types: Rc<RefCell<HashMap<u32, StaticType>>>, pub global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
pub global_purity: Rc<RefCell<HashMap<u32, bool>>>, pub global_purity: Rc<RefCell<HashMap<u32, bool>>>,
pub global_values: Rc<RefCell<Vec<Value>>>, pub global_values: Rc<RefCell<Vec<Value>>>,
pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>, pub function_registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
pub typed_function_registry: Rc<RefCell<HashMap<u32, TypedNode>>>, pub typed_function_registry: Rc<RefCell<HashMap<u32, TypedNode>>>,
pub monomorph_cache: Rc<RefCell<MonoCache>>, pub monomorph_cache: Rc<RefCell<MonoCache>>,
pub debug_mode: bool, pub debug_mode: bool,
pub optimization: bool, pub optimization: bool,
} }
struct EnvFunctionRegistry { struct EnvFunctionRegistry {
registry: Rc<RefCell<HashMap<u32, BoundNode>>>, registry: Rc<RefCell<HashMap<u32, BoundNode>>>,
} }
impl FunctionRegistry for EnvFunctionRegistry { impl FunctionRegistry for EnvFunctionRegistry {
fn resolve(&self, addr: Address) -> Option<BoundNode> { fn resolve(&self, addr: Address) -> Option<BoundNode> {
if let Address::Global(idx) = addr { if let Address::Global(idx) = addr {
self.registry.borrow().get(&idx).cloned() self.registry.borrow().get(&idx).cloned()
} else { } else {
None None
} }
} }
} }
/// 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<Symbol, u32>>>, global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
global_types: Rc<RefCell<HashMap<u32, StaticType>>>, global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
global_values: Rc<RefCell<Vec<Value>>>, global_values: Rc<RefCell<Vec<Value>>>,
} }
impl MacroEvaluator for RuntimeMacroEvaluator { impl MacroEvaluator for RuntimeMacroEvaluator {
fn evaluate( fn evaluate(
&self, &self,
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
bindings: &HashMap<Rc<str>, Node<UntypedKind>>, bindings: &HashMap<Rc<str>, Node<UntypedKind>>,
) -> Result<Value, String> { ) -> 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(sym) = &node.kind if let UntypedKind::Identifier(sym) = &node.kind
&& let Some(arg_node) = bindings.get(&sym.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
let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; let bound_ast = Binder::bind_root(self.global_names.clone(), node)?;
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast, &[])?; let typed_ast = checker.check(bound_ast, &[])?;
let exec_ast = TCO::optimize(typed_ast); let exec_ast = TCO::optimize(typed_ast);
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
vm.run(&exec_ast) vm.run(&exec_ast)
} }
} }
impl Default for Environment { impl Default for Environment {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl Environment { impl Environment {
pub fn new() -> Self { pub fn new() -> Self {
let env = Self { let env = Self {
global_names: Rc::new(RefCell::new(HashMap::new())), global_names: Rc::new(RefCell::new(HashMap::new())),
global_types: Rc::new(RefCell::new(HashMap::new())), global_types: Rc::new(RefCell::new(HashMap::new())),
global_purity: Rc::new(RefCell::new(HashMap::new())), global_purity: Rc::new(RefCell::new(HashMap::new())),
global_values: Rc::new(RefCell::new(Vec::new())), global_values: Rc::new(RefCell::new(Vec::new())),
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
typed_function_registry: Rc::new(RefCell::new(HashMap::new())), typed_function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false, debug_mode: false,
optimization: true, optimization: true,
}; };
env.register_stdlib(); env.register_stdlib();
env env
} }
pub fn set_debug_mode(&mut self, enabled: bool) { pub fn set_debug_mode(&mut self, enabled: bool) {
self.debug_mode = enabled; self.debug_mode = enabled;
} }
fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> { fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> {
let evaluator = RuntimeMacroEvaluator { let evaluator = RuntimeMacroEvaluator {
global_names: self.global_names.clone(), global_names: self.global_names.clone(),
global_types: self.global_types.clone(), global_types: self.global_types.clone(),
global_values: self.global_values.clone(), global_values: self.global_values.clone(),
}; };
MacroExpander::new(MacroRegistry::new(), evaluator) MacroExpander::new(MacroRegistry::new(), evaluator)
} }
pub fn register_native( pub fn register_native(
&self, &self,
name: &str, name: &str,
ty: StaticType, ty: StaticType,
is_pure: bool, is_pure: bool,
func: impl Fn(Vec<Value>) -> Value + 'static, func: impl Fn(Vec<Value>) -> Value + 'static,
) { ) {
let mut names = self.global_names.borrow_mut(); let mut names = self.global_names.borrow_mut();
let mut types = self.global_types.borrow_mut(); let mut types = self.global_types.borrow_mut();
let mut values = self.global_values.borrow_mut(); let mut values = self.global_values.borrow_mut();
let mut purity = self.global_purity.borrow_mut(); let mut purity = self.global_purity.borrow_mut();
let idx = values.len() as u32; let idx = values.len() as u32;
names.insert(Symbol::from(name), idx); names.insert(Symbol::from(name), idx);
types.insert(idx, ty); types.insert(idx, ty);
purity.insert(idx, is_pure); purity.insert(idx, is_pure);
values.push(Value::Function(Rc::new(func))); values.push(Value::Function(Rc::new(func)));
} }
pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) {
let mut names = self.global_names.borrow_mut(); let mut names = self.global_names.borrow_mut();
let mut types = self.global_types.borrow_mut(); let mut types = self.global_types.borrow_mut();
let mut values = self.global_values.borrow_mut(); let mut values = self.global_values.borrow_mut();
let mut purity = self.global_purity.borrow_mut(); let mut purity = self.global_purity.borrow_mut();
let idx = values.len() as u32; let idx = values.len() as u32;
names.insert(Symbol::from(name), idx); names.insert(Symbol::from(name), idx);
types.insert(idx, ty); types.insert(idx, ty);
purity.insert(idx, true); // Constants are always pure purity.insert(idx, true); // Constants are always pure
values.push(val); values.push(val);
} }
fn register_stdlib(&self) { fn register_stdlib(&self) {
// Register all standard library functions via RTL module // Register all standard library functions via RTL module
rtl::register(self); rtl::register(self);
} }
pub fn dump_ast(&self, source: &str) -> Result<String, String> { pub fn dump_ast(&self, source: &str) -> Result<String, String> {
let compiled = self.compile(source)?; let compiled = self.compile(source)?;
let linked = self.link(compiled); let linked = self.link(compiled);
Ok(Dumper::dump(&linked)) Ok(Dumper::dump(&linked))
} }
/// Frontend: Parse -> Expand Macros -> Bind -> Type Check /// Frontend: Parse -> Expand Macros -> Bind -> Type Check
pub fn compile(&self, source: &str) -> Result<TypedNode, String> { pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
// 1. Parse // 1. Parse
let mut parser = Parser::new(source)?; let mut parser = Parser::new(source)?;
let untyped_ast = parser.parse_expression()?; let untyped_ast = parser.parse_expression()?;
// 2. Check for trailing tokens // 2. Check for trailing tokens
if !parser.at_eof() { if !parser.at_eof() {
return Err( return Err(
"Unexpected trailing expressions in script. Use (do ...) for sequences." "Unexpected trailing expressions in script. Use (do ...) for sequences."
.to_string(), .to_string(),
); );
} }
// 3. Expand Macros // 3. Expand Macros
let expanded_ast = self.get_expander().expand(untyped_ast)?; let expanded_ast = self.get_expander().expand(untyped_ast)?;
// 4. Bind // 4. Bind
let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?;
// 5. Collect Lambdas (Populate the registry with untyped templates) // 5. Collect Lambdas (Populate the registry with untyped templates)
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
// 6. Type Check // 6. Type Check
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast, &[])?; let typed_ast = checker.check(bound_ast, &[])?;
// 7. Collect Typed Lambdas // 7. Collect Typed Lambdas
LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut()); LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut());
Ok(typed_ast) Ok(typed_ast)
} }
/// Backend: Optimization (TCO, etc.) /// Backend: Optimization (TCO, etc.)
pub fn link(&self, node: TypedNode) -> ExecNode { pub fn link(&self, node: TypedNode) -> ExecNode {
// 1. Specialize (Always performed for correctness) // 1. Specialize (Always performed for correctness)
let specialized = self.specialize_node(node); let specialized = self.specialize_node(node);
// 2. Optimize (Level 1: Cracking, Level 2: Collapsing) // 2. Optimize (Level 1: Cracking, Level 2: Collapsing)
let optimizer = Optimizer::new(self.optimization) let optimizer = Optimizer::new(self.optimization)
.with_globals(self.global_values.clone()) .with_globals(self.global_values.clone())
.with_purity(self.global_purity.clone()) .with_purity(self.global_purity.clone())
.with_registry(self.typed_function_registry.clone()); .with_registry(self.typed_function_registry.clone());
let optimized = optimizer.optimize(specialized); let optimized = optimizer.optimize(specialized);
// 3. TCO (Always performed, converts to ExecNode) // 3. TCO (Always performed, converts to ExecNode)
TCO::optimize(optimized) TCO::optimize(optimized)
} }
fn specialize_node(&self, node: TypedNode) -> TypedNode { fn specialize_node(&self, node: TypedNode) -> TypedNode {
let registry = Rc::new(EnvFunctionRegistry { let registry = Rc::new(EnvFunctionRegistry {
registry: self.function_registry.clone(), registry: self.function_registry.clone(),
}); });
let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
let func_reg = self.function_registry.clone(); let func_reg = self.function_registry.clone();
let mono_cache = self.monomorph_cache.clone(); let mono_cache = self.monomorph_cache.clone();
let global_values = self.global_values.clone(); let global_values = self.global_values.clone();
let global_types = self.global_types.clone(); let global_types = self.global_types.clone();
let global_purity = self.global_purity.clone(); let global_purity = self.global_purity.clone();
let optimization = self.optimization; let optimization = self.optimization;
let compiler = Rc::new( let compiler = Rc::new(
move |func_template: BoundNode, move |func_template: BoundNode,
arg_types: &[StaticType]| arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> { -> Result<(Value, StaticType), String> {
// 1. Re-TypeCheck the template with concrete argument types // 1. Re-TypeCheck the template with concrete argument types
let checker = TypeChecker::new(global_types.clone()); let checker = TypeChecker::new(global_types.clone());
let retyped_ast = checker.check(func_template, arg_types)?; let retyped_ast = checker.check(func_template, arg_types)?;
// 2. Specialize (Recursive) // 2. Specialize (Recursive)
let sub_registry = Rc::new(EnvFunctionRegistry { let sub_registry = Rc::new(EnvFunctionRegistry {
registry: func_reg.clone(), registry: func_reg.clone(),
}); });
let sub_rtl_lookup = let sub_rtl_lookup =
Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
let sub_specializer = Specializer::new( let sub_specializer = Specializer::new(
Some(sub_registry), Some(sub_registry),
None, None,
Some(sub_rtl_lookup), Some(sub_rtl_lookup),
Some(mono_cache.clone()), Some(mono_cache.clone()),
); );
let specialized_ast = sub_specializer.specialize(retyped_ast); let specialized_ast = sub_specializer.specialize(retyped_ast);
// 3. Optimize (Phase 2: Cracking & Folding) // 3. Optimize (Phase 2: Cracking & Folding)
let optimizer = Optimizer::new(optimization) let optimizer = Optimizer::new(optimization)
.with_globals(global_values.clone()) .with_globals(global_values.clone())
.with_purity(global_purity.clone()); .with_purity(global_purity.clone());
let optimized_ast = optimizer.optimize(specialized_ast); let optimized_ast = optimizer.optimize(specialized_ast);
// 4. TCO (converts to ExecNode) // 4. TCO (converts to ExecNode)
let tco_ast = TCO::optimize(optimized_ast); let tco_ast = TCO::optimize(optimized_ast);
// 5. Compile to Value (VM) // 5. Compile to Value (VM)
let mut vm = VM::new(global_values.clone()); let mut vm = VM::new(global_values.clone());
let compiled_val = match vm.run(&tco_ast) { let compiled_val = match vm.run(&tco_ast) {
Ok(v) => v, Ok(v) => v,
Err(e) => return Err(format!("VM Error during specialization: {}", e)), Err(e) => return Err(format!("VM Error during specialization: {}", e)),
}; };
// 6. Determine correct return type from the newly inferred function signature // 6. Determine correct return type from the newly inferred function signature
let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty { let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty {
sig.ret.clone() sig.ret.clone()
} else { } else {
StaticType::Any StaticType::Any
}; };
Ok((compiled_val, ret_type)) Ok((compiled_val, ret_type))
}, },
); );
let specializer = Specializer::new( let specializer = Specializer::new(
Some(registry), Some(registry),
Some(compiler), Some(compiler),
Some(rtl_lookup), Some(rtl_lookup),
Some(self.monomorph_cache.clone()), Some(self.monomorph_cache.clone()),
); );
specializer.specialize(node) specializer.specialize(node)
} }
/// Runtime: Execute the linked AST in the VM /// Runtime: Execute the linked AST in the VM
pub fn run(&self, node: &ExecNode) -> Result<Value, String> { pub fn run(&self, node: &ExecNode) -> Result<Value, String> {
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
let mut result = vm.run(node)?; let mut result = vm.run(node)?;
// Handle potential script body closure // Handle potential script body closure
if let Value::Object(obj) = &result if let Value::Object(obj) = &result
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>() && let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
{ {
result = vm.run(&closure.exec_node)?; result = vm.run(&closure.exec_node)?;
} }
// IMPORTANT: Resolve any pending tail call requests from the top-level execution // IMPORTANT: Resolve any pending tail call requests from the top-level execution
while let Value::TailCallRequest(payload) = result { while let Value::TailCallRequest(payload) = result {
let (next_obj, next_args) = *payload; let (next_obj, next_args) = *payload;
if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() { if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() {
result = vm.run_with_args(closure, next_args)?; result = vm.run_with_args(closure, next_args)?;
} else { } else {
return Err(format!( return Err(format!(
"Tail call target is not a closure: {}", "Tail call target is not a closure: {}",
next_obj.type_name() next_obj.type_name()
)); ));
} }
} }
Ok(result) Ok(result)
} }
pub fn run_script(&self, source: &str) -> Result<Value, String> { pub fn run_script(&self, source: &str) -> Result<Value, String> {
if self.debug_mode { if self.debug_mode {
let (res, logs) = self.run_debug(source)?; let (res, logs) = self.run_debug(source)?;
for line in logs { for line in logs {
println!("{}", line); println!("{}", line);
} }
res res
} else { } else {
let compiled = self.compile(source)?; let compiled = self.compile(source)?;
let linked = self.link(compiled); let linked = self.link(compiled);
self.run(&linked) self.run(&linked)
} }
} }
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> { pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
let compiled = self.compile(source)?; let compiled = self.compile(source)?;
let linked = self.link(compiled); let linked = self.link(compiled);
// Execute with TracingObserver // Execute with TracingObserver
let mut vm = VM::new(self.global_values.clone()); let mut vm = VM::new(self.global_values.clone());
let mut observer = TracingObserver::new(); let mut observer = TracingObserver::new();
let mut result = vm.run_with_observer(&mut observer, &linked); let mut result = vm.run_with_observer(&mut observer, &linked);
// If result is a closure (script entry), execute the body too // If result is a closure (script entry), execute the body too
if let Ok(Value::Object(obj)) = &result if let Ok(Value::Object(obj)) = &result
&& let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>() && let Some(closure) = obj.as_any().downcast_ref::<crate::ast::vm::Closure>()
{ {
result = vm.run_with_observer(&mut observer, &closure.exec_node); result = vm.run_with_observer(&mut observer, &closure.exec_node);
} }
// Resolve top-level tail calls // Resolve top-level tail calls
while let Ok(Value::TailCallRequest(payload)) = result { while let Ok(Value::TailCallRequest(payload)) = result {
let (next_obj, next_args) = *payload; let (next_obj, next_args) = *payload;
if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() { if let Some(closure) = next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>() {
result = vm.run_with_args_observed(&mut observer, closure, next_args); result = vm.run_with_args_observed(&mut observer, closure, next_args);
} else { } else {
result = Err(format!( result = Err(format!(
"Tail call target is not a closure: {}", "Tail call target is not a closure: {}",
next_obj.type_name() next_obj.type_name()
)); ));
break; break;
} }
} }
Ok((result, observer.logs)) Ok((result, observer.logs))
} }
} }
+219 -191
View File
@@ -1,191 +1,219 @@
use std::iter::Peekable; use crate::ast::types::SourceLocation;
use std::str::Chars; use std::iter::Peekable;
use std::rc::Rc; use std::rc::Rc;
use crate::ast::types::SourceLocation; use std::str::Chars;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum TokenKind { pub enum TokenKind {
LeftParen, RightParen, LeftParen,
LeftBracket, RightBracket, RightParen,
LeftBrace, RightBrace, LeftBracket,
Quote, Backtick, Tilde, At, RightBracket,
Identifier(Rc<str>), LeftBrace,
Keyword(Rc<str>), RightBrace,
Integer(i64), Quote,
Float(f64), Backtick,
String(Rc<str>), Tilde,
EOF, At,
} Identifier(Rc<str>),
Keyword(Rc<str>),
#[derive(Debug, Clone)] Integer(i64),
pub struct Token { Float(f64),
pub kind: TokenKind, String(Rc<str>),
pub location: SourceLocation, EOF,
} }
pub struct Lexer<'a> { #[derive(Debug, Clone)]
input: Peekable<Chars<'a>>, pub struct Token {
line: u32, pub kind: TokenKind,
col: u32, pub location: SourceLocation,
} }
impl<'a> Lexer<'a> { pub struct Lexer<'a> {
pub fn new(input: &'a str) -> Self { input: Peekable<Chars<'a>>,
Self { line: u32,
input: input.chars().peekable(), col: u32,
line: 1, }
col: 1,
} impl<'a> Lexer<'a> {
} pub fn new(input: &'a str) -> Self {
Self {
pub fn next_token(&mut self) -> Result<Token, String> { input: input.chars().peekable(),
self.skip_whitespace(); line: 1,
col: 1,
let start_location = SourceLocation { line: self.line, col: self.col }; }
}
let char = match self.input.next() {
Some(c) => { pub fn next_token(&mut self) -> Result<Token, String> {
let current_char = c; self.skip_whitespace();
self.col += 1;
current_char let start_location = SourceLocation {
}, line: self.line,
None => return Ok(Token { kind: TokenKind::EOF, location: start_location }), col: self.col,
}; };
let kind = match char { let char = match self.input.next() {
'(' => TokenKind::LeftParen, Some(c) => {
')' => TokenKind::RightParen, let current_char = c;
'[' => TokenKind::LeftBracket, self.col += 1;
']' => TokenKind::RightBracket, current_char
'{' => TokenKind::LeftBrace, }
'}' => TokenKind::RightBrace, None => {
'\'' => TokenKind::Quote, return Ok(Token {
'`' => TokenKind::Backtick, kind: TokenKind::EOF,
'~' => TokenKind::Tilde, location: start_location,
'@' => TokenKind::At, });
':' => { }
let id = self.read_while(|c| !c.is_whitespace() && !"()[]{}\"'`~@;".contains(c)); };
TokenKind::Keyword(Rc::from(id))
} let kind = match char {
'"' => TokenKind::String(Rc::from(self.read_string()?)), '(' => TokenKind::LeftParen,
c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => { ')' => TokenKind::RightParen,
let mut num_str = String::from(c); '[' => TokenKind::LeftBracket,
while let Some(&next) = self.peek() { ']' => TokenKind::RightBracket,
if next.is_ascii_digit() || next == '.' { '{' => TokenKind::LeftBrace,
num_str.push(self.input.next().unwrap()); '}' => TokenKind::RightBrace,
self.col += 1; '\'' => TokenKind::Quote,
} else { '`' => TokenKind::Backtick,
break; '~' => TokenKind::Tilde,
} '@' => TokenKind::At,
} ':' => {
let id = self.read_while(|c| !c.is_whitespace() && !"()[]{}\"'`~@;".contains(c));
if num_str.contains('.') { TokenKind::Keyword(Rc::from(id))
TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?) }
} else { '"' => TokenKind::String(Rc::from(self.read_string()?)),
TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?) c if c.is_ascii_digit()
} || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) =>
} {
c if is_ident_start(c) => { let mut num_str = String::from(c);
let mut id = String::from(c); while let Some(&next) = self.peek() {
id.push_str(&self.read_while(is_ident_char)); if next.is_ascii_digit() || next == '.' {
TokenKind::Identifier(Rc::from(id)) num_str.push(self.input.next().unwrap());
} self.col += 1;
_ => return Err(format!("Unexpected character: {} at {}:{}", char, self.line, self.col - 1)), } else {
}; break;
}
Ok(Token { kind, location: start_location }) }
}
if num_str.contains('.') {
fn peek(&mut self) -> Option<&char> { TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?)
self.input.peek() } else {
} TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?)
}
fn skip_whitespace(&mut self) { }
while let Some(&c) = self.peek() { c if is_ident_start(c) => {
if c.is_whitespace() || is_invisible(c) || c == ',' { let mut id = String::from(c);
if c == '\n' { id.push_str(&self.read_while(is_ident_char));
self.line += 1; TokenKind::Identifier(Rc::from(id))
self.col = 1; }
} else { _ => {
self.col += 1; return Err(format!(
} "Unexpected character: {} at {}:{}",
self.input.next(); char,
} else if c == ';' { self.line,
for c in self.input.by_ref() { self.col - 1
if c == '\n' { ));
self.line += 1; }
self.col = 1; };
break;
} Ok(Token {
} kind,
} else { location: start_location,
break; })
} }
}
} fn peek(&mut self) -> Option<&char> {
self.input.peek()
fn read_while<F>(&mut self, mut predicate: F) -> String }
where F: FnMut(char) -> bool {
let mut s = String::new(); fn skip_whitespace(&mut self) {
while let Some(&c) = self.peek() { while let Some(&c) = self.peek() {
if predicate(c) { if c.is_whitespace() || is_invisible(c) || c == ',' {
s.push(self.input.next().unwrap()); if c == '\n' {
self.col += 1; self.line += 1;
} else { self.col = 1;
break; } else {
} self.col += 1;
} }
s self.input.next();
} } else if c == ';' {
for c in self.input.by_ref() {
fn read_string(&mut self) -> Result<String, String> { if c == '\n' {
let mut s = String::new(); self.line += 1;
while let Some(c) = self.input.next() { self.col = 1;
self.col += 1; break;
match c { }
'"' => return Ok(s), }
'\\' => { } else {
let next = self.input.next().ok_or("Unterminated string escape")?; break;
self.col += 1; }
match next { }
'n' => s.push('\n'), }
'r' => s.push('\r'),
't' => s.push('\t'), fn read_while<F>(&mut self, mut predicate: F) -> String
'\\' => s.push('\\'), where
'"' => s.push('"'), F: FnMut(char) -> bool,
_ => s.push(next), {
} let mut s = String::new();
} while let Some(&c) = self.peek() {
'\n' => { if predicate(c) {
self.line += 1; s.push(self.input.next().unwrap());
self.col = 1; self.col += 1;
s.push(c); } else {
} break;
_ => s.push(c), }
} }
} s
Err("Unterminated string".to_string()) }
}
} fn read_string(&mut self) -> Result<String, String> {
let mut s = String::new();
fn is_ident_start(c: char) -> bool { while let Some(c) = self.input.next() {
c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) self.col += 1;
} match c {
'"' => return Ok(s),
fn is_ident_char(c: char) -> bool { '\\' => {
is_ident_start(c) || c.is_ascii_digit() let next = self.input.next().ok_or("Unterminated string escape")?;
} self.col += 1;
match next {
/// Returns true if the character is an invisible control character 'n' => s.push('\n'),
/// that should be ignored by the lexer (like BOM or Zero Width Space). 'r' => s.push('\r'),
fn is_invisible(c: char) -> bool { 't' => s.push('\t'),
match c { '\\' => s.push('\\'),
'\u{FEFF}' | // BOM '"' => s.push('"'),
'\u{200B}' | // Zero Width Space _ => s.push(next),
'\u{200C}' | // Zero Width Non-Joiner }
'\u{200D}' | // Zero Width Joiner }
'\u{2060}' // Word Joiner '\n' => {
=> true, self.line += 1;
_ => false, self.col = 1;
} s.push(c);
} }
_ => s.push(c),
}
}
Err("Unterminated string".to_string())
}
}
fn is_ident_start(c: char) -> bool {
c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c)
}
fn is_ident_char(c: char) -> bool {
is_ident_start(c) || c.is_ascii_digit()
}
/// Returns true if the character is an invisible control character
/// that should be ignored by the lexer (like BOM or Zero Width Space).
fn is_invisible(c: char) -> bool {
match c {
'\u{FEFF}' | // BOM
'\u{200B}' | // Zero Width Space
'\u{200C}' | // Zero Width Non-Joiner
'\u{200D}' | // Zero Width Joiner
'\u{2060}' // Word Joiner
=> true,
_ => false,
}
}
+8 -8
View File
@@ -1,8 +1,8 @@
pub mod types; pub mod compiler;
pub mod lexer; pub mod environment;
pub mod nodes; pub mod lexer;
pub mod parser; pub mod nodes;
pub mod compiler; pub mod parser;
pub mod environment; pub mod rtl;
pub mod vm; pub mod types;
pub mod rtl; pub mod vm;
+118 -112
View File
@@ -1,112 +1,118 @@
use std::rc::Rc; use crate::ast::types::{Identity, Object, Value};
use std::fmt::Debug; use std::any::Any;
use std::any::Any; use std::fmt::Debug;
use crate::ast::types::{Identity, Value, Object}; use std::rc::Rc;
/// A name with an optional context for macro hygiene. /// A name with an optional context for macro hygiene.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Symbol { pub struct Symbol {
pub name: Rc<str>, pub name: Rc<str>,
/// Points to the identity of the Expansion node if this symbol /// Points to the identity of the Expansion node if this symbol
/// was created/referenced inside a macro expansion. /// was created/referenced inside a macro expansion.
pub context: Option<Identity>, pub context: Option<Identity>,
} }
impl From<Rc<str>> for Symbol { impl From<Rc<str>> for Symbol {
fn from(name: Rc<str>) -> Self { fn from(name: Rc<str>) -> Self {
Self { name, context: None } Self {
} name,
} context: None,
}
impl From<&str> for Symbol { }
fn from(name: &str) -> Self { }
Self { name: Rc::from(name), context: None }
} impl From<&str> for Symbol {
} fn from(name: &str) -> Self {
Self {
/// A generic AST Node wrapper to preserve identity and metadata name: Rc::from(name),
#[derive(Debug, Clone, PartialEq)] context: None,
pub struct Node<K, T = ()> { }
pub identity: Identity, }
pub kind: K, }
pub ty: T,
} /// A generic AST Node wrapper to preserve identity and metadata
#[derive(Debug, Clone, PartialEq)]
impl Object for Node<UntypedKind> { pub struct Node<K, T = ()> {
fn type_name(&self) -> &'static str { pub identity: Identity,
"ast-node" pub kind: K,
} pub ty: T,
fn as_any(&self) -> &dyn Any { }
self
} impl Object for Node<UntypedKind> {
} fn type_name(&self) -> &'static str {
"ast-node"
/// The base for custom node types (extensions) }
pub trait CustomNode: Debug { fn as_any(&self) -> &dyn Any {
fn display_name(&self) -> &'static str; self
fn clone_box(&self) -> Box<dyn CustomNode>; }
} }
impl Clone for Box<dyn CustomNode> { /// The base for custom node types (extensions)
fn clone(&self) -> Self { pub trait CustomNode: Debug {
self.clone_box() fn display_name(&self) -> &'static str;
} fn clone_box(&self) -> Box<dyn CustomNode>;
} }
#[derive(Debug, Clone)] impl Clone for Box<dyn CustomNode> {
pub enum UntypedKind { fn clone(&self) -> Self {
Nop, self.clone_box()
Constant(Value), }
Identifier(Symbol), }
Parameter(Symbol),
If { #[derive(Debug, Clone)]
cond: Box<Node<UntypedKind>>, pub enum UntypedKind {
then_br: Box<Node<UntypedKind>>, Nop,
else_br: Option<Box<Node<UntypedKind>>>, Constant(Value),
}, Identifier(Symbol),
Def { Parameter(Symbol),
name: Symbol, If {
value: Box<Node<UntypedKind>>, cond: Box<Node<UntypedKind>>,
}, then_br: Box<Node<UntypedKind>>,
Assign { else_br: Option<Box<Node<UntypedKind>>>,
target: Box<Node<UntypedKind>>, },
value: Box<Node<UntypedKind>>, Def {
}, name: Symbol,
Lambda { value: Box<Node<UntypedKind>>,
params: Box<Node<UntypedKind>>, },
body: Rc<Node<UntypedKind>>, Assign {
}, target: Box<Node<UntypedKind>>,
Call { value: Box<Node<UntypedKind>>,
callee: Box<Node<UntypedKind>>, },
args: Box<Node<UntypedKind>>, Lambda {
}, params: Box<Node<UntypedKind>>,
Block { body: Rc<Node<UntypedKind>>,
exprs: Vec<Node<UntypedKind>>, },
}, Call {
Tuple { callee: Box<Node<UntypedKind>>,
elements: Vec<Node<UntypedKind>>, args: Box<Node<UntypedKind>>,
}, },
Record { Block {
fields: Vec<(Node<UntypedKind>, Node<UntypedKind>)>, exprs: Vec<Node<UntypedKind>>,
}, },
/// A macro declaration that can be expanded at compile time. Tuple {
MacroDecl { elements: Vec<Node<UntypedKind>>,
name: Symbol, },
params: Box<Node<UntypedKind>>, Record {
body: Box<Node<UntypedKind>>, fields: Vec<(Node<UntypedKind>, Node<UntypedKind>)>,
}, },
/// A template for AST nodes, allowing for substitutions. (Quasiquote) /// A macro declaration that can be expanded at compile time.
Template(Box<Node<UntypedKind>>), MacroDecl {
/// A placeholder inside a template to be replaced by a node or value. (Unquote) name: Symbol,
Placeholder(Box<Node<UntypedKind>>), params: Box<Node<UntypedKind>>,
/// A placeholder that flattens a sequence or merges a record into its surroundings. (Splice) body: Box<Node<UntypedKind>>,
Splice(Box<Node<UntypedKind>>), },
/// Represents an expanded macro call, preserving the original call for debugging. /// A template for AST nodes, allowing for substitutions. (Quasiquote)
Expansion { Template(Box<Node<UntypedKind>>),
/// The original call from the source AST. /// A placeholder inside a template to be replaced by a node or value. (Unquote)
call: Box<Node<UntypedKind>>, Placeholder(Box<Node<UntypedKind>>),
/// The resulting AST after macro expansion. /// A placeholder that flattens a sequence or merges a record into its surroundings. (Splice)
expanded: Box<Node<UntypedKind>>, Splice(Box<Node<UntypedKind>>),
}, /// Represents an expanded macro call, preserving the original call for debugging.
Extension(Box<dyn CustomNode>), Expansion {
} /// The original call from the source AST.
call: Box<Node<UntypedKind>>,
/// The resulting AST after macro expansion.
expanded: Box<Node<UntypedKind>>,
},
Extension(Box<dyn CustomNode>),
}
+400 -345
View File
@@ -1,345 +1,400 @@
use std::rc::Rc; use crate::ast::lexer::{Lexer, Token, TokenKind};
use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; use crate::ast::types::{Identity, Keyword, NodeIdentity, Value};
use crate::ast::nodes::{Node, UntypedKind, Symbol}; use std::rc::Rc;
pub struct Parser<'a> { pub struct Parser<'a> {
lexer: Lexer<'a>, lexer: Lexer<'a>,
current_token: Token, current_token: Token,
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Result<Self, String> { pub fn new(input: &'a str) -> Result<Self, String> {
let mut lexer = Lexer::new(input); let mut lexer = Lexer::new(input);
let current_token = lexer.next_token()?; let current_token = lexer.next_token()?;
Ok(Self { lexer, current_token }) Ok(Self {
} lexer,
current_token,
fn advance(&mut self) -> Result<Token, String> { })
let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?); }
Ok(prev)
} fn advance(&mut self) -> Result<Token, String> {
let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?);
fn peek(&self) -> &TokenKind { Ok(prev)
&self.current_token.kind }
}
fn peek(&self) -> &TokenKind {
pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> { &self.current_token.kind
let token_loc = self.current_token.location; }
let identity = Rc::new(NodeIdentity { location: token_loc });
pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> {
match self.peek() { let token_loc = self.current_token.location;
TokenKind::LeftParen => self.parse_list(), let identity = Rc::new(NodeIdentity {
TokenKind::LeftBracket => self.parse_vector_literal(), location: token_loc,
TokenKind::LeftBrace => self.parse_record_literal(), });
TokenKind::Quote => {
self.advance()?; // consume ' match self.peek() {
let expr = self.parse_expression()?; TokenKind::LeftParen => self.parse_list(),
Ok(Node { TokenKind::LeftBracket => self.parse_vector_literal(),
identity: identity.clone(), TokenKind::LeftBrace => self.parse_record_literal(),
kind: UntypedKind::Call { TokenKind::Quote => {
callee: Box::new(self.make_id_node("quote", identity.clone())), self.advance()?; // consume '
args: Box::new(Node { let expr = self.parse_expression()?;
identity, Ok(Node {
kind: UntypedKind::Tuple { elements: vec![expr] }, identity: identity.clone(),
ty: (), kind: UntypedKind::Call {
}), callee: Box::new(self.make_id_node("quote", identity.clone())),
}, args: Box::new(Node {
ty: (), identity,
}) kind: UntypedKind::Tuple {
} elements: vec![expr],
TokenKind::Backtick => { },
self.advance()?; // consume ` ty: (),
let expr = self.parse_expression()?; }),
Ok(Node { },
identity, ty: (),
kind: UntypedKind::Template(Box::new(expr)), })
ty: (), }
}) TokenKind::Backtick => {
} self.advance()?; // consume `
TokenKind::Tilde => { let expr = self.parse_expression()?;
self.advance()?; // consume ~ Ok(Node {
if *self.peek() == TokenKind::At { identity,
self.advance()?; // consume @ kind: UntypedKind::Template(Box::new(expr)),
let expr = self.parse_expression()?; ty: (),
Ok(Node { })
identity, }
kind: UntypedKind::Splice(Box::new(expr)), TokenKind::Tilde => {
ty: (), self.advance()?; // consume ~
}) if *self.peek() == TokenKind::At {
} else { self.advance()?; // consume @
let expr = self.parse_expression()?; let expr = self.parse_expression()?;
Ok(Node { Ok(Node {
identity, identity,
kind: UntypedKind::Placeholder(Box::new(expr)), kind: UntypedKind::Splice(Box::new(expr)),
ty: (), ty: (),
}) })
} } else {
} let expr = self.parse_expression()?;
_ => self.parse_atom(), Ok(Node {
} identity,
} kind: UntypedKind::Placeholder(Box::new(expr)),
ty: (),
pub fn at_eof(&self) -> bool { })
matches!(self.current_token.kind, TokenKind::EOF) }
} }
_ => self.parse_atom(),
fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> { }
let token = self.advance()?; }
let identity = Rc::new(NodeIdentity { location: token.location });
pub fn at_eof(&self) -> bool {
let kind = match token.kind { matches!(self.current_token.kind, TokenKind::EOF)
TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)), }
TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)),
TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)), fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> {
TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))), let token = self.advance()?;
TokenKind::Identifier(id) => { let identity = Rc::new(NodeIdentity {
match id.as_ref() { location: token.location,
"..." => UntypedKind::Nop, });
_ => UntypedKind::Identifier(id.into()),
} let kind = match token.kind {
} TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)),
_ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)), TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)),
}; TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)),
TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))),
Ok(Node { identity, kind, ty: () }) TokenKind::Identifier(id) => match id.as_ref() {
} "..." => UntypedKind::Nop,
_ => UntypedKind::Identifier(id.into()),
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> { },
let start_loc = self.advance()?.location; // consume '(' _ => {
let identity = Rc::new(NodeIdentity { location: start_loc }); return Err(format!(
"Unexpected token in atom: {:?} at {:?}",
if *self.peek() == TokenKind::RightParen { token.kind, token.location
return Err(format!("Empty list () is not a valid expression at {:?}", start_loc)); ));
} }
};
let head = self.parse_expression()?;
Ok(Node {
let result = if let UntypedKind::Identifier(ref sym) = head.kind { identity,
match sym.name.as_ref() { kind,
"if" => self.parse_if(identity), ty: (),
"fn" => self.parse_fn(identity), })
"def" => self.parse_def(identity), }
"assign" => self.parse_assign(identity),
"do" => self.parse_do(identity), fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
"macro" => self.parse_macro_decl(identity), let start_loc = self.advance()?.location; // consume '('
_ => self.parse_call(head, identity), let identity = Rc::new(NodeIdentity {
} location: start_loc,
} else { });
self.parse_call(head, identity)
}; if *self.peek() == TokenKind::RightParen {
return Err(format!(
self.expect(TokenKind::RightParen)?; "Empty list () is not a valid expression at {:?}",
result start_loc
} ));
}
fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let cond = Box::new(self.parse_expression()?); let head = self.parse_expression()?;
let then_br = Box::new(self.parse_expression()?);
let mut else_br = None; let result = if let UntypedKind::Identifier(ref sym) = head.kind {
match sym.name.as_ref() {
if *self.peek() != TokenKind::RightParen { "if" => self.parse_if(identity),
else_br = Some(Box::new(self.parse_expression()?)); "fn" => self.parse_fn(identity),
} "def" => self.parse_def(identity),
"assign" => self.parse_assign(identity),
Ok(Node { "do" => self.parse_do(identity),
identity, "macro" => self.parse_macro_decl(identity),
kind: UntypedKind::If { cond, then_br, else_br }, _ => self.parse_call(head, identity),
ty: (), }
}) } else {
} self.parse_call(head, identity)
};
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let name_node = self.parse_expression()?; self.expect(TokenKind::RightParen)?;
let name = match name_node.kind { result
UntypedKind::Identifier(sym) => sym, }
_ => return Err("Expected identifier for def name".to_string()),
}; fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let cond = Box::new(self.parse_expression()?);
let value = Box::new(self.parse_expression()?); let then_br = Box::new(self.parse_expression()?);
let mut else_br = None;
Ok(Node {
identity, if *self.peek() != TokenKind::RightParen {
kind: UntypedKind::Def { name, value }, else_br = Some(Box::new(self.parse_expression()?));
ty: (), }
})
} Ok(Node {
identity,
fn parse_assign(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> { kind: UntypedKind::If {
// (assign target value) cond,
let target = Box::new(self.parse_expression()?); then_br,
let value = Box::new(self.parse_expression()?); else_br,
},
Ok(Node { ty: (),
identity, })
kind: UntypedKind::Assign { target, value }, }
ty: (),
}) fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
} let name_node = self.parse_expression()?;
let name = match name_node.kind {
fn parse_do(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> { UntypedKind::Identifier(sym) => sym,
let mut exprs = Vec::new(); _ => return Err("Expected identifier for def name".to_string()),
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { };
exprs.push(self.parse_expression()?);
} let value = Box::new(self.parse_expression()?);
Ok(Node {
identity, Ok(Node {
kind: UntypedKind::Block { exprs }, identity,
ty: (), kind: UntypedKind::Def { name, value },
}) ty: (),
} })
}
fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let params = Box::new(self.parse_param_vector()?); fn parse_assign(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let body = self.parse_expression()?; // (assign target value)
let target = Box::new(self.parse_expression()?);
Ok(Node { let value = Box::new(self.parse_expression()?);
identity,
kind: UntypedKind::Lambda { Ok(Node {
params, identity,
body: Rc::new(body), kind: UntypedKind::Assign { target, value },
}, ty: (),
ty: (), })
}) }
}
fn parse_do(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> { let mut exprs = Vec::new();
let name_node = self.parse_expression()?; while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
let name = match name_node.kind { exprs.push(self.parse_expression()?);
UntypedKind::Identifier(sym) => sym, }
_ => return Err("Expected identifier for macro name".to_string()), Ok(Node {
}; identity,
let params = Box::new(self.parse_param_vector()?); kind: UntypedKind::Block { exprs },
let body = self.parse_expression()?; ty: (),
Ok(Node { })
identity, }
kind: UntypedKind::MacroDecl { name, params, body: Box::new(body) },
ty: (), fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
}) let params = Box::new(self.parse_param_vector()?);
} let body = self.parse_expression()?;
fn parse_param_vector(&mut self) -> Result<Node<UntypedKind>, String> { Ok(Node {
if *self.peek() != TokenKind::LeftBracket { identity,
return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); kind: UntypedKind::Lambda {
} params,
let token = self.advance()?; body: Rc::new(body),
let identity = Rc::new(NodeIdentity { location: token.location }); },
ty: (),
let mut elements = Vec::new(); })
while *self.peek() != TokenKind::RightBracket { }
let next_peek = self.peek();
match next_peek { fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
TokenKind::Identifier(name) => { let name_node = self.parse_expression()?;
let name = name.clone(); let name = match name_node.kind {
let token = self.advance()?; UntypedKind::Identifier(sym) => sym,
let p_identity = Rc::new(NodeIdentity { location: token.location }); _ => return Err("Expected identifier for macro name".to_string()),
elements.push(Node { };
identity: p_identity, let params = Box::new(self.parse_param_vector()?);
kind: UntypedKind::Parameter(name.into()), let body = self.parse_expression()?;
ty: (), Ok(Node {
}); identity,
}, kind: UntypedKind::MacroDecl {
TokenKind::LeftBracket => { name,
elements.push(self.parse_param_vector()?); params,
}, body: Box::new(body),
_ => return Err(format!("Expected identifier or nested parameter vector, found {:?}", next_peek)), },
} ty: (),
} })
self.expect(TokenKind::RightBracket)?; }
Ok(Node {
identity, fn parse_param_vector(&mut self) -> Result<Node<UntypedKind>, String> {
kind: UntypedKind::Tuple { elements }, if *self.peek() != TokenKind::LeftBracket {
ty: (), return Err(format!(
}) "Expected parameter vector [...] for fn, found {:?}",
} self.peek()
));
fn parse_call(&mut self, callee: Node<UntypedKind>, identity: Identity) -> Result<Node<UntypedKind>, String> { }
let mut elements = Vec::new(); let token = self.advance()?;
let identity = Rc::new(NodeIdentity {
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { location: token.location,
elements.push(self.parse_expression()?); });
}
let mut elements = Vec::new();
// The arguments are wrapped in a Tuple node, reusing the call's identity/location. while *self.peek() != TokenKind::RightBracket {
let args_node = Node { let next_peek = self.peek();
identity: identity.clone(), match next_peek {
kind: UntypedKind::Tuple { elements }, TokenKind::Identifier(name) => {
ty: (), let name = name.clone();
}; let token = self.advance()?;
let p_identity = Rc::new(NodeIdentity {
Ok(Node { location: token.location,
identity, });
kind: UntypedKind::Call { callee: Box::new(callee), args: Box::new(args_node) }, elements.push(Node {
ty: (), identity: p_identity,
}) kind: UntypedKind::Parameter(name.into()),
} ty: (),
});
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> { }
let token = self.advance()?; TokenKind::LeftBracket => {
let mut elements = Vec::new(); elements.push(self.parse_param_vector()?);
}
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { _ => {
let expr = self.parse_expression()?; return Err(format!(
elements.push(expr); "Expected identifier or nested parameter vector, found {:?}",
} next_peek
));
self.expect(TokenKind::RightBracket)?; }
}
Ok(Node { }
identity: Rc::new(NodeIdentity { location: token.location }), self.expect(TokenKind::RightBracket)?;
kind: UntypedKind::Tuple { elements }, Ok(Node {
ty: (), identity,
}) kind: UntypedKind::Tuple { elements },
} ty: (),
})
fn parse_record_literal(&mut self) -> Result<Node<UntypedKind>, String> { }
let token = self.advance()?;
let mut fields = Vec::new(); fn parse_call(
&mut self,
while *self.peek() != TokenKind::RightBrace { callee: Node<UntypedKind>,
if *self.peek() == TokenKind::EOF { identity: Identity,
return Err("Unexpected EOF in record literal".to_string()); ) -> Result<Node<UntypedKind>, String> {
} let mut elements = Vec::new();
let key_node = self.parse_expression()?; while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
// We check for keyword kind here (syntactically) to avoid ambiguity, but elements.push(self.parse_expression()?);
// strictly we could allow any expression and check at runtime. }
// Delphi enforces keywords. We can do minimal check here.
match &key_node.kind { // The arguments are wrapped in a Tuple node, reusing the call's identity/location.
UntypedKind::Constant(Value::Keyword(_)) => {}, let args_node = Node {
_ => return Err("Record keys must be keywords (syntactically)".to_string()), identity: identity.clone(),
} kind: UntypedKind::Tuple { elements },
ty: (),
if *self.peek() == TokenKind::RightBrace { };
return Err("Record literal must have even number of forms".to_string());
} Ok(Node {
let val_node = self.parse_expression()?; identity,
kind: UntypedKind::Call {
fields.push((key_node, val_node)); callee: Box::new(callee),
} args: Box::new(args_node),
self.expect(TokenKind::RightBrace)?; },
ty: (),
Ok(Node { })
identity: Rc::new(NodeIdentity { location: token.location }), }
kind: UntypedKind::Record { fields },
ty: (), fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
}) let token = self.advance()?;
} let mut elements = Vec::new();
fn expect(&mut self, kind: TokenKind) -> Result<(), String> { while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
let token = self.advance()?; let expr = self.parse_expression()?;
if token.kind == kind { elements.push(expr);
Ok(()) }
} else {
Err(format!("Expected {:?}, but found {:?} at {:?}", kind, token.kind, token.location)) self.expect(TokenKind::RightBracket)?;
}
} Ok(Node {
identity: Rc::new(NodeIdentity {
fn make_id_node(&self, name: &str, identity: Identity) -> Node<UntypedKind> { location: token.location,
Node { }),
identity, kind: UntypedKind::Tuple { elements },
kind: UntypedKind::Identifier(Symbol::from(name)), ty: (),
ty: (), })
} }
}
} fn parse_record_literal(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?;
let mut fields = Vec::new();
while *self.peek() != TokenKind::RightBrace {
if *self.peek() == TokenKind::EOF {
return Err("Unexpected EOF in record literal".to_string());
}
let key_node = self.parse_expression()?;
// We check for keyword kind here (syntactically) to avoid ambiguity, but
// strictly we could allow any expression and check at runtime.
// Delphi enforces keywords. We can do minimal check here.
match &key_node.kind {
UntypedKind::Constant(Value::Keyword(_)) => {}
_ => return Err("Record keys must be keywords (syntactically)".to_string()),
}
if *self.peek() == TokenKind::RightBrace {
return Err("Record literal must have even number of forms".to_string());
}
let val_node = self.parse_expression()?;
fields.push((key_node, val_node));
}
self.expect(TokenKind::RightBrace)?;
Ok(Node {
identity: Rc::new(NodeIdentity {
location: token.location,
}),
kind: UntypedKind::Record { fields },
ty: (),
})
}
fn expect(&mut self, kind: TokenKind) -> Result<(), String> {
let token = self.advance()?;
if token.kind == kind {
Ok(())
} else {
Err(format!(
"Expected {:?}, but found {:?} at {:?}",
kind, token.kind, token.location
))
}
}
fn make_id_node(&self, name: &str, identity: Identity) -> Node<UntypedKind> {
Node {
identity,
kind: UntypedKind::Identifier(Symbol::from(name)),
ty: (),
}
}
}
+473 -334
View File
@@ -1,334 +1,473 @@
use std::rc::Rc; use crate::ast::environment::Environment;
use crate::ast::types::{Value, StaticType, Signature}; use crate::ast::types::{Signature, StaticType, Value};
use crate::ast::environment::Environment; use std::rc::Rc;
pub fn register(env: &Environment) {
pub fn register(env: &Environment) { register_constants(env);
register_constants(env); register_arithmetic(env);
register_arithmetic(env); register_comparison(env);
register_comparison(env); register_logic(env);
register_logic(env); }
}
fn register_constants(env: &Environment) {
fn register_constants(env: &Environment) { // True/False are keywords or literals in parser, but could be exposed as constants too if needed.
// True/False are keywords or literals in parser, but could be exposed as constants too if needed. // In Delphi RTL: CFalse, CTrue, CNaN
// In Delphi RTL: CFalse, CTrue, CNaN
// We register NaN as a value, not a function.
// We register NaN as a value, not a function. env.register_constant("NaN", StaticType::Float, Value::Float(f64::NAN));
env.register_constant("NaN", StaticType::Float, Value::Float(f64::NAN)); env.register_constant("true", StaticType::Bool, Value::Bool(true));
env.register_constant("true", StaticType::Bool, Value::Bool(true)); env.register_constant("false", StaticType::Bool, Value::Bool(false));
env.register_constant("false", StaticType::Bool, Value::Bool(false)); }
}
fn register_arithmetic(env: &Environment) {
fn register_arithmetic(env: &Environment) { // --- Add (+) ---
// --- Add (+) --- let add_ty = StaticType::FunctionOverloads(vec![
let add_ty = StaticType::FunctionOverloads(vec![ Signature {
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, ret: StaticType::Int,
Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text }, },
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, Signature {
]); params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
env.register_native("+", add_ty, true, |args| { ret: StaticType::Float,
if args.len() == 2 { },
match (&args[0], &args[1]) { Signature {
(Value::Int(a), Value::Int(b)) => Value::Int(a + b), params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]),
(Value::Float(a), Value::Float(b)) => Value::Float(a + b), ret: StaticType::Text,
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b), },
(Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64), Signature {
(Value::Text(a), Value::Text(b)) => { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]),
let mut res = a.to_string(); ret: StaticType::DateTime,
res.push_str(b); },
Value::Text(Rc::from(res)) ]);
}, env.register_native("+", add_ty, true, |args| {
(Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms), if args.len() == 2 {
_ => Value::Void, match (&args[0], &args[1]) {
} (Value::Int(a), Value::Int(b)) => Value::Int(a + b),
} else { (Value::Float(a), Value::Float(b)) => Value::Float(a + b),
// Variadic sum (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b),
let mut acc = 0.0; (Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64),
for arg in args { (Value::Text(a), Value::Text(b)) => {
if let Value::Int(i) = arg { acc += i as f64; } let mut res = a.to_string();
else if let Value::Float(f) = arg { acc += f; } res.push_str(b);
} Value::Text(Rc::from(res))
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } }
} (Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms),
}); _ => Value::Void,
}
// --- Subtract (-) --- } else {
let sub_ty = StaticType::FunctionOverloads(vec![ // Variadic sum
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, let mut acc = 0.0;
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, for arg in args {
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, // Negation if let Value::Int(i) = arg {
Signature { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float }, acc += i as f64;
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int }, } else if let Value::Float(f) = arg {
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, acc += f;
]); }
env.register_native("-", sub_ty, true, |args| { }
if args.is_empty() { return Value::Void; } if acc.fract() == 0.0 {
if args.len() == 1 { Value::Int(acc as i64)
return match args[0] { } else {
Value::Int(i) => Value::Int(-i), Value::Float(acc)
Value::Float(f) => Value::Float(-f), }
_ => Value::Void, }
}; });
}
if args.len() == 2 { // --- Subtract (-) ---
return match (&args[0], &args[1]) { let sub_ty = StaticType::FunctionOverloads(vec![
(Value::Int(a), Value::Int(b)) => Value::Int(a - b), Signature {
(Value::Float(a), Value::Float(b)) => Value::Float(a - b), params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b), ret: StaticType::Int,
(Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64), },
(Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b), Signature {
(Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b), params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
_ => Value::Void, ret: StaticType::Float,
}; },
} Signature {
// Variadic sub params: StaticType::Tuple(vec![StaticType::Int]),
let mut acc = match args[0] { ret: StaticType::Int,
Value::Int(i) => i as f64, }, // Negation
Value::Float(f) => f, Signature {
_ => return Value::Void, params: StaticType::Tuple(vec![StaticType::Float]),
}; ret: StaticType::Float,
for arg in &args[1..] { },
if let Value::Int(i) = arg { acc -= *i as f64; } Signature {
else if let Value::Float(f) = arg { acc -= f; } params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]),
} ret: StaticType::Int,
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } },
}); Signature {
params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]),
// --- Multiply (*) --- ret: StaticType::DateTime,
let mul_ty = StaticType::FunctionOverloads(vec![ },
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, ]);
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, env.register_native("-", sub_ty, true, |args| {
]); if args.is_empty() {
env.register_native("*", mul_ty, true, |args| { return Value::Void;
if args.len() == 2 { }
match (&args[0], &args[1]) { if args.len() == 1 {
(Value::Int(a), Value::Int(b)) => Value::Int(a * b), return match args[0] {
(Value::Float(a), Value::Float(b)) => Value::Float(a * b), Value::Int(i) => Value::Int(-i),
(Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b), Value::Float(f) => Value::Float(-f),
(Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64), _ => Value::Void,
_ => Value::Void, };
} }
} else { if args.len() == 2 {
let mut acc = 1.0; return match (&args[0], &args[1]) {
for arg in args { (Value::Int(a), Value::Int(b)) => Value::Int(a - b),
if let Value::Int(i) = arg { acc *= i as f64; } (Value::Float(a), Value::Float(b)) => Value::Float(a - b),
else if let Value::Float(f) = arg { acc *= f; } (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b),
} (Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64),
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } (Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b),
} (Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b),
}); _ => Value::Void,
};
// --- Divide (/) --- }
let div_ty = StaticType::FunctionOverloads(vec![ // Variadic sub
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float }, let mut acc = match args[0] {
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, Value::Int(i) => i as f64,
]); Value::Float(f) => f,
env.register_native("/", div_ty, true, |args| { _ => return Value::Void,
if args.len() != 2 { return Value::Void; } };
let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void }; for arg in &args[1..] {
let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void }; if let Value::Int(i) = arg {
if b == 0.0 { Value::Float(f64::NAN) } else { Value::Float(a / b) } acc -= *i as f64;
}); } else if let Value::Float(f) = arg {
acc -= f;
// --- Integer Divide (//) --- }
let int_div_ty = StaticType::Function(Box::new( }
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } if acc.fract() == 0.0 {
)); Value::Int(acc as i64)
env.register_native("//", int_div_ty, true, |args| { } else {
if args.len() != 2 { return Value::Void; } Value::Float(acc)
match (&args[0], &args[1]) { }
(Value::Int(a), Value::Int(b)) => { });
if *b == 0 { Value::Void } else { Value::Int(a / b) }
}, // --- Multiply (*) ---
// Also allow float truncation? Delphi RTL says IntDivide takes DataValue and returns DataValue. let mul_ty = StaticType::FunctionOverloads(vec![
// Usually div is for integers. Let's stick to Int. Signature {
_ => Value::Void, params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
} ret: StaticType::Int,
}); },
Signature {
// --- Modulus (%) --- params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
let mod_ty = StaticType::Function(Box::new( ret: StaticType::Float,
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } },
)); ]);
env.register_native("%", mod_ty, true, |args| { env.register_native("*", mul_ty, true, |args| {
if args.len() != 2 { return Value::Void; } if args.len() == 2 {
match (&args[0], &args[1]) { match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => { (Value::Int(a), Value::Int(b)) => Value::Int(a * b),
if *b == 0 { Value::Void } else { Value::Int(a % b) } (Value::Float(a), Value::Float(b)) => Value::Float(a * b),
}, (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b),
_ => Value::Void, (Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64),
} _ => Value::Void,
}); }
} } else {
let mut acc = 1.0;
fn register_comparison(env: &Environment) { for arg in args {
let cmp_ty = StaticType::FunctionOverloads(vec![ if let Value::Int(i) = arg {
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Bool }, acc *= i as f64;
Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Bool }, } else if let Value::Float(f) = arg {
Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Bool }, acc *= f;
]); }
}
// --- Greater Than (>) --- if acc.fract() == 0.0 {
env.register_native(">", cmp_ty.clone(), true, |args| { Value::Int(acc as i64)
if args.len() != 2 { return Value::Void; } } else {
match (&args[0], &args[1]) { Value::Float(acc)
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b), }
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), }
(Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)), });
(Value::Float(a), Value::Float(b)) => Value::Bool(a > b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b), // --- Divide (/) ---
_ => Value::Bool(false), let div_ty = StaticType::FunctionOverloads(vec![
} Signature {
}); params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Float,
// --- Less Than (<) --- },
env.register_native("<", cmp_ty.clone(), true, |args| { Signature {
if args.len() != 2 { return Value::Void; } params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
match (&args[0], &args[1]) { ret: StaticType::Float,
(Value::Int(a), Value::Int(b)) => Value::Bool(a < b), },
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b), ]);
(Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)), env.register_native("/", div_ty, true, |args| {
(Value::Float(a), Value::Float(b)) => Value::Bool(a < b), if args.len() != 2 {
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b), return Value::Void;
_ => Value::Bool(false), }
} let a = match args[0] {
}); Value::Int(i) => i as f64,
Value::Float(f) => f,
// --- Greater Or Equal (>=) --- _ => return Value::Void,
env.register_native(">=", cmp_ty.clone(), true, |args| { };
if args.len() != 2 { return Value::Void; } let b = match args[1] {
match (&args[0], &args[1]) { Value::Int(i) => i as f64,
(Value::Int(a), Value::Int(b)) => Value::Bool(a >= b), Value::Float(f) => f,
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) >= *b), _ => return Value::Void,
(Value::Float(a), Value::Int(b)) => Value::Bool(*a >= (*b as f64)), };
(Value::Float(a), Value::Float(b)) => Value::Bool(a >= b), if b == 0.0 {
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a >= b), Value::Float(f64::NAN)
_ => Value::Bool(false), } else {
} Value::Float(a / b)
}); }
});
// --- Less Or Equal (<=) ---
env.register_native("<=", cmp_ty.clone(), true, |args| { // --- Integer Divide (//) ---
if args.len() != 2 { return Value::Void; } let int_div_ty = StaticType::Function(Box::new(Signature {
match (&args[0], &args[1]) { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
(Value::Int(a), Value::Int(b)) => Value::Bool(a <= b), ret: StaticType::Int,
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) <= *b), }));
(Value::Float(a), Value::Int(b)) => Value::Bool(*a <= (*b as f64)), env.register_native("//", int_div_ty, true, |args| {
(Value::Float(a), Value::Float(b)) => Value::Bool(a <= b), if args.len() != 2 {
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a <= b), return Value::Void;
_ => Value::Bool(false), }
} match (&args[0], &args[1]) {
}); (Value::Int(a), Value::Int(b)) => {
if *b == 0 {
// --- Equal (=) --- Value::Void
let eq_ty = StaticType::Function(Box::new( } else {
Signature { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool } Value::Int(a / b)
)); }
env.register_native("=", eq_ty.clone(), true, |args| { }
if args.len() != 2 { return Value::Void; } // Also allow float truncation? Delphi RTL says IntDivide takes DataValue and returns DataValue.
// Simple equality check. // Usually div is for integers. Let's stick to Int.
// Note: Floating point equality is tricky, but we follow standard behavior for now. _ => Value::Void,
match (&args[0], &args[1]) { }
(Value::Int(a), Value::Int(b)) => Value::Bool(a == b), });
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() < f64::EPSILON),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() < f64::EPSILON), // --- Modulus (%) ---
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() < f64::EPSILON), let mod_ty = StaticType::Function(Box::new(Signature {
(Value::Text(a), Value::Text(b)) => Value::Bool(a == b), params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a == b), ret: StaticType::Int,
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a == b), }));
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a == b), env.register_native("%", mod_ty, true, |args| {
(Value::Void, Value::Void) => Value::Bool(true), if args.len() != 2 {
_ => Value::Bool(false), return Value::Void;
} }
}); match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => {
// --- Not Equal (<>) --- if *b == 0 {
env.register_native("<>", eq_ty, true, |args| { Value::Void
if args.len() != 2 { return Value::Void; } } else {
match (&args[0], &args[1]) { Value::Int(a % b)
(Value::Int(a), Value::Int(b)) => Value::Bool(a != b), }
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() >= f64::EPSILON), }
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() >= f64::EPSILON), _ => Value::Void,
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() >= f64::EPSILON), }
(Value::Text(a), Value::Text(b)) => Value::Bool(a != b), });
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a != b), }
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a != b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a != b), fn register_comparison(env: &Environment) {
(Value::Void, Value::Void) => Value::Bool(false), let cmp_ty = StaticType::FunctionOverloads(vec![
_ => Value::Bool(true), Signature {
} params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
}); ret: StaticType::Bool,
} },
Signature {
fn register_logic(env: &Environment) { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]),
// --- Not (not) --- ret: StaticType::Bool,
let not_ty = StaticType::FunctionOverloads(vec![ },
Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool }, Signature {
Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]),
]); ret: StaticType::Bool,
env.register_native("not", not_ty, true, |args| { },
if args.len() != 1 { return Value::Void; } ]);
match &args[0] {
Value::Bool(b) => Value::Bool(!b), // --- Greater Than (>) ---
Value::Int(i) => Value::Int(!i), // Bitwise NOT env.register_native(">", cmp_ty.clone(), true, |args| {
_ => Value::Void, if args.len() != 2 {
} return Value::Void;
}); }
match (&args[0], &args[1]) {
// --- And (and) --- (Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
let logic_op_ty = StaticType::FunctionOverloads(vec![ (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b),
Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool }, (Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)),
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, (Value::Float(a), Value::Float(b)) => Value::Bool(a > b),
]); (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b),
env.register_native("and", logic_op_ty.clone(), true, |args| { _ => Value::Bool(false),
if args.len() != 2 { return Value::Void; } }
match (&args[0], &args[1]) { });
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a & b), // --- Less Than (<) ---
_ => Value::Void, env.register_native("<", cmp_ty.clone(), true, |args| {
} if args.len() != 2 {
}); return Value::Void;
}
// --- Or (or) --- match (&args[0], &args[1]) {
env.register_native("or", logic_op_ty.clone(), true, |args| { (Value::Int(a), Value::Int(b)) => Value::Bool(a < b),
if args.len() != 2 { return Value::Void; } (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b),
match (&args[0], &args[1]) { (Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b), (Value::Float(a), Value::Float(b)) => Value::Bool(a < b),
(Value::Int(a), Value::Int(b)) => Value::Int(a | b), (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b),
_ => Value::Void, _ => Value::Bool(false),
} }
}); });
// --- Xor (xor) --- // --- Greater Or Equal (>=) ---
env.register_native("xor", logic_op_ty.clone(), true, |args| { env.register_native(">=", cmp_ty.clone(), true, |args| {
if args.len() != 2 { return Value::Void; } if args.len() != 2 {
match (&args[0], &args[1]) { return Value::Void;
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b), }
(Value::Int(a), Value::Int(b)) => Value::Int(a ^ b), match (&args[0], &args[1]) {
_ => Value::Void, (Value::Int(a), Value::Int(b)) => Value::Bool(a >= b),
} (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) >= *b),
}); (Value::Float(a), Value::Int(b)) => Value::Bool(*a >= (*b as f64)),
(Value::Float(a), Value::Float(b)) => Value::Bool(a >= b),
// --- Shift Left (<<) --- (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a >= b),
let shift_ty = StaticType::Function(Box::new( _ => Value::Bool(false),
Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } }
)); });
env.register_native("<<", shift_ty.clone(), true, |args| {
if args.len() != 2 { return Value::Void; } // --- Less Or Equal (<=) ---
match (&args[0], &args[1]) { env.register_native("<=", cmp_ty.clone(), true, |args| {
(Value::Int(a), Value::Int(b)) => Value::Int(a << b), if args.len() != 2 {
_ => Value::Void, return Value::Void;
} }
}); match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a <= b),
// --- Shift Right (>>) --- (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) <= *b),
env.register_native(">>", shift_ty, true, |args| { (Value::Float(a), Value::Int(b)) => Value::Bool(*a <= (*b as f64)),
if args.len() != 2 { return Value::Void; } (Value::Float(a), Value::Float(b)) => Value::Bool(a <= b),
match (&args[0], &args[1]) { (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a <= b),
(Value::Int(a), Value::Int(b)) => Value::Int(a >> b), _ => Value::Bool(false),
_ => Value::Void, }
} });
});
} // --- Equal (=) ---
let eq_ty = StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]),
ret: StaticType::Bool,
}));
env.register_native("=", eq_ty.clone(), true, |args| {
if args.len() != 2 {
return Value::Void;
}
// Simple equality check.
// Note: Floating point equality is tricky, but we follow standard behavior for now.
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a == b),
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() < f64::EPSILON),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() < f64::EPSILON),
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() < f64::EPSILON),
(Value::Text(a), Value::Text(b)) => Value::Bool(a == b),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a == b),
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a == b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a == b),
(Value::Void, Value::Void) => Value::Bool(true),
_ => Value::Bool(false),
}
});
// --- Not Equal (<>) ---
env.register_native("<>", eq_ty, true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a != b),
(Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() >= f64::EPSILON),
(Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() >= f64::EPSILON),
(Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() >= f64::EPSILON),
(Value::Text(a), Value::Text(b)) => Value::Bool(a != b),
(Value::Bool(a), Value::Bool(b)) => Value::Bool(a != b),
(Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a != b),
(Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a != b),
(Value::Void, Value::Void) => Value::Bool(false),
_ => Value::Bool(true),
}
});
}
fn register_logic(env: &Environment) {
// --- Not (not) ---
let not_ty = StaticType::FunctionOverloads(vec![
Signature {
params: StaticType::Tuple(vec![StaticType::Bool]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int]),
ret: StaticType::Int,
},
]);
env.register_native("not", not_ty, true, |args| {
if args.len() != 1 {
return Value::Void;
}
match &args[0] {
Value::Bool(b) => Value::Bool(!b),
Value::Int(i) => Value::Int(!i), // Bitwise NOT
_ => Value::Void,
}
});
// --- And (and) ---
let logic_op_ty = StaticType::FunctionOverloads(vec![
Signature {
params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]),
ret: StaticType::Bool,
},
Signature {
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
},
]);
env.register_native("and", logic_op_ty.clone(), true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a & b),
_ => Value::Void,
}
});
// --- Or (or) ---
env.register_native("or", logic_op_ty.clone(), true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a | b),
_ => Value::Void,
}
});
// --- Xor (xor) ---
env.register_native("xor", logic_op_ty.clone(), true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b),
(Value::Int(a), Value::Int(b)) => Value::Int(a ^ b),
_ => Value::Void,
}
});
// --- Shift Left (<<) ---
let shift_ty = StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]),
ret: StaticType::Int,
}));
env.register_native("<<", shift_ty.clone(), true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a << b),
_ => Value::Void,
}
});
// --- Shift Right (>>) ---
env.register_native(">>", shift_ty, true, |args| {
if args.len() != 2 {
return Value::Void;
}
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Int(a >> b),
_ => Value::Void,
}
});
}
+29 -25
View File
@@ -1,25 +1,29 @@
use crate::ast::types::{Value, StaticType, Signature}; use crate::ast::environment::Environment;
use crate::ast::environment::Environment; use crate::ast::types::{Signature, StaticType, Value};
use chrono::{NaiveDate, NaiveDateTime}; use chrono::{NaiveDate, NaiveDateTime};
pub fn register(env: &Environment) { pub fn register(env: &Environment) {
let date_ty = StaticType::Function(Box::new(Signature { let date_ty = StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Text]), params: StaticType::Tuple(vec![StaticType::Text]),
ret: StaticType::DateTime ret: StaticType::DateTime,
})); }));
env.register_native("date", date_ty, true, |args| { env.register_native("date", date_ty, true, |args| {
if let Value::Text(s) = &args[0] { if let Value::Text(s) = &args[0] {
// Try parse YYYY-MM-DD // Try parse YYYY-MM-DD
if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
let ts = dt.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis(); let ts = dt
return Value::DateTime(ts); .and_hms_opt(0, 0, 0)
} .unwrap()
// Try parse YYYY-MM-DD HH:MM:SS .and_utc()
if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { .timestamp_millis();
return Value::DateTime(dt.and_utc().timestamp_millis()); return Value::DateTime(ts);
} }
} // Try parse YYYY-MM-DD HH:MM:SS
Value::Void if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
}); return Value::DateTime(dt.and_utc().timestamp_millis());
} }
}
Value::Void
});
}
+116 -108
View File
@@ -1,108 +1,116 @@
use std::rc::Rc; use crate::ast::types::{StaticType, Value};
use crate::ast::types::{Value, StaticType}; use std::rc::Rc;
/// Looks up a specialized intrinsic function for the given operator and argument types. /// Looks up a specialized intrinsic function for the given operator and argument types.
/// Returns (Executable Value, Return Type) if a fast-path exists. /// Returns (Executable Value, Return Type) if a fast-path exists.
pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> { pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> {
match (name, args) { match (name, args) {
// --- Integer Arithmetic --- // --- Integer Arithmetic ---
("+", [StaticType::Int, StaticType::Int]) => Some(( ("+", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| { Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a + b) Value::Int(a + b)
} else { } else {
Value::Int(0) // Should not happen if type checker works Value::Int(0) // Should not happen if type checker works
} }
})), })),
StaticType::Int StaticType::Int,
)), )),
("-", [StaticType::Int, StaticType::Int]) => Some(( ("-", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| { Value::Function(Rc::new(|args| {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
Value::Int(a - b) Value::Int(a - b)
} else { } else {
Value::Int(0) Value::Int(0)
} }
})), })),
StaticType::Int StaticType::Int,
)), )),
("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some(( ("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some((
// Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary) // Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary)
// MyC's core.rs supports variadic subtraction. // MyC's core.rs supports variadic subtraction.
Value::Function(Rc::new(|args| { Value::Function(Rc::new(|args| {
let a = match args[0] { Value::Int(i) => i, _ => 0 }; let a = match args[0] {
let b = match args[1] { Value::Int(i) => i, _ => 0 }; Value::Int(i) => i,
let c = match args[2] { Value::Int(i) => i, _ => 0 }; _ => 0,
Value::Int(a - b - c) };
})), let b = match args[1] {
StaticType::Int Value::Int(i) => i,
)), _ => 0,
("*", [StaticType::Int, StaticType::Int]) => Some(( };
Value::Function(Rc::new(|args| { let c = match args[2] {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Int(i) => i,
Value::Int(a * b) _ => 0,
} else { };
Value::Int(0) Value::Int(a - b - c)
} })),
})), StaticType::Int,
StaticType::Int )),
)), ("*", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
// --- Integer Comparison --- if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
("<=", [StaticType::Int, StaticType::Int]) => Some(( Value::Int(a * b)
Value::Function(Rc::new(|args| { } else {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Int(0)
Value::Bool(a <= b) }
} else { })),
Value::Bool(false) StaticType::Int,
} )),
})),
StaticType::Bool // --- Integer Comparison ---
)), ("<=", [StaticType::Int, StaticType::Int]) => Some((
("<", [StaticType::Int, StaticType::Int]) => Some(( Value::Function(Rc::new(|args| {
Value::Function(Rc::new(|args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a <= b)
Value::Bool(a < b) } else {
} else { Value::Bool(false)
Value::Bool(false) }
} })),
})), StaticType::Bool,
StaticType::Bool )),
)), ("<", [StaticType::Int, StaticType::Int]) => Some((
(">", [StaticType::Int, StaticType::Int]) => Some(( Value::Function(Rc::new(|args| {
Value::Function(Rc::new(|args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a < b)
Value::Bool(a > b) } else {
} else { Value::Bool(false)
Value::Bool(false) }
} })),
})), StaticType::Bool,
StaticType::Bool )),
)), (">", [StaticType::Int, StaticType::Int]) => Some((
(">=", [StaticType::Int, StaticType::Int]) => Some(( Value::Function(Rc::new(|args| {
Value::Function(Rc::new(|args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a > b)
Value::Bool(a >= b) } else {
} else { Value::Bool(false)
Value::Bool(false) }
} })),
})), StaticType::Bool,
StaticType::Bool )),
)), (">=", [StaticType::Int, StaticType::Int]) => Some((
("=", [StaticType::Int, StaticType::Int]) => Some(( Value::Function(Rc::new(|args| {
Value::Function(Rc::new(|args| { if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { Value::Bool(a >= b)
Value::Bool(a == b) } else {
} else { Value::Bool(false)
Value::Bool(false) }
} })),
})), StaticType::Bool,
StaticType::Bool )),
)), ("=", [StaticType::Int, StaticType::Int]) => Some((
Value::Function(Rc::new(|args| {
// --- Constant Unary for -1 (decrement optimization) --- if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) {
// Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]). Value::Bool(a == b)
} else {
_ => None Value::Bool(false)
} }
} })),
StaticType::Bool,
)),
// --- Constant Unary for -1 (decrement optimization) ---
// Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]).
_ => None,
}
}
+11 -11
View File
@@ -1,11 +1,11 @@
pub mod core; pub mod core;
pub mod datetime; pub mod datetime;
pub mod type_registry; pub mod intrinsics;
pub mod intrinsics; pub mod type_registry;
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
pub fn register(env: &Environment) { pub fn register(env: &Environment) {
core::register(env); core::register(env);
datetime::register(env); datetime::register(env);
} }
+276 -249
View File
@@ -1,249 +1,276 @@
use std::collections::HashMap; use crate::ast::types::{Keyword, Signature, StaticType, Value};
use std::rc::Rc; use std::any::TypeId;
use std::any::{TypeId}; use std::collections::HashMap;
use crate::ast::types::{Value, StaticType, Keyword, Signature}; use std::rc::Rc;
/// Represents a Rust type that can be exposed to the script environment. /// Represents a Rust type that can be exposed to the script environment.
pub trait Scriptable: 'static + Sized { pub trait Scriptable: 'static + Sized {
/// Returns the name of the type for debugging/AST. /// Returns the name of the type for debugging/AST.
fn type_name() -> &'static str; fn type_name() -> &'static str;
/// Returns the static type definition (methods, properties) for the AST. /// Returns the static type definition (methods, properties) for the AST.
fn static_type() -> StaticType; fn static_type() -> StaticType;
/// Wraps the instance into a Value (usually a Record of closures). /// Wraps the instance into a Value (usually a Record of closures).
fn wrap(self) -> Value; fn wrap(self) -> Value;
} }
/// A registry for tracking registered types and their static definitions. /// A registry for tracking registered types and their static definitions.
pub struct TypeRegistry { pub struct TypeRegistry {
known_types: HashMap<TypeId, StaticType>, known_types: HashMap<TypeId, StaticType>,
} }
impl Default for TypeRegistry { impl Default for TypeRegistry {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl TypeRegistry { impl TypeRegistry {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
known_types: HashMap::new(), known_types: HashMap::new(),
} }
} }
/// Registers a type T. Equivalent to `RegisterType<T>` in Delphi. /// Registers a type T. Equivalent to `RegisterType<T>` in Delphi.
pub fn register<T: Scriptable>(&mut self) { pub fn register<T: Scriptable>(&mut self) {
let id = TypeId::of::<T>(); let id = TypeId::of::<T>();
self.known_types.entry(id).or_insert_with(T::static_type); self.known_types.entry(id).or_insert_with(T::static_type);
} }
/// Resolves the static type for a Rust type. /// Resolves the static type for a Rust type.
pub fn resolve_type<T: 'static>(&self) -> StaticType { pub fn resolve_type<T: 'static>(&self) -> StaticType {
self.known_types.get(&TypeId::of::<T>()).cloned().unwrap_or(StaticType::Any) self.known_types
} .get(&TypeId::of::<T>())
.cloned()
/// Creates a factory function value that can be bound in the environment. .unwrap_or(StaticType::Any)
/// }
/// # Arguments
/// * `factory_func` - A Rust closure that takes script arguments and returns a Result<T, String>. /// Creates a factory function value that can be bound in the environment.
pub fn create_factory<T, F>( ///
factory_func: F /// # Arguments
) -> Value /// * `factory_func` - A Rust closure that takes script arguments and returns a Result<T, String>.
where pub fn create_factory<T, F>(factory_func: F) -> Value
T: Scriptable, where
F: Fn(Vec<Value>) -> Result<T, String> + 'static T: Scriptable,
{ F: Fn(Vec<Value>) -> Result<T, String> + 'static,
// The factory is a script function that calls the Rust factory, gets T, then wraps it. {
let closure = move |args: Vec<Value>| -> Value { // The factory is a script function that calls the Rust factory, gets T, then wraps it.
match factory_func(args) { let closure = move |args: Vec<Value>| -> Value {
Ok(instance) => instance.wrap(), match factory_func(args) {
Err(msg) => { Ok(instance) => instance.wrap(),
// In a real system, we'd propagate this error. For now, panic or return Void. Err(msg) => {
// Delphi returns Void if nil, but raises exception on error. // In a real system, we'd propagate this error. For now, panic or return Void.
// Since Value doesn't have Error, we panic to stop execution. // Delphi returns Void if nil, but raises exception on error.
panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg); // Since Value doesn't have Error, we panic to stop execution.
} panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg);
} }
}; }
Value::Function(Rc::new(closure)) };
} Value::Function(Rc::new(closure))
} }
}
/// Helper to build the shadow record for an instance.
/// This is used inside `Scriptable::wrap`. /// Helper to build the shadow record for an instance.
pub struct RecordBuilder { /// This is used inside `Scriptable::wrap`.
fields: Vec<(Keyword, Value)>, pub struct RecordBuilder {
} fields: Vec<(Keyword, Value)>,
}
impl Default for RecordBuilder {
fn default() -> Self { impl Default for RecordBuilder {
Self::new() fn default() -> Self {
} Self::new()
} }
}
impl RecordBuilder {
pub fn new() -> Self { impl RecordBuilder {
Self { pub fn new() -> Self {
fields: Vec::new(), Self { fields: Vec::new() }
} }
}
pub fn method<F>(mut self, name: &str, func: F) -> Self
pub fn method<F>(mut self, name: &str, func: F) -> Self where
where F: Fn(Vec<Value>) -> Value + 'static F: Fn(Vec<Value>) -> Value + 'static,
{ {
let key = Keyword::intern(name); let key = Keyword::intern(name);
self.fields.push((key, Value::Function(Rc::new(func)))); self.fields.push((key, Value::Function(Rc::new(func))));
self self
} }
// Helper for methods that return Result (propagating panics for now) // Helper for methods that return Result (propagating panics for now)
pub fn method_checked<F>(self, name: &str, func: F) -> Self pub fn method_checked<F>(self, name: &str, func: F) -> Self
where F: Fn(Vec<Value>) -> Result<Value, String> + 'static where
{ F: Fn(Vec<Value>) -> Result<Value, String> + 'static,
let closure = move |args: Vec<Value>| { {
match func(args) { let closure = move |args: Vec<Value>| match func(args) {
Ok(v) => v, Ok(v) => v,
Err(e) => panic!("Method call error: {}", e), Err(e) => panic!("Method call error: {}", e),
} };
}; self.method(name, closure)
self.method(name, closure) }
}
pub fn build(self) -> Value {
pub fn build(self) -> Value { let mut keys = Vec::with_capacity(self.fields.len());
let mut keys = Vec::with_capacity(self.fields.len()); let mut values = Vec::with_capacity(self.fields.len());
let mut values = Vec::with_capacity(self.fields.len()); for (k, v) in self.fields {
for (k, v) in self.fields { keys.push(k);
keys.push(k); values.push(v);
values.push(v); }
} Value::make_record(keys, values)
Value::make_record(keys, values) }
} }
}
/// Helper to build the StaticType definition.
/// Helper to build the StaticType definition. pub struct TypeBuilder {
pub struct TypeBuilder { fields: Vec<(Keyword, StaticType)>,
fields: Vec<(Keyword, StaticType)>, }
}
impl Default for TypeBuilder {
impl Default for TypeBuilder { fn default() -> Self {
fn default() -> Self { Self::new()
Self::new() }
} }
}
impl TypeBuilder {
impl TypeBuilder { pub fn new() -> Self {
pub fn new() -> Self { Self { fields: Vec::new() }
Self { }
fields: Vec::new(),
} pub fn method(mut self, name: &str, params: Vec<StaticType>, ret: StaticType) -> Self {
} let key = Keyword::intern(name);
let sig = StaticType::Function(Box::new(Signature {
pub fn method(mut self, name: &str, params: Vec<StaticType>, ret: StaticType) -> Self { params: StaticType::Tuple(params),
let key = Keyword::intern(name); ret,
let sig = StaticType::Function(Box::new(Signature { params: StaticType::Tuple(params), ret })); }));
self.fields.push((key, sig)); self.fields.push((key, sig));
self self
} }
pub fn build(self) -> StaticType { pub fn build(self) -> StaticType {
StaticType::Record(Rc::new(self.fields)) StaticType::Record(Rc::new(self.fields))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::types::{Value, StaticType}; use crate::ast::types::{StaticType, Value};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Person { struct Person {
name: String, name: String,
age: u32, age: u32,
} }
impl Scriptable for Person { impl Scriptable for Person {
fn type_name() -> &'static str { "Person" } fn type_name() -> &'static str {
"Person"
fn static_type() -> StaticType { }
TypeBuilder::new()
.method("greet", vec![], StaticType::Text) fn static_type() -> StaticType {
.method("older", vec![], StaticType::Int) TypeBuilder::new()
.build() .method("greet", vec![], StaticType::Text)
} .method("older", vec![], StaticType::Int)
.build()
fn wrap(self) -> Value { }
let name = self.name.clone();
let age = self.age; fn wrap(self) -> Value {
let name = self.name.clone();
RecordBuilder::new() let age = self.age;
.method("greet", move |_| Value::Text(format!("Hello {}", name).into()))
.method("older", move |_| Value::Int((age + 1) as i64)) RecordBuilder::new()
.build() .method("greet", move |_| {
} Value::Text(format!("Hello {}", name).into())
} })
.method("older", move |_| Value::Int((age + 1) as i64))
#[test] .build()
fn test_register_and_wrap() { }
let mut registry = TypeRegistry::new(); }
registry.register::<Person>();
#[test]
let p = Person { name: "Alice".to_string(), age: 30 }; fn test_register_and_wrap() {
let wrapped = p.wrap(); let mut registry = TypeRegistry::new();
registry.register::<Person>();
// Check static type
let st = TypeRegistry::resolve_type::<Person>(&registry); let p = Person {
if let StaticType::Record(fields) = st { name: "Alice".to_string(),
assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet"))); age: 30,
} else { };
panic!("Expected Record type"); let wrapped = p.wrap();
}
// Check static type
// Check runtime behavior let st = TypeRegistry::resolve_type::<Person>(&registry);
if let Value::Record(r) = wrapped { if let StaticType::Record(fields) = st {
let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap(); assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet")));
let greet_fn = &r.values[idx]; } else {
panic!("Expected Record type");
if let Value::Function(f) = greet_fn { }
let res = f(vec![]);
if let Value::Text(s) = res { // Check runtime behavior
assert_eq!(&*s, "Hello Alice"); if let Value::Record(r) = wrapped {
} else { let idx = r
panic!("Expected Text result"); .keys
} .iter()
} else { .position(|k| *k == Keyword::intern("greet"))
panic!("Expected Function value for method"); .unwrap();
} let greet_fn = &r.values[idx];
} else {
panic!("Expected Record value"); if let Value::Function(f) = greet_fn {
} let res = f(vec![]);
} if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Alice");
#[test] } else {
fn test_factory() { panic!("Expected Text result");
let factory_val = TypeRegistry::create_factory(|args: Vec<Value>| { }
if args.len() != 2 { } else {
return Err("Expected 2 args".to_string()); panic!("Expected Function value for method");
} }
let name = match &args[0] { Value::Text(t) => t.to_string(), _ => return Err("Name must be text".to_string()) }; } else {
let age = match &args[1] { Value::Int(i) => *i as u32, _ => return Err("Age must be int".to_string()) }; panic!("Expected Record value");
Ok(Person { name, age }) }
}); }
if let Value::Function(f) = factory_val { #[test]
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]); fn test_factory() {
if let Value::Record(r) = instance { let factory_val = TypeRegistry::create_factory(|args: Vec<Value>| {
let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap(); if args.len() != 2 {
let greet_fn = &r.values[idx]; return Err("Expected 2 args".to_string());
}
if let Value::Function(gf) = greet_fn { let name = match &args[0] {
let res = gf(vec![]); Value::Text(t) => t.to_string(),
if let Value::Text(s) = res { _ => return Err("Name must be text".to_string()),
assert_eq!(&*s, "Hello Bob"); };
} else { panic!("Wrong return type"); } let age = match &args[1] {
} Value::Int(i) => *i as u32,
} else { panic!("Factory should return Record"); } _ => return Err("Age must be int".to_string()),
} else { panic!("Factory is not a function"); } };
} Ok(Person { name, age })
} });
if let Value::Function(f) = factory_val {
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]);
if let Value::Record(r) = instance {
let idx = r
.keys
.iter()
.position(|k| *k == Keyword::intern("greet"))
.unwrap();
let greet_fn = &r.values[idx];
if let Value::Function(gf) = greet_fn {
let res = gf(vec![]);
if let Value::Text(s) = res {
assert_eq!(&*s, "Hello Bob");
} else {
panic!("Wrong return type");
}
}
} else {
panic!("Factory should return Record");
}
} else {
panic!("Factory is not a function");
}
}
}
+392 -370
View File
@@ -1,370 +1,392 @@
use std::collections::HashMap; use chrono::{TimeZone, Utc};
use std::rc::Rc; use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::collections::HashMap;
use std::sync::OnceLock; use std::fmt;
use std::sync::Mutex; // Still needed for global keyword registry use std::rc::Rc;
use std::any::Any; use std::sync::Mutex; // Still needed for global keyword registry
use chrono::{TimeZone, Utc}; use std::sync::OnceLock;
/// Simple source location /// Simple source location
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SourceLocation { pub struct SourceLocation {
pub line: u32, pub line: u32,
pub col: u32, pub col: u32,
} }
/// Shared identity for nodes (Location, etc.) /// Shared identity for nodes (Location, etc.)
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeIdentity { pub struct NodeIdentity {
pub location: SourceLocation, pub location: SourceLocation,
} }
pub type Identity = Rc<NodeIdentity>; pub type Identity = Rc<NodeIdentity>;
/// Interned string identifier /// Interned string identifier
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Keyword(pub u32); pub struct Keyword(pub u32);
static KEYWORD_REGISTRY: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new(); static KEYWORD_REGISTRY: OnceLock<Mutex<HashMap<String, u32>>> = OnceLock::new();
static KEYWORD_REVERSE: OnceLock<Mutex<Vec<String>>> = OnceLock::new(); static KEYWORD_REVERSE: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
impl Keyword { impl Keyword {
pub fn intern(name: &str) -> Self { pub fn intern(name: &str) -> Self {
let mut reg = KEYWORD_REGISTRY.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap(); let mut reg = KEYWORD_REGISTRY
if let Some(&id) = reg.get(name) { .get_or_init(|| Mutex::new(HashMap::new()))
Keyword(id) .lock()
} else { .unwrap();
let mut rev = KEYWORD_REVERSE.get_or_init(|| Mutex::new(Vec::new())).lock().unwrap(); if let Some(&id) = reg.get(name) {
let id = rev.len() as u32; Keyword(id)
reg.insert(name.to_string(), id); } else {
rev.push(name.to_string()); let mut rev = KEYWORD_REVERSE
Keyword(id) .get_or_init(|| Mutex::new(Vec::new()))
} .lock()
} .unwrap();
let id = rev.len() as u32;
pub fn name(&self) -> String { reg.insert(name.to_string(), id);
let rev = KEYWORD_REVERSE.get_or_init(|| Mutex::new(Vec::new())).lock().unwrap(); rev.push(name.to_string());
rev[self.0 as usize].clone() Keyword(id)
} }
} }
/// Interface for custom objects (Closures, Series, Streams) pub fn name(&self) -> String {
pub trait Object: fmt::Debug { let rev = KEYWORD_REVERSE
fn type_name(&self) -> &'static str; .get_or_init(|| Mutex::new(Vec::new()))
fn as_any(&self) -> &dyn Any; .lock()
} .unwrap();
rev[self.0 as usize].clone()
/// A shared sequence of values, used by both Tuples and Records. }
pub type ValueList = Rc<Vec<Value>>; }
/// Internal storage for Records to allow sharing schema (keys) between instances. /// Interface for custom objects (Closures, Series, Streams)
#[derive(Debug, Clone, PartialEq)] pub trait Object: fmt::Debug {
pub struct RecordData { fn type_name(&self) -> &'static str;
/// Names for slots. fn as_any(&self) -> &dyn Any;
pub keys: Rc<Vec<Keyword>>, }
/// The actual values, potentially shared with a Tuple.
pub values: ValueList, /// A shared sequence of values, used by both Tuples and Records.
} pub type ValueList = Rc<Vec<Value>>;
/// Core data value in Myc Script (similar to TDataValue) /// Internal storage for Records to allow sharing schema (keys) between instances.
#[derive(Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum Value { pub struct RecordData {
Void, /// Names for slots.
Bool(bool), pub keys: Rc<Vec<Keyword>>,
Int(i64), /// The actual values, potentially shared with a Tuple.
Float(f64), pub values: ValueList,
DateTime(i64), }
Text(Rc<str>),
Keyword(Keyword), /// Core data value in Myc Script (similar to TDataValue)
Tuple(ValueList), #[derive(Clone)]
Record(Rc<RecordData>), pub enum Value {
Function(Rc<dyn Fn(Vec<Value>) -> Value>), Void,
Object(Rc<dyn Object>), // For compiled Closures and other opaque types Bool(bool),
Cell(Rc<RefCell<Value>>), // Boxed value for captures Int(i64),
TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // Internal: For TCO (Boxed to keep Value small) Float(f64),
} DateTime(i64),
Text(Rc<str>),
impl PartialEq for Value { Keyword(Keyword),
fn eq(&self, other: &Self) -> bool { Tuple(ValueList),
match (self, other) { Record(Rc<RecordData>),
(Value::Void, Value::Void) => true, Function(Rc<dyn Fn(Vec<Value>) -> Value>),
(Value::Bool(a), Value::Bool(b)) => a == b, Object(Rc<dyn Object>), // For compiled Closures and other opaque types
(Value::Int(a), Value::Int(b)) => a == b, Cell(Rc<RefCell<Value>>), // Boxed value for captures
(Value::Float(a), Value::Float(b)) => a == b, TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // Internal: For TCO (Boxed to keep Value small)
(Value::DateTime(a), Value::DateTime(b)) => a == b, }
(Value::Text(a), Value::Text(b)) => a == b,
(Value::Keyword(a), Value::Keyword(b)) => a == b, impl PartialEq for Value {
(Value::Tuple(a), Value::Tuple(b)) => a == b, fn eq(&self, other: &Self) -> bool {
(Value::Record(a), Value::Record(b)) => a == b, match (self, other) {
(Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), (Value::Void, Value::Void) => true,
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), (Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b), (Value::Int(a), Value::Int(b)) => a == b,
(Value::TailCallRequest(a), Value::TailCallRequest(b)) => { (Value::Float(a), Value::Float(b)) => a == b,
Rc::ptr_eq(&a.0, &b.0) && a.1 == b.1 (Value::DateTime(a), Value::DateTime(b)) => a == b,
} (Value::Text(a), Value::Text(b)) => a == b,
_ => false, (Value::Keyword(a), Value::Keyword(b)) => a == b,
} (Value::Tuple(a), Value::Tuple(b)) => a == b,
} (Value::Record(a), Value::Record(b)) => a == b,
} (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b),
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
#[derive(Debug, Clone, PartialEq, Eq, Hash)] (Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b),
pub struct Signature { (Value::TailCallRequest(a), Value::TailCallRequest(b)) => {
pub params: StaticType, Rc::ptr_eq(&a.0, &b.0) && a.1 == b.1
pub ret: StaticType, }
} _ => false,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] }
pub enum StaticType { }
Any,
Void, #[derive(Debug, Clone, PartialEq, Eq, Hash)]
Bool, pub struct Signature {
Int, pub params: StaticType,
Float, pub ret: StaticType,
DateTime, }
Text,
Keyword, #[derive(Debug, Clone, PartialEq, Eq, Hash)]
List(Box<StaticType>), // Legacy / Dynamic list pub enum StaticType {
Tuple(Vec<StaticType>), // Heterogeneous fixed-size Any,
Vector(Box<StaticType>, usize), // Homogeneous fixed-size Void,
Matrix(Box<StaticType>, Vec<usize>), // Multi-dimensional homogeneous Bool,
Record(Rc<Vec<(Keyword, StaticType)>>), Int,
Function(Box<Signature>), Float,
FunctionOverloads(Vec<Signature>), DateTime,
Object(&'static str), Text,
} Keyword,
List(Box<StaticType>), // Legacy / Dynamic list
impl fmt::Display for StaticType { Tuple(Vec<StaticType>), // Heterogeneous fixed-size
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Vector(Box<StaticType>, usize), // Homogeneous fixed-size
match self { Matrix(Box<StaticType>, Vec<usize>), // Multi-dimensional homogeneous
StaticType::Any => write!(f, "any"), Record(Rc<Vec<(Keyword, StaticType)>>),
StaticType::Void => write!(f, "void"), Function(Box<Signature>),
StaticType::Bool => write!(f, "bool"), FunctionOverloads(Vec<Signature>),
StaticType::Int => write!(f, "int"), Object(&'static str),
StaticType::Float => write!(f, "float"), }
StaticType::DateTime => write!(f, "datetime"),
StaticType::Text => write!(f, "text"), impl fmt::Display for StaticType {
StaticType::Keyword => write!(f, "keyword"), fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
StaticType::List(inner) => write!(f, "[{}]", inner), match self {
StaticType::Tuple(elements) => { StaticType::Any => write!(f, "any"),
write!(f, "[")?; StaticType::Void => write!(f, "void"),
for (i, el) in elements.iter().enumerate() { StaticType::Bool => write!(f, "bool"),
if i > 0 { write!(f, " ")?; } StaticType::Int => write!(f, "int"),
write!(f, "{}", el)?; StaticType::Float => write!(f, "float"),
} StaticType::DateTime => write!(f, "datetime"),
write!(f, "]") StaticType::Text => write!(f, "text"),
}, StaticType::Keyword => write!(f, "keyword"),
StaticType::Vector(inner, len) => write!(f, "vector<{}, {}>", inner, len), StaticType::List(inner) => write!(f, "[{}]", inner),
StaticType::Matrix(inner, shape) => { StaticType::Tuple(elements) => {
write!(f, "matrix<{}, [", inner)?; write!(f, "[")?;
for (i, s) in shape.iter().enumerate() { for (i, el) in elements.iter().enumerate() {
if i > 0 { write!(f, " ")?; } if i > 0 {
write!(f, "{}", s)?; write!(f, " ")?;
} }
write!(f, "]>") write!(f, "{}", el)?;
}, }
StaticType::Record(fields) => { write!(f, "]")
write!(f, "{{")?; }
for (i, (k, v)) in fields.iter().enumerate() { StaticType::Vector(inner, len) => write!(f, "vector<{}, {}>", inner, len),
if i > 0 { write!(f, ", ")?; } StaticType::Matrix(inner, shape) => {
write!(f, ":{} {}", k.name(), v)?; write!(f, "matrix<{}, [", inner)?;
} for (i, s) in shape.iter().enumerate() {
write!(f, "}}") if i > 0 {
}, write!(f, " ")?;
StaticType::Function(sig) => { }
write!(f, "fn({}) -> {}", sig.params, sig.ret) write!(f, "{}", s)?;
}, }
StaticType::FunctionOverloads(sigs) => { write!(f, "]>")
write!(f, "overloads({} variants)", sigs.len()) }
} StaticType::Record(fields) => {
StaticType::Object(name) => write!(f, "{}", name), write!(f, "{{")?;
} for (i, (k, v)) in fields.iter().enumerate() {
} if i > 0 {
} write!(f, ", ")?;
}
impl StaticType { write!(f, ":{} {}", k.name(), v)?;
/// Returns true if `other` can be assigned to a location of type `self`. }
pub fn is_assignable_from(&self, other: &StaticType) -> bool { write!(f, "}}")
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) { }
return true; StaticType::Function(sig) => {
} write!(f, "fn({}) -> {}", sig.params, sig.ret)
}
match (self, other) { StaticType::FunctionOverloads(sigs) => {
// A Vector is a Tuple write!(f, "overloads({} variants)", sigs.len())
(StaticType::Tuple(elements), StaticType::Vector(inner, len)) => { }
if elements.len() != *len { return false; } StaticType::Object(name) => write!(f, "{}", name),
elements.iter().all(|e| e.is_assignable_from(inner)) }
}, }
// A Matrix is a Vector (of Vectors/Matrices) }
(StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => {
if shape.is_empty() || shape[0] != *len { return false; } impl StaticType {
if shape.len() == 1 { /// Returns true if `other` can be assigned to a location of type `self`.
inner.is_assignable_from(m_inner) pub fn is_assignable_from(&self, other: &StaticType) -> bool {
} else { if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) {
// It's a matrix of higher dimension, so inner must be assignable from a sub-matrix return true;
let sub_shape = shape[1..].to_vec(); }
inner.is_assignable_from(&StaticType::Matrix(m_inner.clone(), sub_shape))
} match (self, other) {
}, // A Vector is a Tuple
_ => false (StaticType::Tuple(elements), StaticType::Vector(inner, len)) => {
} if elements.len() != *len {
} return false;
}
/// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type. elements.iter().all(|e| e.is_assignable_from(inner))
pub fn resolve_call(&self, args_ty: &StaticType) -> Option<StaticType> { }
match self { // A Matrix is a Vector (of Vectors/Matrices)
StaticType::Any => Some(StaticType::Any), (StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => {
StaticType::Function(sig) => { if shape.is_empty() || shape[0] != *len {
if sig.params.is_assignable_from(args_ty) { return false;
Some(sig.ret.clone()) }
} else { if shape.len() == 1 {
None inner.is_assignable_from(m_inner)
} } else {
} // It's a matrix of higher dimension, so inner must be assignable from a sub-matrix
StaticType::FunctionOverloads(sigs) => { let sub_shape = shape[1..].to_vec();
sigs.iter() inner.is_assignable_from(&StaticType::Matrix(m_inner.clone(), sub_shape))
.find(|sig| sig.params.is_assignable_from(args_ty)) }
.map(|sig| sig.ret.clone()) }
} _ => false,
_ => None, }
} }
}
/// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type.
/// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.) pub fn resolve_call(&self, args_ty: &StaticType) -> Option<StaticType> {
pub fn is_scalar_pure(&self) -> bool { match self {
match self { StaticType::Any => Some(StaticType::Any),
StaticType::Int | StaticType::Float | StaticType::Bool | StaticType::DateTime => true, StaticType::Function(sig) => {
StaticType::Tuple(elements) => elements.iter().all(|e| e.is_scalar_pure()), if sig.params.is_assignable_from(args_ty) {
StaticType::Vector(inner, _) => inner.is_scalar_pure(), Some(sig.ret.clone())
StaticType::Matrix(inner, _) => inner.is_scalar_pure(), } else {
_ => false, None
} }
} }
} StaticType::FunctionOverloads(sigs) => sigs
.iter()
impl Value { .find(|sig| sig.params.is_assignable_from(args_ty))
pub fn is_truthy(&self) -> bool { .map(|sig| sig.ret.clone()),
match self { _ => None,
Value::Void => false, }
Value::Bool(b) => *b, }
Value::Cell(c) => c.borrow().is_truthy(),
_ => true, /// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.)
} pub fn is_scalar_pure(&self) -> bool {
} match self {
StaticType::Int | StaticType::Float | StaticType::Bool | StaticType::DateTime => true,
/// Returns the underlying values as a slice if this is a Tuple or Record. StaticType::Tuple(elements) => elements.iter().all(|e| e.is_scalar_pure()),
pub fn as_slice(&self) -> Option<&[Value]> { StaticType::Vector(inner, _) => inner.is_scalar_pure(),
match self { StaticType::Matrix(inner, _) => inner.is_scalar_pure(),
Value::Tuple(v) => Some(v), _ => false,
Value::Record(r) => Some(&r.values), }
_ => None, }
} }
}
impl Value {
pub fn make_tuple(values: Vec<Value>) -> Self { pub fn is_truthy(&self) -> bool {
Value::Tuple(Rc::new(values)) match self {
} Value::Void => false,
Value::Bool(b) => *b,
pub fn make_record(keys: Vec<Keyword>, values: Vec<Value>) -> Self { Value::Cell(c) => c.borrow().is_truthy(),
Value::Record(Rc::new(RecordData { _ => true,
keys: Rc::new(keys), }
values: Rc::new(values) }
}))
} /// Returns the underlying values as a slice if this is a Tuple or Record.
pub fn as_slice(&self) -> Option<&[Value]> {
pub fn static_type(&self) -> StaticType { match self {
match self { Value::Tuple(v) => Some(v),
Value::Void => StaticType::Void, Value::Record(r) => Some(&r.values),
Value::Bool(_) => StaticType::Bool, _ => None,
Value::Int(_) => StaticType::Int, }
Value::Float(_) => StaticType::Float, }
Value::DateTime(_) => StaticType::DateTime,
Value::Text(_) => StaticType::Text, pub fn make_tuple(values: Vec<Value>) -> Self {
Value::Keyword(_) => StaticType::Keyword, Value::Tuple(Rc::new(values))
Value::Tuple(values) => { }
if values.is_empty() {
return StaticType::Vector(Box::new(StaticType::Any), 0); pub fn make_record(keys: Vec<Keyword>, values: Vec<Value>) -> Self {
} Value::Record(Rc::new(RecordData {
keys: Rc::new(keys),
let element_types: Vec<_> = values.iter().map(|v| v.static_type()).collect(); values: Rc::new(values),
}))
// Check for Homogeneity (Vector) }
let first_ty = &element_types[0];
let all_same = element_types.iter().all(|t| t == first_ty); pub fn static_type(&self) -> StaticType {
match self {
if all_same { Value::Void => StaticType::Void,
match first_ty { Value::Bool(_) => StaticType::Bool,
StaticType::Vector(inner, len) => { Value::Int(_) => StaticType::Int,
// Possible Matrix Value::Float(_) => StaticType::Float,
StaticType::Matrix(inner.clone(), vec![values.len(), *len]) Value::DateTime(_) => StaticType::DateTime,
}, Value::Text(_) => StaticType::Text,
StaticType::Matrix(inner, shape) => { Value::Keyword(_) => StaticType::Keyword,
let mut new_shape = vec![values.len()]; Value::Tuple(values) => {
new_shape.extend(shape); if values.is_empty() {
StaticType::Matrix(inner.clone(), new_shape) return StaticType::Vector(Box::new(StaticType::Any), 0);
}, }
_ => StaticType::Vector(Box::new(first_ty.clone()), values.len())
} let element_types: Vec<_> = values.iter().map(|v| v.static_type()).collect();
} else {
StaticType::Tuple(element_types) // Check for Homogeneity (Vector)
} let first_ty = &element_types[0];
}, let all_same = element_types.iter().all(|t| t == first_ty);
Value::Record(r) => {
let mut fields = Vec::with_capacity(r.values.len()); if all_same {
for (i, v) in r.values.iter().enumerate() { match first_ty {
fields.push((r.keys[i], v.static_type())); StaticType::Vector(inner, len) => {
} // Possible Matrix
StaticType::Record(Rc::new(fields)) StaticType::Matrix(inner.clone(), vec![values.len(), *len])
}, }
Value::Function(_) => StaticType::Any, // Dynamic function StaticType::Matrix(inner, shape) => {
Value::Object(o) => StaticType::Object(o.type_name()), let mut new_shape = vec![values.len()];
Value::Cell(c) => c.borrow().static_type(), new_shape.extend(shape);
Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any StaticType::Matrix(inner.clone(), new_shape)
} }
} _ => StaticType::Vector(Box::new(first_ty.clone()), values.len()),
} }
} else {
impl fmt::Display for Value { StaticType::Tuple(element_types)
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { }
match self { }
Value::Void => write!(f, "void"), Value::Record(r) => {
Value::Bool(b) => write!(f, "{}", b), let mut fields = Vec::with_capacity(r.values.len());
Value::Int(i) => write!(f, "{}", i), for (i, v) in r.values.iter().enumerate() {
Value::Float(fl) => write!(f, "{}", fl), fields.push((r.keys[i], v.static_type()));
Value::DateTime(ts) => { }
match Utc.timestamp_millis_opt(*ts) { StaticType::Record(Rc::new(fields))
chrono::LocalResult::Single(dt) => write!(f, "#{}#", dt.format("%Y-%m-%d %H:%M:%S")), }
_ => write!(f, "#timestamp({})#", ts), Value::Function(_) => StaticType::Any, // Dynamic function
} Value::Object(o) => StaticType::Object(o.type_name()),
}, Value::Cell(c) => c.borrow().static_type(),
Value::Text(t) => write!(f, "\"{}\"", t), Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any
Value::Keyword(k) => write!(f, ":{}", k.name()), }
Value::Tuple(values) => { }
write!(f, "[")?; }
for (i, val) in values.iter().enumerate() {
if i > 0 { write!(f, " ")?; } impl fmt::Display for Value {
write!(f, "{}", val)?; fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
} match self {
write!(f, "]") Value::Void => write!(f, "void"),
}, Value::Bool(b) => write!(f, "{}", b),
Value::Record(r) => { Value::Int(i) => write!(f, "{}", i),
write!(f, "{{")?; Value::Float(fl) => write!(f, "{}", fl),
for i in 0..r.values.len() { Value::DateTime(ts) => match Utc.timestamp_millis_opt(*ts) {
if i > 0 { write!(f, ", ")?; } chrono::LocalResult::Single(dt) => {
write!(f, ":{} {}", r.keys[i].name(), r.values[i])?; write!(f, "#{}#", dt.format("%Y-%m-%d %H:%M:%S"))
} }
write!(f, "}}") _ => write!(f, "#timestamp({})#", ts),
}, },
Value::Function(_) => write!(f, "<native fn>"), Value::Text(t) => write!(f, "\"{}\"", t),
Value::Object(o) => write!(f, "<{}>", o.type_name()), Value::Keyword(k) => write!(f, ":{}", k.name()),
Value::Cell(c) => write!(f, "{}", c.borrow()), Value::Tuple(values) => {
Value::TailCallRequest(_) => write!(f, "<tail call request>"), write!(f, "[")?;
} for (i, val) in values.iter().enumerate() {
} if i > 0 {
} write!(f, " ")?;
}
impl fmt::Debug for Value { write!(f, "{}", val)?;
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { }
fmt::Display::fmt(self, f) write!(f, "]")
} }
} Value::Record(r) => {
write!(f, "{{")?;
for i in 0..r.values.len() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, ":{} {}", r.keys[i].name(), r.values[i])?;
}
write!(f, "}}")
}
Value::Function(_) => write!(f, "<native fn>"),
Value::Object(o) => write!(f, "<{}>", o.type_name()),
Value::Cell(c) => write!(f, "{}", c.borrow()),
Value::TailCallRequest(_) => write!(f, "<tail call request>"),
}
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
+712 -465
View File
File diff suppressed because it is too large Load Diff
+134 -132
View File
@@ -1,132 +1,134 @@
use myc::ast::environment::Environment; use clap::Parser;
use myc::utils::tester; use myc::ast::environment::Environment;
use clap::Parser; use myc::utils::tester;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Parser)] #[derive(Parser)]
#[command(author, version, about = "MYC AST Compiler & Benchmarker", long_about = None)] #[command(author, version, about = "MYC AST Compiler & Benchmarker", long_about = None)]
struct Cli { struct Cli {
/// The script file to run or benchmark /// The script file to run or benchmark
#[arg(value_name = "FILE")] #[arg(value_name = "FILE")]
file: Option<PathBuf>, file: Option<PathBuf>,
/// Run a script string directly /// Run a script string directly
#[arg(short, long)] #[arg(short, long)]
eval: Option<String>, eval: Option<String>,
/// Run benchmarks (Only allowed in Release mode) /// Run benchmarks (Only allowed in Release mode)
#[arg(short, long)] #[arg(short, long)]
bench: bool, bench: bool,
/// Update the benchmark baseline (Only allowed in Release mode) /// Update the benchmark baseline (Only allowed in Release mode)
#[arg(short, long)] #[arg(short, long)]
update_bench: bool, update_bench: bool,
/// Dump the compiled AST /// Dump the compiled AST
#[arg(short, long)] #[arg(short, long)]
dump: bool, dump: bool,
/// Run with TracingObserver enabled /// Run with TracingObserver enabled
#[arg(short, long)] #[arg(short, long)]
trace: bool, trace: bool,
/// Disable optimization /// Disable optimization
#[arg(long)] #[arg(long)]
no_opt: bool, no_opt: bool,
} }
fn main() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let mut env = Environment::new(); let mut env = Environment::new();
env.optimization = !cli.no_opt; env.optimization = !cli.no_opt;
if cli.bench || cli.update_bench { if cli.bench || cli.update_bench {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
eprintln!("❌ ERROR: Benchmarks must be run in Release mode!"); eprintln!("❌ ERROR: Benchmarks must be run in Release mode!");
eprintln!(" Use: cargo run --release --bin ast -- --bench"); eprintln!(" Use: cargo run --release --bin ast -- --bench");
std::process::exit(1); std::process::exit(1);
} }
println!("🚀 Running benchmarks in RELEASE mode...\n"); println!("🚀 Running benchmarks in RELEASE mode...\n");
let results = tester::run_benchmarks(cli.update_bench); let results = tester::run_benchmarks(cli.update_bench);
for res in results { for res in results {
let diff = res.diff_pct.map_or(String::new(), |d| format!(" ({:+.1}%)", d)); let diff = res
println!("{}: {} - {:?}{}", res.status, res.name, res.median, diff); .diff_pct
} .map_or(String::new(), |d| format!(" ({:+.1}%)", d));
return; println!("{}: {} - {:?}{}", res.status, res.name, res.median, diff);
} }
return;
if let Some(script_str) = cli.eval { }
if cli.dump {
match env.dump_ast(&script_str) { if let Some(script_str) = cli.eval {
Ok(dump) => println!("{}", dump), if cli.dump {
Err(e) => eprintln!("Error dumping AST: {}", e), match env.dump_ast(&script_str) {
} Ok(dump) => println!("{}", dump),
} else if cli.trace { Err(e) => eprintln!("Error dumping AST: {}", e),
execute_trace(&mut env, &script_str); }
} else { } else if cli.trace {
execute(&env, &script_str); execute_trace(&mut env, &script_str);
} } else {
} else if let Some(file_path) = cli.file { execute(&env, &script_str);
match fs::read_to_string(&file_path) { }
Ok(content) => { } else if let Some(file_path) = cli.file {
if cli.dump { match fs::read_to_string(&file_path) {
match env.dump_ast(&content) { Ok(content) => {
Ok(dump) => println!("{}", dump), if cli.dump {
Err(e) => eprintln!("Error dumping AST: {}", e), match env.dump_ast(&content) {
} Ok(dump) => println!("{}", dump),
} else if cli.trace { Err(e) => eprintln!("Error dumping AST: {}", e),
execute_trace(&mut env, &content); }
} else { } else if cli.trace {
execute(&env, &content); execute_trace(&mut env, &content);
} } else {
}, execute(&env, &content);
Err(e) => { }
eprintln!("Error reading file {:?}: {}", file_path, e); }
std::process::exit(1); Err(e) => {
} eprintln!("Error reading file {:?}: {}", file_path, e);
} std::process::exit(1);
} else { }
println!("MYC AST Compiler CLI. Use --help for usage."); }
} } else {
} println!("MYC AST Compiler CLI. Use --help for usage.");
}
fn execute(env: &Environment, source: &str) { }
match env.run_script(source) {
Ok(result) => println!("{}", result), fn execute(env: &Environment, source: &str) {
Err(e) => { match env.run_script(source) {
eprintln!("Error: {}", e); Ok(result) => println!("{}", result),
std::process::exit(1); Err(e) => {
} eprintln!("Error: {}", e);
} std::process::exit(1);
} }
}
fn execute_trace(env: &mut Environment, source: &str) { }
match env.compile(source) {
Ok(compiled) => { fn execute_trace(env: &mut Environment, source: &str) {
let linked = env.link(compiled); match env.compile(source) {
let mut vm = myc::ast::vm::VM::new(env.global_values.clone()); Ok(compiled) => {
let mut observer = myc::ast::vm::TracingObserver::new(); let linked = env.link(compiled);
match vm.run_with_observer(&mut observer, &linked) { let mut vm = myc::ast::vm::VM::new(env.global_values.clone());
Ok(result) => { let mut observer = myc::ast::vm::TracingObserver::new();
for line in observer.logs { match vm.run_with_observer(&mut observer, &linked) {
println!("{}", line); Ok(result) => {
} for line in observer.logs {
println!("Result: {}", result); println!("{}", line);
} }
Err(e) => { println!("Result: {}", result);
for line in observer.logs { }
println!("{}", line); Err(e) => {
} for line in observer.logs {
eprintln!("Runtime Error: {}", e); println!("{}", line);
std::process::exit(1); }
} eprintln!("Runtime Error: {}", e);
} std::process::exit(1);
} }
Err(e) => { }
eprintln!("Compilation Error: {}", e); }
std::process::exit(1); Err(e) => {
} eprintln!("Compilation Error: {}", e);
} std::process::exit(1);
} }
}
}
+334 -328
View File
@@ -1,328 +1,334 @@
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
use regex::Regex; use regex::Regex;
use std::fs; use std::fs;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub struct TestResult { pub struct TestResult {
pub name: String, pub name: String,
pub success: bool, pub success: bool,
pub message: String, pub message: String,
} }
pub struct BenchmarkResult { pub struct BenchmarkResult {
pub name: String, pub name: String,
pub median: Duration, pub median: Duration,
pub baseline: Option<Duration>, pub baseline: Option<Duration>,
pub diff_pct: Option<f64>, pub diff_pct: Option<f64>,
pub status: String, pub status: String,
} }
pub fn run_functional_tests() -> Vec<TestResult> { pub fn run_functional_tests() -> Vec<TestResult> {
run_functional_tests_with_optimization(false) run_functional_tests_with_optimization(false)
} }
pub fn run_functional_tests_with_optimization(enabled: bool) -> Vec<TestResult> { pub fn run_functional_tests_with_optimization(enabled: bool) -> 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 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 mut env = Environment::new(); // Fresh environment per test file let mut env = Environment::new(); // Fresh environment per test file
env.optimization = enabled; env.optimization = enabled;
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();
let name = path.file_name().unwrap().to_string_lossy().to_string(); let name = path.file_name().unwrap().to_string_lossy().to_string();
let expected_output = output_re let expected_output = output_re
.captures(&content) .captures(&content)
.map(|m| m.get(1).unwrap().as_str().trim().to_string()); .map(|m| m.get(1).unwrap().as_str().trim().to_string());
if let Some(expected) = expected_output { if let Some(expected) = expected_output {
match env.run_script(&content) { match env.run_script(&content) {
Ok(val) => { Ok(val) => {
let val_str = format!("{}", val); let val_str = format!("{}", val);
if val_str == expected { if val_str == expected {
results.push(TestResult { results.push(TestResult {
name, name,
success: true, success: true,
message: format!("OK: {}", val_str), message: format!("OK: {}", val_str),
}); });
} else { } else {
results.push(TestResult { results.push(TestResult {
name, name,
success: false, success: false,
message: format!( message: format!(
"Opt {}: Expected {}, got {}", "Opt {}: Expected {}, got {}",
if enabled { "ON" } else { "OFF" }, expected, val_str if enabled { "ON" } else { "OFF" },
), expected,
}); val_str
} ),
} });
Err(e) => results.push(TestResult { }
name, }
success: false, Err(e) => results.push(TestResult {
message: format!("Opt {}: Error: {}", if enabled { "ON" } else { "OFF" }, e), name,
}), success: false,
} message: format!(
} "Opt {}: Error: {}",
} if enabled { "ON" } else { "OFF" },
} e
results ),
} }),
}
pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> { }
let mut results = Vec::new(); }
let entries = fs::read_dir("examples").unwrap(); }
let is_release = !cfg!(debug_assertions); results
let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap(); }
let repeat_re = Regex::new(r";; Benchmark-Repeat: (\d+)").unwrap();
pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
for entry in entries.filter_map(|e| e.ok()) { let mut results = Vec::new();
let path = entry.path(); let entries = fs::read_dir("examples").unwrap();
if path.extension().is_none_or(|ext| ext != "myc") { let is_release = !cfg!(debug_assertions);
continue; let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap();
} let repeat_re = Regex::new(r";; Benchmark-Repeat: (\d+)").unwrap();
let content = fs::read_to_string(&path).unwrap(); for entry in entries.filter_map(|e| e.ok()) {
let name = path.file_name().unwrap().to_string_lossy().to_string(); let path = entry.path();
if path.extension().is_none_or(|ext| ext != "myc") {
let baseline_match = baseline_re.captures(&content); continue;
let repeat_match = repeat_re.captures(&content); }
let mut repeats = repeat_match let content = fs::read_to_string(&path).unwrap();
.and_then(|m| m.get(1)) let name = path.file_name().unwrap().to_string_lossy().to_string();
.and_then(|m| m.as_str().parse::<u32>().ok())
.unwrap_or(1); let baseline_match = baseline_re.captures(&content);
let repeat_match = repeat_re.captures(&content);
// Compile once for this file (symbols/types are compatible with all fresh environments)
let initial_env = Environment::new(); let mut repeats = repeat_match
let compiled_once = match initial_env.compile(&content) { .and_then(|m| m.get(1))
Ok(c) => c, .and_then(|m| m.as_str().parse::<u32>().ok())
Err(e) => { .unwrap_or(1);
results.push(BenchmarkResult {
name, // Compile once for this file (symbols/types are compatible with all fresh environments)
median: Duration::ZERO, let initial_env = Environment::new();
baseline: None, let compiled_once = match initial_env.compile(&content) {
diff_pct: None, Ok(c) => c,
status: format!("COMPILE ERROR: {}", e), Err(e) => {
}); results.push(BenchmarkResult {
continue; name,
} median: Duration::ZERO,
}; baseline: None,
diff_pct: None,
// Helper to measure sum of VM execution times over N executions in one environment status: format!("COMPILE ERROR: {}", e),
let measure_sum = });
|n: u32, node: &crate::ast::compiler::TypedNode| -> Result<Duration, String> { continue;
let env = Environment::new(); }
// Link once per sample };
let linked = env.link(node.clone());
let mut total = Duration::ZERO; // Helper to measure sum of VM execution times over N executions in one environment
for _ in 0..n { let measure_sum =
let start = Instant::now(); |n: u32, node: &crate::ast::compiler::TypedNode| -> Result<Duration, String> {
let _ = env.run(&linked)?; let env = Environment::new();
total += start.elapsed(); // Link once per sample
} let linked = env.link(node.clone());
Ok(total) let mut total = Duration::ZERO;
}; for _ in 0..n {
let start = Instant::now();
if update { let _ = env.run(&linked)?;
repeats = 1; total += start.elapsed();
loop { }
match measure_sum(repeats, &compiled_once) { Ok(total)
Ok(total) => { };
if total >= Duration::from_millis(2) || repeats >= 100_000 {
break; if update {
} repeats = 1;
let nanos = total.as_nanos().max(1) as f64; loop {
let factor = 2_000_000.0 / nanos; match measure_sum(repeats, &compiled_once) {
repeats = (repeats as f64 * factor).ceil() as u32; Ok(total) => {
repeats = repeats.max(repeats + 1); if total >= Duration::from_millis(2) || repeats >= 100_000 {
} break;
Err(e) => { }
results.push(BenchmarkResult { let nanos = total.as_nanos().max(1) as f64;
name: name.clone(), let factor = 2_000_000.0 / nanos;
median: Duration::ZERO, repeats = (repeats as f64 * factor).ceil() as u32;
baseline: None, repeats = repeats.max(repeats + 1);
diff_pct: None, }
status: format!("ERROR: {}", e), Err(e) => {
}); results.push(BenchmarkResult {
break; name: name.clone(),
} median: Duration::ZERO,
} baseline: None,
} diff_pct: None,
if results.last().is_some_and(|r| r.name == name) { status: format!("ERROR: {}", e),
continue; });
} break;
} }
}
let mut runs = Vec::new(); }
let mut error = None; if results.last().is_some_and(|r| r.name == name) {
// Adaptive samples: High repeats need fewer samples for stable median continue;
let num_samples = if repeats > 1000 { }
10 }
} else if repeats > 100 {
30 let mut runs = Vec::new();
} else { let mut error = None;
100 // Adaptive samples: High repeats need fewer samples for stable median
}; let num_samples = if repeats > 1000 {
10
for _ in 0..num_samples { } else if repeats > 100 {
match measure_sum(repeats, &compiled_once) { 30
Ok(d) => runs.push(d), } else {
Err(e) => { 100
error = Some(e); };
break;
} for _ in 0..num_samples {
} match measure_sum(repeats, &compiled_once) {
} Ok(d) => runs.push(d),
Err(e) => {
if let Some(e) = error { error = Some(e);
results.push(BenchmarkResult { break;
name, }
median: Duration::ZERO, }
baseline: None, }
diff_pct: None,
status: format!("ERROR: {}", e), if let Some(e) = error {
}); results.push(BenchmarkResult {
continue; name,
} median: Duration::ZERO,
baseline: None,
runs.sort(); diff_pct: None,
let median_total = runs[runs.len() / 2]; status: format!("ERROR: {}", e),
let median_single = median_total / repeats; });
continue;
if update { }
let new_val = format_duration(median_single);
let mut updated_content = content.clone(); runs.sort();
let median_total = runs[runs.len() / 2];
// 1. Update/Insert Benchmark let median_single = median_total / repeats;
let bench_line = format!(";; Benchmark: {}", new_val);
if let Some(m) = baseline_match { if update {
updated_content = updated_content.replace(m.get(0).unwrap().as_str(), &bench_line); let new_val = format_duration(median_single);
} else { let mut updated_content = content.clone();
updated_content = format!("{}\n{}", bench_line, updated_content);
} // 1. Update/Insert Benchmark
let bench_line = format!(";; Benchmark: {}", new_val);
// 2. Update/Insert/Remove Benchmark-Repeat if let Some(m) = baseline_match {
let repeat_line = if repeats > 1 { updated_content = updated_content.replace(m.get(0).unwrap().as_str(), &bench_line);
Some(format!(";; Benchmark-Repeat: {}", repeats)) } else {
} else { updated_content = format!("{}\n{}", bench_line, updated_content);
None }
};
let current_repeat_str = repeat_re // 2. Update/Insert/Remove Benchmark-Repeat
.captures(&updated_content) let repeat_line = if repeats > 1 {
.map(|m| m.get(0).unwrap().as_str().to_string()); Some(format!(";; Benchmark-Repeat: {}", repeats))
} else {
if let Some(line) = repeat_line { None
if let Some(old_line) = current_repeat_str { };
updated_content = updated_content.replace(&old_line, &line); let current_repeat_str = repeat_re
} else if let Some(m) = baseline_re.captures(&updated_content) { .captures(&updated_content)
let b_str = m.get(0).unwrap().as_str().to_string(); .map(|m| m.get(0).unwrap().as_str().to_string());
if let Some(pos) = updated_content.find(&b_str) {
updated_content.insert_str(pos + b_str.len(), &format!("\n{}", line)); if let Some(line) = repeat_line {
} if let Some(old_line) = current_repeat_str {
} updated_content = updated_content.replace(&old_line, &line);
} else if let Some(old_line) = current_repeat_str { } else if let Some(m) = baseline_re.captures(&updated_content) {
updated_content = updated_content.replace(&format!("{}\n", old_line), ""); let b_str = m.get(0).unwrap().as_str().to_string();
updated_content = updated_content.replace(&old_line, ""); if let Some(pos) = updated_content.find(&b_str) {
} updated_content.insert_str(pos + b_str.len(), &format!("\n{}", line));
}
fs::write(&path, updated_content).unwrap(); }
results.push(BenchmarkResult { } else if let Some(old_line) = current_repeat_str {
name, updated_content = updated_content.replace(&format!("{}\n", old_line), "");
median: median_single, updated_content = updated_content.replace(&old_line, "");
baseline: None, }
diff_pct: None,
status: format!("UPDATED: {}", new_val), fs::write(&path, updated_content).unwrap();
}); results.push(BenchmarkResult {
} else if let Some(m) = baseline_match { name,
let baseline_str = m.get(1).unwrap().as_str(); median: median_single,
let baseline = parse_duration(baseline_str).unwrap(); baseline: None,
diff_pct: None,
let diff = (median_single.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0; status: format!("UPDATED: {}", new_val),
});
let threshold = if is_release { 0.15 } else { 0.30 }; } else if let Some(m) = baseline_match {
let status = if median_single > baseline && diff > threshold { let baseline_str = m.get(1).unwrap().as_str();
"FAILED" let baseline = parse_duration(baseline_str).unwrap();
} else {
"OK" let diff = (median_single.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0;
};
let threshold = if is_release { 0.15 } else { 0.30 };
results.push(BenchmarkResult { let status = if median_single > baseline && diff > threshold {
name, "FAILED"
median: median_single, } else {
baseline: Some(baseline), "OK"
diff_pct: Some(diff * 100.0), };
status: status.to_string(),
}); results.push(BenchmarkResult {
} else { name,
results.push(BenchmarkResult { median: median_single,
name, baseline: Some(baseline),
median: median_single, diff_pct: Some(diff * 100.0),
baseline: None, status: status.to_string(),
diff_pct: None, });
status: "MISSING BASELINE".to_string(), } else {
}); results.push(BenchmarkResult {
} name,
} median: median_single,
results baseline: None,
} diff_pct: None,
status: "MISSING BASELINE".to_string(),
fn format_duration(d: Duration) -> String { });
if d.as_nanos() < 1000 { }
format!("{}ns", d.as_nanos()) }
} else if d.as_micros() < 1000 { results
format!("{:.1}us", d.as_nanos() as f64 / 1000.0) }
} else if d.as_millis() < 1000 {
format!("{:.1}ms", d.as_micros() as f64 / 1000.0) fn format_duration(d: Duration) -> String {
} else { if d.as_nanos() < 1000 {
format!("{:.1}s", d.as_millis() as f64 / 1000.0) format!("{}ns", d.as_nanos())
} } else if d.as_micros() < 1000 {
} format!("{:.1}us", d.as_nanos() as f64 / 1000.0)
} else if d.as_millis() < 1000 {
fn parse_duration(s: &str) -> Option<Duration> { format!("{:.1}ms", d.as_micros() as f64 / 1000.0)
use std::sync::OnceLock; } else {
static RE: OnceLock<Regex> = OnceLock::new(); format!("{:.1}s", d.as_millis() as f64 / 1000.0)
let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap()); }
}
let caps = re.captures(s)?;
let val: f64 = caps[1].parse().ok()?; fn parse_duration(s: &str) -> Option<Duration> {
let unit = &caps[2]; use std::sync::OnceLock;
match unit { static RE: OnceLock<Regex> = OnceLock::new();
"ns" => Some(Duration::from_nanos(val as u64)), let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap());
"us" => Some(Duration::from_nanos((val * 1000.0) as u64)),
"ms" => Some(Duration::from_nanos((val * 1_000_000.0) as u64)), let caps = re.captures(s)?;
"s" => Some(Duration::from_nanos((val * 1_000_000_000.0) as u64)), let val: f64 = caps[1].parse().ok()?;
_ => None, let unit = &caps[2];
} match unit {
} "ns" => Some(Duration::from_nanos(val as u64)),
"us" => Some(Duration::from_nanos((val * 1000.0) as u64)),
#[cfg(test)] "ms" => Some(Duration::from_nanos((val * 1_000_000.0) as u64)),
mod tests { "s" => Some(Duration::from_nanos((val * 1_000_000_000.0) as u64)),
#[test] _ => None,
#[cfg(not(debug_assertions))] }
fn benchmark_regression_test() { }
use super::*;
let results = run_benchmarks(false); #[cfg(test)]
let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect(); mod tests {
#[test]
if !failures.is_empty() { #[cfg(not(debug_assertions))]
let error_msg = failures fn benchmark_regression_test() {
.iter() use super::*;
.map(|r| { let results = run_benchmarks(false);
format!( let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect();
"{}: Median {:?}, Baseline {:?}, Diff {:.2}%",
r.name, if !failures.is_empty() {
r.median, let error_msg = failures
r.baseline.unwrap_or_default(), .iter()
r.diff_pct.unwrap_or(0.0) .map(|r| {
) format!(
}) "{}: Median {:?}, Baseline {:?}, Diff {:.2}%",
.collect::<Vec<_>>() r.name,
.join("\n"); r.median,
r.baseline.unwrap_or_default(),
panic!("Performance regression detected:\n{}", error_msg); r.diff_pct.unwrap_or(0.0)
} )
} })
} .collect::<Vec<_>>()
.join("\n");
panic!("Performance regression detected:\n{}", error_msg);
}
}
}