Lines
93.64 %
Functions
52.34 %
Branches
100 %
use std::collections::HashMap;
use crate::ast::Expr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SymbolKind {
/// Built-in operators like +, -, *, /
Operator,
/// Native functions implemented in the runtime
Native,
/// User-defined functions
Function,
/// Variables and constants
Variable,
/// Special forms like if, let, define, lambda
SpecialForm,
/// User-defined macros
Macro,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Symbol {
name: String,
kind: SymbolKind,
value: Option<Expr>,
function: Option<Expr>,
doc: Option<String>,
properties: HashMap<String, Expr>,
impl Symbol {
pub fn new(name: impl Into<String>, kind: SymbolKind) -> Self {
Self {
name: name.into(),
kind,
value: None,
function: None,
doc: None,
properties: HashMap::new(),
#[must_use]
pub fn with_value(mut self, value: Expr) -> Self {
self.value = Some(value);
self
pub fn name(&self) -> &str {
&self.name
pub fn kind(&self) -> SymbolKind {
self.kind
pub fn value(&self) -> Option<&Expr> {
self.value.as_ref()
pub fn set_value(&mut self, value: Expr) {
pub fn with_function(mut self, func: Expr) -> Self {
self.function = Some(func);
pub fn function(&self) -> Option<&Expr> {
self.function.as_ref()
pub fn set_function(&mut self, func: Expr) {
pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
self.doc = Some(doc.into());
pub fn doc(&self) -> Option<&str> {
self.doc.as_deref()
pub fn set_doc(&mut self, doc: impl Into<String>) {
pub fn get_property(&self, key: &str) -> Option<&Expr> {
self.properties.get(key)
pub fn set_property(&mut self, key: impl Into<String>, value: Expr) {
self.properties.insert(key.into(), value);
pub fn remove_property(&mut self, key: &str) -> Option<Expr> {
self.properties.remove(key)
pub fn properties(&self) -> &HashMap<String, Expr> {
&self.properties
#[derive(Debug, Clone, Default)]
pub struct SymbolTable {
symbols: HashMap<String, Symbol>,
struct_fields: HashMap<String, Vec<String>>,
impl SymbolTable {
pub fn new() -> Self {
Self::default()
pub fn with_builtins() -> Self {
let mut table = Self::new();
table.register_builtins();
table.load_standard_library();
table
pub fn with_builtins_for_wasm() -> Self {
// Skip loading standard library to avoid runtime values that can't be compiled to WASM
// The essential macros and financial structs will be defined inline in scripts
pub fn register_builtins(&mut self) {
let operators = ["+", "-", "*", "/", "=", "/=", "<", ">", "<=", ">=", "MOD"];
for op in operators {
self.define(Symbol::new(op, SymbolKind::Operator));
let special_forms = [
"IF",
"COND",
"LET",
"LET*",
"LETREC",
"DEFINE",
"DEFUN",
"DEFVAR",
"DEFPARAMETER",
"LAMBDA",
"QUOTE",
"FUNCTION",
"SET!",
"SETF",
"BEGIN",
"AND",
"OR",
"APPLY",
"FUNCALL",
"COMPILE",
"EVAL",
"DESCRIBE",
"DO",
"DO*",
"DEFMACRO",
"MACROEXPAND-1",
"MACROEXPAND",
"LABELS",
"DOLIST",
"DEFSTRUCT",
];
for form in special_forms {
self.define(Symbol::new(form, SymbolKind::SpecialForm));
let natives = [
"CAR",
"CDR",
"CONS",
"LIST",
"NULL?",
"PAIR?",
"EQ?",
"EQUAL?",
"NOT",
"PRINT",
"DISPLAY",
"NEWLINE",
"LENGTH",
"APPEND",
"REVERSE",
"MAP",
"FILTER",
"FOLD",
"DEBUG",
"EQL",
"EQUAL",
"MAKE-STRUCT-INSTANCE",
"STRUCT-FIELD",
"STRUCT-P",
"STRUCT-SET-FIELD",
"UPCASE-STRING",
"GET-INPUT-ENTITIES",
"MAKE-STRUCT-RUNTIME",
for native in natives {
self.define(Symbol::new(native, SymbolKind::Native));
// Add MAP* family macros
self.add_map_family_macros();
fn add_map_family_macros(&mut self) {
use crate::ast::{Expr, LambdaParams};
// MAPCAR - alias to MAP, supports one or more list arguments
let mapcar_params = LambdaParams {
required: vec!["func".to_string(), "list".to_string()],
optional: Vec::new(),
rest: Some("lists".to_string()),
key: Vec::new(),
aux: Vec::new(),
};
let mapcar_body = Expr::List(vec![
Expr::Symbol("APPLY".to_string()),
Expr::Quote(Box::new(Expr::Symbol("LIST".to_string()))),
Expr::Quote(Box::new(Expr::Symbol("MAP".to_string()))),
Expr::Symbol("func".to_string()),
Expr::Symbol("list".to_string()),
Expr::Symbol("lists".to_string()),
]);
let mapcar_lambda = Expr::Lambda(mapcar_params, Box::new(mapcar_body));
self.define(Symbol::new("MAPCAR", SymbolKind::Macro).with_function(mapcar_lambda));
// MAPC - apply function for side effects, return the original list
let mapc_params = LambdaParams::simple(vec!["func".to_string(), "list".to_string()]);
let mapc_body = Expr::List(vec![
Expr::Symbol("LIST".to_string()),
let mapc_lambda = Expr::Lambda(mapc_params, Box::new(mapc_body));
self.define(Symbol::new("MAPC", SymbolKind::Macro).with_function(mapc_lambda));
fn load_standard_library(&mut self) {
// Load financial domain structs automatically
self.load_financial_structs();
// Load essential Common Lisp macros
self.load_essential_macros();
fn load_financial_structs(&mut self) {
// Define financial domain structs that match the binary format
let financial_structs = [
(
"transaction",
vec![
"post-date",
"enter-date",
"split-count",
"tag-count",
"is-multi-currency",
],
),
"split",
"account-id",
"commodity-id",
"value-num",
"value-denom",
"reconcile-state",
"reconcile-date",
("tag", vec!["name", "value"]),
"account",
vec!["parent-account-id", "name", "path", "tag-count"],
("commodity", vec!["symbol", "name", "tag-count"]),
// Use the special forms compiler to define each struct
for (struct_name, field_names) in financial_structs {
let mut defstruct_args = vec![Expr::Symbol(struct_name.to_uppercase())];
for field_name in field_names {
defstruct_args.push(Expr::Symbol(field_name.to_uppercase()));
// Call defstruct to define the struct and its accessors
if let Err(e) = crate::compiler::special::call(self, "DEFSTRUCT", &defstruct_args) {
tracing::warn!("Failed to load financial struct {}: {:?}", struct_name, e);
fn load_essential_macros(&mut self) {
// WHEN macro: (when test body...)
let when_params = LambdaParams {
required: vec!["test".to_string()],
rest: Some("body".to_string()),
let when_body = Expr::Quasiquote(Box::new(Expr::List(vec![
Expr::Symbol("IF".to_string()),
Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
Expr::List(vec![
Expr::Symbol("BEGIN".to_string()),
Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
]),
Expr::Nil,
])));
let when_lambda = Expr::Lambda(when_params, Box::new(when_body));
self.define(Symbol::new("WHEN", SymbolKind::Macro).with_function(when_lambda));
// UNLESS macro: (unless test body...)
let unless_params = LambdaParams {
let unless_body = Expr::Quasiquote(Box::new(Expr::List(vec![
let unless_lambda = Expr::Lambda(unless_params, Box::new(unless_body));
self.define(Symbol::new("UNLESS", SymbolKind::Macro).with_function(unless_lambda));
// Add more essential utility functions
self.add_utility_functions();
fn add_utility_functions(&mut self) {
// UPCASE function (for string manipulation)
let upcase_params = LambdaParams::simple(vec!["string".to_string()]);
let upcase_body = Expr::Symbol("UPCASE-STRING".to_string()); // Will be implemented as native
let upcase_lambda = Expr::Lambda(upcase_params, Box::new(upcase_body));
self.define(Symbol::new("UPCASE", SymbolKind::Function).with_function(upcase_lambda));
pub fn define(&mut self, symbol: Symbol) {
self.symbols.insert(symbol.name.clone(), symbol);
pub fn lookup(&self, name: &str) -> Option<&Symbol> {
self.symbols.get(name)
pub fn lookup_mut(&mut self, name: &str) -> Option<&mut Symbol> {
self.symbols.get_mut(name)
pub fn remove(&mut self, name: &str) -> Option<Symbol> {
self.symbols.remove(name)
pub fn contains(&self, name: &str) -> bool {
self.symbols.contains_key(name)
pub fn iter(&self) -> impl Iterator<Item = (&String, &Symbol)> {
self.symbols.iter()
pub fn define_struct_fields(&mut self, name: impl Into<String>, fields: Vec<String>) {
self.struct_fields.insert(name.into(), fields);
pub fn struct_fields(&self, name: &str) -> Option<&[String]> {
self.struct_fields.get(name).map(Vec::as_slice)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_creation() {
let sym = Symbol::new("foo", SymbolKind::Variable);
assert_eq!(sym.name(), "foo");
assert_eq!(sym.kind(), SymbolKind::Variable);
assert!(sym.value().is_none());
fn test_symbol_with_value() {
let sym = Symbol::new("x", SymbolKind::Variable).with_value(Expr::Bool(true));
assert!(sym.value().is_some());
assert_eq!(sym.value(), Some(&Expr::Bool(true)));
fn test_symbol_properties() {
let mut sym = Symbol::new("test", SymbolKind::Function);
sym.set_property("doc", Expr::String("A test function".into()));
sym.set_property("pure", Expr::Bool(true));
assert_eq!(
sym.get_property("doc"),
Some(&Expr::String("A test function".into()))
);
assert_eq!(sym.get_property("pure"), Some(&Expr::Bool(true)));
assert!(sym.get_property("nonexistent").is_none());
fn test_symbol_table_builtins() {
let table = SymbolTable::with_builtins();
assert!(table.contains("+"));
assert_eq!(table.lookup("+").unwrap().kind(), SymbolKind::Operator);
assert!(table.contains("IF"));
assert_eq!(table.lookup("IF").unwrap().kind(), SymbolKind::SpecialForm);
assert!(table.contains("CAR"));
assert_eq!(table.lookup("CAR").unwrap().kind(), SymbolKind::Native);
assert!(table.contains("EVAL"));
table.lookup("EVAL").unwrap().kind(),
SymbolKind::SpecialForm
assert!(table.contains("DEBUG"));
assert_eq!(table.lookup("DEBUG").unwrap().kind(), SymbolKind::Native);
fn test_symbol_table_define_lookup() {
let mut table = SymbolTable::new();
table.define(Symbol::new("my-func", SymbolKind::Function));
assert!(table.contains("my-func"));
assert!(!table.contains("undefined"));
let sym = table.lookup("my-func").unwrap();
assert_eq!(sym.kind(), SymbolKind::Function);