Lines
92.62 %
Functions
60 %
Branches
100 %
//! Standard-library loading: financial-domain structs (via DEFSTRUCT)
//! and the essential CL macros `WHEN`, `UNLESS`, plus the `UPCASE`
//! utility wrapper.
use crate::ast::{Expr, LambdaParams};
use super::entry::{Symbol, SymbolKind};
use super::table::SymbolTable;
impl SymbolTable {
pub(super) fn load_standard_library(&mut self) {
self.load_financial_structs();
self.load_essential_macros();
}
fn load_financial_structs(&mut self) {
let financial_structs = [
(
"transaction",
vec![
"id",
"parent-idx",
"post-date",
"enter-date",
"split-count",
"tag-count",
"is-multi-currency",
],
),
"split",
"account-id",
"commodity-id",
"value-num",
"value-denom",
"reconcile-state",
"reconcile-date",
("tag", vec!["id", "parent-idx", "name", "value"]),
"account",
"parent-account-id",
"name",
"path",
"commodity",
vec!["id", "parent-idx", "symbol", "name", "tag-count"],
];
for (struct_name, field_names) in financial_structs {
let mut defstruct_args = vec![Expr::Symbol(struct_name.to_uppercase())];
for field_name in field_names {
defstruct_args.push(Expr::Symbol(field_name.to_uppercase()));
if let Err(e) = crate::compiler::special::call(self, "DEFSTRUCT", &defstruct_args) {
tracing::warn!("Failed to load financial struct {}: {:?}", struct_name, e);
fn load_essential_macros(&mut self) {
let when_params = LambdaParams {
required: vec!["test".to_string()],
optional: Vec::new(),
rest: Some("body".to_string()),
key: Vec::new(),
aux: Vec::new(),
};
let when_body = Expr::Quasiquote(Box::new(Expr::List(vec![
Expr::Symbol("IF".to_string()),
Expr::Unquote(Box::new(Expr::Symbol("test".to_string()))),
Expr::List(vec![
Expr::Symbol("BEGIN".to_string()),
Expr::UnquoteSplicing(Box::new(Expr::Symbol("body".to_string()))),
]),
Expr::Nil,
])));
let when_lambda = Expr::Lambda(when_params, Box::new(when_body));
self.define(Symbol::new("WHEN", SymbolKind::Macro).with_function(when_lambda));
let unless_params = LambdaParams {
let unless_body = Expr::Quasiquote(Box::new(Expr::List(vec![
let unless_lambda = Expr::Lambda(unless_params, Box::new(unless_body));
self.define(Symbol::new("UNLESS", SymbolKind::Macro).with_function(unless_lambda));
self.add_utility_functions();
fn add_utility_functions(&mut self) {
let upcase_params = LambdaParams::simple(vec!["string".to_string()]);
let upcase_body = Expr::Symbol("UPCASE-STRING".to_string());
let upcase_lambda = Expr::Lambda(upcase_params, Box::new(upcase_body));
self.define(Symbol::new("UPCASE", SymbolKind::Function).with_function(upcase_lambda));
/// Loads the universal nomiscript prelude (ADR-0029) — reusable helpers
/// authored IN nomiscript and shared by every table. Called last in
/// `with_builtins*` so the prelude's DEFUNs see the builtins + accessors.
/// The source is DEFUN-only and references only universally-present
/// symbols, so it loads identically on the eval/host and pure-wasm paths
/// (a DEFUN stores its lambda body without resolving it). Host-fn-dependent
/// helpers are loaded separately on the rpc Session path.
pub(super) fn load_prelude(&mut self) {
self.load_nomiscript_defuns(include_str!("prelude.nms"), "universal prelude");
/// Parses a DEFUN-only nomiscript source and registers each `defun` into
/// this table via the eval special-form path (the same mechanism
/// `load_financial_structs` uses for DEFSTRUCT). Shared by the universal
/// prelude here and the rpc host prelude. A parse or non-DEFUN form is a
/// first-party authoring bug: it is logged and (in debug) asserts, rather
/// than silently shipping a broken prelude.
pub fn load_nomiscript_defuns(&mut self, source: &str, label: &str) {
let program = match crate::reader::Reader::parse(source) {
Ok(program) => program,
Err(e) => {
debug_assert!(false, "{label} failed to parse: {e:?}");
tracing::error!("{label} failed to parse: {e:?}");
return;
for form in &program.exprs {
match form.as_list().and_then(<[Expr]>::split_first) {
Some((Expr::Symbol(head), tail)) if head == "DEFUN" => {
if let Err(e) = crate::compiler::special::call(self, "DEFUN", tail) {
debug_assert!(false, "{label} defun failed: {e:?}");
tracing::error!("{label} defun failed: {e:?}");
_ => {
debug_assert!(false, "{label}: only top-level DEFUN forms are allowed");
tracing::error!("{label}: ignored non-DEFUN top-level form");