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
114769
    pub(super) fn load_standard_library(&mut self) {
12
114769
        self.load_financial_structs();
13
114769
        self.load_essential_macros();
14
114769
    }
15

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

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

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

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

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

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

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