1use std::collections::HashMap;
2
3use crate::ast::Expr;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SymbolKind {
7 Operator,
9 Native,
11 Function,
13 Variable,
15 SpecialForm,
17 Macro,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct Symbol {
23 name: String,
24 kind: SymbolKind,
25 value: Option<Expr>,
26 function: Option<Expr>,
27 doc: Option<String>,
28 properties: HashMap<String, Expr>,
29}
30
31impl Symbol {
32 pub fn new(name: impl Into<String>, kind: SymbolKind) -> Self {
33 Self {
34 name: name.into(),
35 kind,
36 value: None,
37 function: None,
38 doc: None,
39 properties: HashMap::new(),
40 }
41 }
42
43 #[must_use]
44 pub fn with_value(mut self, value: Expr) -> Self {
45 self.value = Some(value);
46 self
47 }
48
49 #[must_use]
50 pub fn name(&self) -> &str {
51 &self.name
52 }
53
54 #[must_use]
55 pub fn kind(&self) -> SymbolKind {
56 self.kind
57 }
58
59 #[must_use]
60 pub fn value(&self) -> Option<&Expr> {
61 self.value.as_ref()
62 }
63
64 pub fn set_value(&mut self, value: Expr) {
65 self.value = Some(value);
66 }
67
68 #[must_use]
69 pub fn with_function(mut self, func: Expr) -> Self {
70 self.function = Some(func);
71 self
72 }
73
74 #[must_use]
75 pub fn function(&self) -> Option<&Expr> {
76 self.function.as_ref()
77 }
78
79 pub fn set_function(&mut self, func: Expr) {
80 self.function = Some(func);
81 }
82
83 #[must_use]
84 pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
85 self.doc = Some(doc.into());
86 self
87 }
88
89 #[must_use]
90 pub fn doc(&self) -> Option<&str> {
91 self.doc.as_deref()
92 }
93
94 pub fn set_doc(&mut self, doc: impl Into<String>) {
95 self.doc = Some(doc.into());
96 }
97
98 #[must_use]
99 pub fn get_property(&self, key: &str) -> Option<&Expr> {
100 self.properties.get(key)
101 }
102
103 pub fn set_property(&mut self, key: impl Into<String>, value: Expr) {
104 self.properties.insert(key.into(), value);
105 }
106
107 pub fn remove_property(&mut self, key: &str) -> Option<Expr> {
108 self.properties.remove(key)
109 }
110
111 #[must_use]
112 pub fn properties(&self) -> &HashMap<String, Expr> {
113 &self.properties
114 }
115}
116
117#[derive(Debug, Clone, Default)]
118pub struct SymbolTable {
119 symbols: HashMap<String, Symbol>,
120 struct_fields: HashMap<String, Vec<String>>,
121}
122
123impl SymbolTable {
124 #[must_use]
125 pub fn new() -> Self {
126 Self::default()
127 }
128
129 #[must_use]
130 pub fn with_builtins() -> Self {
131 let mut table = Self::new();
132 table.register_builtins();
133 table.load_standard_library();
134 table.register_entity_accessors();
136 table
137 }
138
139 #[must_use]
140 pub fn with_builtins_for_wasm() -> Self {
141 let mut table = Self::new();
142 table.register_builtins();
143 table.load_standard_library();
144 table.register_entity_accessors();
145 table
146 }
147
148 pub fn register_builtins(&mut self) {
149 let operators = ["+", "-", "*", "/", "=", "/=", "<", ">", "<=", ">=", "MOD"];
150 for op in operators {
151 self.define(Symbol::new(op, SymbolKind::Operator));
152 }
153
154 let special_forms = [
155 "IF",
156 "COND",
157 "LET",
158 "LET*",
159 "LETREC",
160 "DEFINE",
161 "DEFUN",
162 "DEFVAR",
163 "DEFPARAMETER",
164 "LAMBDA",
165 "QUOTE",
166 "FUNCTION",
167 "SET!",
168 "SETF",
169 "BEGIN",
170 "AND",
171 "OR",
172 "APPLY",
173 "FUNCALL",
174 "COMPILE",
175 "EVAL",
176 "DESCRIBE",
177 "DO",
178 "DO*",
179 "DEFMACRO",
180 "MACROEXPAND-1",
181 "MACROEXPAND",
182 "LABELS",
183 "DOLIST",
184 "DEFSTRUCT",
185 ];
186 for form in special_forms {
187 self.define(Symbol::new(form, SymbolKind::SpecialForm));
188 }
189
190 let natives = [
191 "CAR",
192 "CDR",
193 "CONS",
194 "LIST",
195 "NULL?",
196 "PAIR?",
197 "EQ?",
198 "EQUAL?",
199 "NOT",
200 "PRINT",
201 "DISPLAY",
202 "NEWLINE",
203 "LENGTH",
204 "APPEND",
205 "REVERSE",
206 "MAP",
207 "FILTER",
208 "FOLD",
209 "DEBUG",
210 "EQL",
211 "EQUAL",
212 "MAKE-STRUCT-INSTANCE",
213 "STRUCT-FIELD",
214 "STRUCT-P",
215 "STRUCT-SET-FIELD",
216 "UPCASE-STRING",
217 "GET-INPUT-ENTITIES",
218 "MAKE-STRUCT-RUNTIME",
219 ];
220 for native in natives {
221 self.define(Symbol::new(native, SymbolKind::Native));
222 }
223
224 self.register_entity_constants();
225 self.register_entity_accessors();
226
227 self.add_map_family_macros();
229 }
230
231 fn register_entity_constants(&mut self) {
232 use crate::ast::{Expr, Fraction};
233 use scripting_format::{ContextType, EntityType};
234
235 let entity_types: &[(&str, EntityType)] = &[
236 ("+ENTITY-TRANSACTION+", EntityType::Transaction),
237 ("+ENTITY-SPLIT+", EntityType::Split),
238 ("+ENTITY-TAG+", EntityType::Tag),
239 ("+ENTITY-ACCOUNT+", EntityType::Account),
240 ("+ENTITY-COMMODITY+", EntityType::Commodity),
241 ];
242 for (name, ty) in entity_types {
243 self.define(
244 Symbol::new(*name, SymbolKind::Variable)
245 .with_value(Expr::Number(Fraction::from_integer(i64::from(*ty as u8)))),
246 );
247 }
248
249 let context_types: &[(&str, ContextType)] = &[
250 ("+CONTEXT-CREATE+", ContextType::EntityCreate),
251 ("+CONTEXT-UPDATE+", ContextType::EntityUpdate),
252 ("+CONTEXT-DELETE+", ContextType::EntityDelete),
253 ("+CONTEXT-BATCH+", ContextType::BatchProcess),
254 ];
255 for (name, ct) in context_types {
256 self.define(
257 Symbol::new(*name, SymbolKind::Variable)
258 .with_value(Expr::Number(Fraction::from_integer(i64::from(*ct as u8)))),
259 );
260 }
261 }
262
263 fn register_entity_accessors(&mut self) {
264 let accessors = [
265 "ENTITY-COUNT",
266 "CONTEXT-TYPE",
267 "PRIMARY-ENTITY-TYPE",
268 "PRIMARY-ENTITY-IDX",
269 "ENTITY-TYPE",
270 "ENTITY-PARENT-IDX",
271 "TRANSACTION-SPLIT-COUNT",
272 "TRANSACTION-TAG-COUNT",
273 "TRANSACTION-IS-MULTI-CURRENCY",
274 "TRANSACTION-POST-DATE",
275 "TRANSACTION-ENTER-DATE",
276 "SPLIT-VALUE-NUM",
277 "SPLIT-VALUE-DENOM",
278 "SPLIT-VALUE",
279 "SPLIT-RECONCILE-STATE",
280 "SPLIT-RECONCILE-DATE",
281 "CREATE-TAG",
282 "TAG-NAME",
283 "TAG-VALUE",
284 "STRING=",
285 "DELETE-ENTITY",
286 ];
287 for name in accessors {
288 self.define(Symbol::new(name, SymbolKind::Native));
289 }
290 }
291
292 fn add_map_family_macros(&mut self) {
293 use crate::ast::{Expr, LambdaParams};
294
295 let mapcar_params = LambdaParams {
297 required: vec!["func".to_string(), "list".to_string()],
298 optional: Vec::new(),
299 rest: Some("lists".to_string()),
300 key: Vec::new(),
301 aux: Vec::new(),
302 };
303 let mapcar_body = Expr::List(vec![
304 Expr::Symbol("APPLY".to_string()),
305 Expr::Quote(Box::new(Expr::Symbol("LIST".to_string()))),
306 Expr::Quote(Box::new(Expr::Symbol("MAP".to_string()))),
307 Expr::Symbol("func".to_string()),
308 Expr::Symbol("list".to_string()),
309 Expr::Symbol("lists".to_string()),
310 ]);
311 let mapcar_lambda = Expr::Lambda(mapcar_params, Box::new(mapcar_body));
312 self.define(Symbol::new("MAPCAR", SymbolKind::Macro).with_function(mapcar_lambda));
313
314 let mapc_params = LambdaParams::simple(vec!["func".to_string(), "list".to_string()]);
316 let mapc_body = Expr::List(vec![
317 Expr::Symbol("LIST".to_string()),
318 Expr::Quote(Box::new(Expr::Symbol("MAP".to_string()))),
319 Expr::Symbol("func".to_string()),
320 Expr::Symbol("list".to_string()),
321 ]);
322 let mapc_lambda = Expr::Lambda(mapc_params, Box::new(mapc_body));
323 self.define(Symbol::new("MAPC", SymbolKind::Macro).with_function(mapc_lambda));
324 }
325
326 fn load_standard_library(&mut self) {
327 self.load_financial_structs();
329
330 self.load_essential_macros();
332 }
333
334 fn load_financial_structs(&mut self) {
335 let financial_structs = [
337 (
338 "transaction",
339 vec![
340 "id",
341 "parent-idx",
342 "post-date",
343 "enter-date",
344 "split-count",
345 "tag-count",
346 "is-multi-currency",
347 ],
348 ),
349 (
350 "split",
351 vec![
352 "id",
353 "parent-idx",
354 "account-id",
355 "commodity-id",
356 "value-num",
357 "value-denom",
358 "reconcile-state",
359 "reconcile-date",
360 ],
361 ),
362 ("tag", vec!["id", "parent-idx", "name", "value"]),
363 (
364 "account",
365 vec![
366 "id",
367 "parent-idx",
368 "parent-account-id",
369 "name",
370 "path",
371 "tag-count",
372 ],
373 ),
374 (
375 "commodity",
376 vec!["id", "parent-idx", "symbol", "name", "tag-count"],
377 ),
378 ];
379
380 for (struct_name, field_names) in financial_structs {
382 let mut defstruct_args = vec![Expr::Symbol(struct_name.to_uppercase())];
383 for field_name in field_names {
384 defstruct_args.push(Expr::Symbol(field_name.to_uppercase()));
385 }
386
387 if let Err(e) = crate::compiler::special::call(self, "DEFSTRUCT", &defstruct_args) {
389 tracing::warn!("Failed to load financial struct {}: {:?}", struct_name, e);
390 }
391 }
392 }
393
394 fn load_essential_macros(&mut self) {
395 use crate::ast::{Expr, LambdaParams};
396
397 let when_params = LambdaParams {
399 required: vec!["test".to_string()],
400 optional: Vec::new(),
401 rest: Some("body".to_string()),
402 key: Vec::new(),
403 aux: Vec::new(),
404 };
405 let when_body = Expr::Quasiquote(Box::new(Expr::List(vec![
406 Expr::Symbol("IF".to_string()),
407 Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
408 Expr::List(vec![
409 Expr::Symbol("BEGIN".to_string()),
410 Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
411 ]),
412 Expr::Nil,
413 ])));
414 let when_lambda = Expr::Lambda(when_params, Box::new(when_body));
415 self.define(Symbol::new("WHEN", SymbolKind::Macro).with_function(when_lambda));
416
417 let unless_params = LambdaParams {
419 required: vec!["test".to_string()],
420 optional: Vec::new(),
421 rest: Some("body".to_string()),
422 key: Vec::new(),
423 aux: Vec::new(),
424 };
425 let unless_body = Expr::Quasiquote(Box::new(Expr::List(vec![
426 Expr::Symbol("IF".to_string()),
427 Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
428 Expr::Nil,
429 Expr::List(vec![
430 Expr::Symbol("BEGIN".to_string()),
431 Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
432 ]),
433 ])));
434 let unless_lambda = Expr::Lambda(unless_params, Box::new(unless_body));
435 self.define(Symbol::new("UNLESS", SymbolKind::Macro).with_function(unless_lambda));
436
437 self.add_utility_functions();
439 }
440
441 fn add_utility_functions(&mut self) {
442 use crate::ast::{Expr, LambdaParams};
443
444 let upcase_params = LambdaParams::simple(vec!["string".to_string()]);
446 let upcase_body = Expr::Symbol("UPCASE-STRING".to_string()); let upcase_lambda = Expr::Lambda(upcase_params, Box::new(upcase_body));
448 self.define(Symbol::new("UPCASE", SymbolKind::Function).with_function(upcase_lambda));
449 }
450
451 pub fn define(&mut self, symbol: Symbol) {
452 self.symbols.insert(symbol.name.clone(), symbol);
453 }
454
455 #[must_use]
456 pub fn lookup(&self, name: &str) -> Option<&Symbol> {
457 self.symbols.get(name)
458 }
459
460 pub fn lookup_mut(&mut self, name: &str) -> Option<&mut Symbol> {
461 self.symbols.get_mut(name)
462 }
463
464 pub fn remove(&mut self, name: &str) -> Option<Symbol> {
465 self.symbols.remove(name)
466 }
467
468 #[must_use]
469 pub fn contains(&self, name: &str) -> bool {
470 self.symbols.contains_key(name)
471 }
472
473 pub fn iter(&self) -> impl Iterator<Item = (&String, &Symbol)> {
474 self.symbols.iter()
475 }
476
477 pub fn define_struct_fields(&mut self, name: impl Into<String>, fields: Vec<String>) {
478 self.struct_fields.insert(name.into(), fields);
479 }
480
481 #[must_use]
482 pub fn struct_fields(&self, name: &str) -> Option<&[String]> {
483 self.struct_fields.get(name).map(Vec::as_slice)
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 #[test]
492 fn test_symbol_creation() {
493 let sym = Symbol::new("foo", SymbolKind::Variable);
494 assert_eq!(sym.name(), "foo");
495 assert_eq!(sym.kind(), SymbolKind::Variable);
496 assert!(sym.value().is_none());
497 }
498
499 #[test]
500 fn test_symbol_with_value() {
501 let sym = Symbol::new("x", SymbolKind::Variable).with_value(Expr::Bool(true));
502 assert!(sym.value().is_some());
503 assert_eq!(sym.value(), Some(&Expr::Bool(true)));
504 }
505
506 #[test]
507 fn test_symbol_properties() {
508 let mut sym = Symbol::new("test", SymbolKind::Function);
509 sym.set_property("doc", Expr::String("A test function".into()));
510 sym.set_property("pure", Expr::Bool(true));
511
512 assert_eq!(
513 sym.get_property("doc"),
514 Some(&Expr::String("A test function".into()))
515 );
516 assert_eq!(sym.get_property("pure"), Some(&Expr::Bool(true)));
517 assert!(sym.get_property("nonexistent").is_none());
518 }
519
520 #[test]
521 fn test_symbol_table_builtins() {
522 let table = SymbolTable::with_builtins();
523
524 assert!(table.contains("+"));
525 assert_eq!(table.lookup("+").unwrap().kind(), SymbolKind::Operator);
526
527 assert!(table.contains("IF"));
528 assert_eq!(table.lookup("IF").unwrap().kind(), SymbolKind::SpecialForm);
529
530 assert!(table.contains("CAR"));
531 assert_eq!(table.lookup("CAR").unwrap().kind(), SymbolKind::Native);
532
533 assert!(table.contains("EVAL"));
534 assert_eq!(
535 table.lookup("EVAL").unwrap().kind(),
536 SymbolKind::SpecialForm
537 );
538
539 assert!(table.contains("DEBUG"));
540 assert_eq!(table.lookup("DEBUG").unwrap().kind(), SymbolKind::Native);
541 }
542
543 #[test]
544 fn test_symbol_table_define_lookup() {
545 let mut table = SymbolTable::new();
546 table.define(Symbol::new("my-func", SymbolKind::Function));
547
548 assert!(table.contains("my-func"));
549 assert!(!table.contains("undefined"));
550
551 let sym = table.lookup("my-func").unwrap();
552 assert_eq!(sym.kind(), SymbolKind::Function);
553 }
554}