1
//! Standard-library loading: financial-domain structs (via DEFSTRUCT)
2
//! and the essential CL macros `WHEN`, `UNLESS`, plus the `UPCASE`
3
//! utility wrapper.
4

            
5
use crate::ast::{Expr, LambdaParams};
6

            
7
use super::entry::{Symbol, SymbolKind};
8
use super::table::SymbolTable;
9

            
10
impl SymbolTable {
11
79072
    pub(super) fn load_standard_library(&mut self) {
12
79072
        self.load_financial_structs();
13
79072
        self.load_essential_macros();
14
79072
    }
15

            
16
79072
    fn load_financial_structs(&mut self) {
17
79072
        let financial_structs = [
18
79072
            (
19
79072
                "transaction",
20
79072
                vec![
21
79072
                    "id",
22
79072
                    "parent-idx",
23
79072
                    "post-date",
24
79072
                    "enter-date",
25
79072
                    "split-count",
26
79072
                    "tag-count",
27
79072
                    "is-multi-currency",
28
79072
                ],
29
79072
            ),
30
79072
            (
31
79072
                "split",
32
79072
                vec![
33
79072
                    "id",
34
79072
                    "parent-idx",
35
79072
                    "account-id",
36
79072
                    "commodity-id",
37
79072
                    "value-num",
38
79072
                    "value-denom",
39
79072
                    "reconcile-state",
40
79072
                    "reconcile-date",
41
79072
                ],
42
79072
            ),
43
79072
            ("tag", vec!["id", "parent-idx", "name", "value"]),
44
79072
            (
45
79072
                "account",
46
79072
                vec![
47
79072
                    "id",
48
79072
                    "parent-idx",
49
79072
                    "parent-account-id",
50
79072
                    "name",
51
79072
                    "path",
52
79072
                    "tag-count",
53
79072
                ],
54
79072
            ),
55
79072
            (
56
79072
                "commodity",
57
79072
                vec!["id", "parent-idx", "symbol", "name", "tag-count"],
58
79072
            ),
59
79072
        ];
60

            
61
395360
        for (struct_name, field_names) in financial_structs {
62
395360
            let mut defstruct_args = vec![Expr::Symbol(struct_name.to_uppercase())];
63
2372160
            for field_name in field_names {
64
2372160
                defstruct_args.push(Expr::Symbol(field_name.to_uppercase()));
65
2372160
            }
66

            
67
395360
            if let Err(e) = crate::compiler::special::call(self, "DEFSTRUCT", &defstruct_args) {
68
                tracing::warn!("Failed to load financial struct {}: {:?}", struct_name, e);
69
395360
            }
70
        }
71
79072
    }
72

            
73
79072
    fn load_essential_macros(&mut self) {
74
79072
        let when_params = LambdaParams {
75
79072
            required: vec!["test".to_string()],
76
79072
            optional: Vec::new(),
77
79072
            rest: Some("body".to_string()),
78
79072
            key: Vec::new(),
79
79072
            aux: Vec::new(),
80
79072
        };
81
79072
        let when_body = Expr::Quasiquote(Box::new(Expr::List(vec![
82
79072
            Expr::Symbol("IF".to_string()),
83
79072
            Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
84
79072
            Expr::List(vec![
85
79072
                Expr::Symbol("BEGIN".to_string()),
86
79072
                Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
87
79072
            ]),
88
79072
            Expr::Nil,
89
79072
        ])));
90
79072
        let when_lambda = Expr::Lambda(when_params, Box::new(when_body));
91
79072
        self.define(Symbol::new("WHEN", SymbolKind::Macro).with_function(when_lambda));
92

            
93
79072
        let unless_params = LambdaParams {
94
79072
            required: vec!["test".to_string()],
95
79072
            optional: Vec::new(),
96
79072
            rest: Some("body".to_string()),
97
79072
            key: Vec::new(),
98
79072
            aux: Vec::new(),
99
79072
        };
100
79072
        let unless_body = Expr::Quasiquote(Box::new(Expr::List(vec![
101
79072
            Expr::Symbol("IF".to_string()),
102
79072
            Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
103
79072
            Expr::Nil,
104
79072
            Expr::List(vec![
105
79072
                Expr::Symbol("BEGIN".to_string()),
106
79072
                Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
107
79072
            ]),
108
79072
        ])));
109
79072
        let unless_lambda = Expr::Lambda(unless_params, Box::new(unless_body));
110
79072
        self.define(Symbol::new("UNLESS", SymbolKind::Macro).with_function(unless_lambda));
111

            
112
79072
        self.add_utility_functions();
113
79072
    }
114

            
115
79072
    fn add_utility_functions(&mut self) {
116
79072
        let upcase_params = LambdaParams::simple(vec!["string".to_string()]);
117
79072
        let upcase_body = Expr::Symbol("UPCASE-STRING".to_string());
118
79072
        let upcase_lambda = Expr::Lambda(upcase_params, Box::new(upcase_body));
119
79072
        self.define(Symbol::new("UPCASE", SymbolKind::Function).with_function(upcase_lambda));
120
79072
    }
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
79072
    pub(super) fn load_prelude(&mut self) {
130
79072
        self.load_nomiscript_defuns(include_str!("prelude.nms"), "universal prelude");
131
79072
    }
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
90088
    pub fn load_nomiscript_defuns(&mut self, source: &str, label: &str) {
140
90088
        let program = match crate::reader::Reader::parse(source) {
141
90088
            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
327304
        for form in &program.exprs {
149
327304
            match form.as_list().and_then(<[Expr]>::split_first) {
150
327304
                Some((Expr::Symbol(head), tail)) if head == "DEFUN" => {
151
327304
                    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
327304
                    }
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
90088
    }
163
}