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

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

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

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

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

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

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

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