Skip to main content

nomiscript/runtime/symbol/
stdlib.rs

1//! Standard-library loading: financial-domain structs (via DEFSTRUCT)
2//! and the essential CL macros `WHEN`, `UNLESS`, plus the `UPCASE`
3//! utility wrapper.
4
5use crate::ast::{Expr, LambdaParams};
6
7use super::entry::{Symbol, SymbolKind};
8use super::table::SymbolTable;
9
10impl SymbolTable {
11    pub(super) fn load_standard_library(&mut self) {
12        self.load_financial_structs();
13        self.load_essential_macros();
14    }
15
16    fn load_financial_structs(&mut self) {
17        let financial_structs = [
18            (
19                "transaction",
20                vec![
21                    "id",
22                    "parent-idx",
23                    "post-date",
24                    "enter-date",
25                    "split-count",
26                    "tag-count",
27                    "is-multi-currency",
28                ],
29            ),
30            (
31                "split",
32                vec![
33                    "id",
34                    "parent-idx",
35                    "account-id",
36                    "commodity-id",
37                    "value-num",
38                    "value-denom",
39                    "reconcile-state",
40                    "reconcile-date",
41                ],
42            ),
43            ("tag", vec!["id", "parent-idx", "name", "value"]),
44            (
45                "account",
46                vec![
47                    "id",
48                    "parent-idx",
49                    "parent-account-id",
50                    "name",
51                    "path",
52                    "tag-count",
53                ],
54            ),
55            (
56                "commodity",
57                vec!["id", "parent-idx", "symbol", "name", "tag-count"],
58            ),
59        ];
60
61        for (struct_name, field_names) in financial_structs {
62            let mut defstruct_args = vec![Expr::Symbol(struct_name.to_uppercase())];
63            for field_name in field_names {
64                defstruct_args.push(Expr::Symbol(field_name.to_uppercase()));
65            }
66
67            if let Err(e) = crate::compiler::special::call(self, "DEFSTRUCT", &defstruct_args) {
68                tracing::warn!("Failed to load financial struct {}: {:?}", struct_name, e);
69            }
70        }
71    }
72
73    fn load_essential_macros(&mut self) {
74        let when_params = LambdaParams {
75            required: vec!["test".to_string()],
76            optional: Vec::new(),
77            rest: Some("body".to_string()),
78            key: Vec::new(),
79            aux: Vec::new(),
80        };
81        let when_body = Expr::Quasiquote(Box::new(Expr::List(vec![
82            Expr::Symbol("IF".to_string()),
83            Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
84            Expr::List(vec![
85                Expr::Symbol("BEGIN".to_string()),
86                Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
87            ]),
88            Expr::Nil,
89        ])));
90        let when_lambda = Expr::Lambda(when_params, Box::new(when_body));
91        self.define(Symbol::new("WHEN", SymbolKind::Macro).with_function(when_lambda));
92
93        let unless_params = LambdaParams {
94            required: vec!["test".to_string()],
95            optional: Vec::new(),
96            rest: Some("body".to_string()),
97            key: Vec::new(),
98            aux: Vec::new(),
99        };
100        let unless_body = Expr::Quasiquote(Box::new(Expr::List(vec![
101            Expr::Symbol("IF".to_string()),
102            Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
103            Expr::Nil,
104            Expr::List(vec![
105                Expr::Symbol("BEGIN".to_string()),
106                Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
107            ]),
108        ])));
109        let unless_lambda = Expr::Lambda(unless_params, Box::new(unless_body));
110        self.define(Symbol::new("UNLESS", SymbolKind::Macro).with_function(unless_lambda));
111
112        self.add_utility_functions();
113    }
114
115    fn add_utility_functions(&mut self) {
116        let upcase_params = LambdaParams::simple(vec!["string".to_string()]);
117        let upcase_body = Expr::Symbol("UPCASE-STRING".to_string());
118        let upcase_lambda = Expr::Lambda(upcase_params, Box::new(upcase_body));
119        self.define(Symbol::new("UPCASE", SymbolKind::Function).with_function(upcase_lambda));
120    }
121
122    /// Loads the universal nomiscript prelude (ADR-0029) — reusable helpers
123    /// authored IN nomiscript and shared by every table. Called last in
124    /// `with_builtins*` so the prelude's DEFUNs see the builtins + accessors.
125    /// The source is DEFUN-only and references only universally-present
126    /// symbols, so it loads identically on the eval/host and pure-wasm paths
127    /// (a DEFUN stores its lambda body without resolving it). Host-fn-dependent
128    /// helpers are loaded separately on the rpc Session path.
129    pub(super) fn load_prelude(&mut self) {
130        self.load_nomiscript_defuns(include_str!("prelude.nms"), "universal prelude");
131    }
132
133    /// Parses a DEFUN-only nomiscript source and registers each `defun` into
134    /// this table via the eval special-form path (the same mechanism
135    /// `load_financial_structs` uses for DEFSTRUCT). Shared by the universal
136    /// prelude here and the rpc host prelude. A parse or non-DEFUN form is a
137    /// first-party authoring bug: it is logged and (in debug) asserts, rather
138    /// than silently shipping a broken prelude.
139    pub fn load_nomiscript_defuns(&mut self, source: &str, label: &str) {
140        let program = match crate::reader::Reader::parse(source) {
141            Ok(program) => program,
142            Err(e) => {
143                debug_assert!(false, "{label} failed to parse: {e:?}");
144                tracing::error!("{label} failed to parse: {e:?}");
145                return;
146            }
147        };
148        for form in &program.exprs {
149            match form.as_list().and_then(<[Expr]>::split_first) {
150                Some((Expr::Symbol(head), tail)) if head == "DEFUN" => {
151                    if let Err(e) = crate::compiler::special::call(self, "DEFUN", tail) {
152                        debug_assert!(false, "{label} defun failed: {e:?}");
153                        tracing::error!("{label} defun failed: {e:?}");
154                    }
155                }
156                _ => {
157                    debug_assert!(false, "{label}: only top-level DEFUN forms are allowed");
158                    tracing::error!("{label}: ignored non-DEFUN top-level form");
159                }
160            }
161        }
162    }
163}