Skip to main content

sim_lib_cookbook/
seed_catalog.rs

1//! A [`LibCatalog`] over the crate-shipped seed domains.
2//!
3//! `sim-lib-cookbook` already depends on the seeded domain libs (to embed their
4//! recipe books), so exposing a catalog over them adds NO new dependency and no
5//! cycle: it is a convenience for hosts, gated on the same `seed-recipes`
6//! feature. A production host assembles its own catalog
7//! from the full standard distribution at the facade level; this one covers the
8//! numbers domains, which is enough to demonstrate Category A (pure compute that
9//! only needs its lib loaded).
10//!
11//! A recipe resolves against this catalog by its `requires` name (`numbers/cas`,
12//! or the tail `cas`): the returned lib's own manifest id IS the cookbook name,
13//! so there is no separate name table to drift.
14
15use std::sync::Arc;
16
17use sim_cookbook::EmbeddedDir;
18use sim_kernel::{CodecId, Lib};
19
20use crate::catalog::LibCatalog;
21use crate::config::{ConfigProvider, LoadableLibResolver, ResolvedLoadable, built_in_config};
22use crate::loadable::LoadableLibList;
23
24/// A catalog over the seeded numbers domains.
25pub struct SeededLibCatalog {
26    libs: Vec<Box<dyn Lib>>,
27}
28
29impl SeededLibCatalog {
30    /// Build a catalog over the seeded numbers domain libs.
31    ///
32    /// Each lib is a pure, deterministic number domain; loading one adds its ops
33    /// (construction, arithmetic, CAS diff/eval, tensor arithmetic) to the eval
34    /// `Cx` without any effect capability.
35    pub fn standard() -> Self {
36        let libs: Vec<Box<dyn Lib>> = vec![
37            Box::new(sim_lib_numbers_i64::I64NumbersLib::new()),
38            Box::new(sim_lib_numbers_arith::NumbersArithmeticLib::new()),
39            Box::new(sim_lib_numbers_bigint::BigIntNumbersLib::new()),
40            Box::new(sim_lib_numbers_bool::BoolNumbersLib::new()),
41            Box::new(sim_lib_numbers_f64::F64NumbersLib::new()),
42            Box::new(sim_lib_numbers_rational::RationalNumbersLib::new()),
43            Box::new(sim_lib_numbers_complex::ComplexNumbersLib::new()),
44            Box::new(sim_lib_numbers_func::FuncNumbersLib::new()),
45            Box::new(sim_lib_numbers_cas::CasNumbersLib::new()),
46            Box::new(sim_lib_numbers_tensor::TensorNumbersLib::new()),
47            // Tensor broadcast arithmetic loads via `requires`, so
48            // `tensor-scale` (`(math/mul (vec 3 4) 2)`) runs host-independently
49            // rather than only through the boot prelude.
50            Box::new(sim_lib_numbers_tensor_bcast::TensorBroadcastLib::new()),
51            Box::new(sim_lib_discrete::DiscreteLib),
52            // Eval-policy special forms load on demand via a recipe's `requires`
53            // (`binding`/`control`/`sequence`/`pattern`), so
54            // `let`/`if`/`seq/*`/`match` recipes run without preloading them.
55            Box::new(sim_lib_binding::BindingLib),
56            Box::new(sim_lib_control::ControlLib),
57            Box::new(sim_lib_sequence::SequenceLib),
58            Box::new(sim_lib_pattern::PatternLib),
59            // Language codecs load on demand so a recipe with
60            // `codec = "algol"`/`"scheme"` parses+evals its own surface. Each is
61            // bound to a distinct fixed CodecId that cannot collide with the
62            // boot `codec/lisp` (id 1); both lower an eval-position surface to a
63            // Term the general evaluator runs. Algol depends on `codec/lisp`
64            // (boot-loaded).
65            Box::new(sim_codec_algol::AlgolCodecLib::new(CodecId(201))),
66            Box::new(sim_lib_lang_scheme::SchemeCodecLib::new(CodecId(202))),
67            // Offline MIDI render reduced to a deterministic frame digest (no
68            // device/clock/entropy).
69            Box::new(sim_lib_midi_core::MidiDigestLib),
70        ];
71        Self { libs }
72    }
73
74    /// Build the default host loadable-lib directory for the seeded catalog.
75    pub fn loadable_libs() -> LoadableLibList {
76        let resolver = BuiltInLoadableResolver;
77        let provider = ConfigProvider::new(built_in_config(), &resolver);
78        let (directory, diagnostics) = provider.loadable_libs();
79        debug_assert!(diagnostics.is_empty(), "{diagnostics:?}");
80        directory
81    }
82}
83
84impl LibCatalog for SeededLibCatalog {
85    fn resolve(&self, name: &str) -> Option<&dyn Lib> {
86        self.libs
87            .iter()
88            .find(|lib| {
89                let id = lib.manifest().id;
90                id.as_qualified_str() == name || id.name.as_ref() == name
91            })
92            .map(|lib| lib.as_ref())
93    }
94}
95
96/// Resolver for the seed-recipes built-in loadable-lib directory.
97pub struct BuiltInLoadableResolver;
98
99impl LoadableLibResolver for BuiltInLoadableResolver {
100    fn resolve(&self, source: &str, _id: &str) -> Option<ResolvedLoadable> {
101        match source {
102            "symbol:numbers/i64" => Some(resolved(
103                "I64 numbers",
104                Some(sim_lib_numbers_i64::RECIPES),
105                || Box::new(sim_lib_numbers_i64::I64NumbersLib::new()),
106            )),
107            "symbol:numbers/arith" => Some(resolved(
108                "Arithmetic numbers",
109                Some(sim_lib_numbers_arith::RECIPES),
110                || Box::new(sim_lib_numbers_arith::NumbersArithmeticLib::new()),
111            )),
112            "symbol:numbers/bigint" => Some(resolved(
113                "Big integer numbers",
114                Some(sim_lib_numbers_bigint::RECIPES),
115                || Box::new(sim_lib_numbers_bigint::BigIntNumbersLib::new()),
116            )),
117            "symbol:numbers/bool" => Some(resolved(
118                "Boolean numbers",
119                Some(sim_lib_numbers_bool::RECIPES),
120                || Box::new(sim_lib_numbers_bool::BoolNumbersLib::new()),
121            )),
122            "symbol:numbers/f64" => Some(resolved(
123                "F64 numbers",
124                Some(sim_lib_numbers_f64::RECIPES),
125                || Box::new(sim_lib_numbers_f64::F64NumbersLib::new()),
126            )),
127            "symbol:numbers/rational" => Some(resolved(
128                "Rational numbers",
129                Some(sim_lib_numbers_rational::RECIPES),
130                || Box::new(sim_lib_numbers_rational::RationalNumbersLib::new()),
131            )),
132            "symbol:numbers/complex" => Some(resolved(
133                "Complex numbers",
134                Some(sim_lib_numbers_complex::RECIPES),
135                || Box::new(sim_lib_numbers_complex::ComplexNumbersLib::new()),
136            )),
137            "symbol:numbers/func" => Some(resolved(
138                "Function numbers",
139                Some(sim_lib_numbers_func::RECIPES),
140                || Box::new(sim_lib_numbers_func::FuncNumbersLib::new()),
141            )),
142            "symbol:numbers/cas" => Some(resolved(
143                "CAS numbers",
144                Some(sim_lib_numbers_cas::RECIPES),
145                || Box::new(sim_lib_numbers_cas::CasNumbersLib::new()),
146            )),
147            "symbol:numbers/tensor" => Some(resolved(
148                "Tensor numbers",
149                Some(sim_lib_numbers_tensor::RECIPES),
150                || Box::new(sim_lib_numbers_tensor::TensorNumbersLib::new()),
151            )),
152            "symbol:numbers/tensor-bcast" => {
153                Some(resolved("Tensor broadcast numbers", None, || {
154                    Box::new(sim_lib_numbers_tensor_bcast::TensorBroadcastLib::new())
155                }))
156            }
157            "symbol:discrete" => Some(resolved(
158                "Discrete math",
159                Some(sim_lib_discrete::RECIPES),
160                || Box::new(sim_lib_discrete::DiscreteLib),
161            )),
162            "symbol:organ/binding" => Some(resolved(
163                "Binding organ",
164                Some(sim_lib_binding::RECIPES),
165                || Box::new(sim_lib_binding::BindingLib),
166            )),
167            "symbol:organ/control" => Some(resolved(
168                "Control organ",
169                Some(sim_lib_control::RECIPES),
170                || Box::new(sim_lib_control::ControlLib),
171            )),
172            "symbol:organ/sequence" => Some(resolved(
173                "Sequence organ",
174                Some(sim_lib_sequence::RECIPES),
175                || Box::new(sim_lib_sequence::SequenceLib),
176            )),
177            "symbol:organ/pattern" => Some(resolved(
178                "Pattern organ",
179                Some(sim_lib_pattern::RECIPES),
180                || Box::new(sim_lib_pattern::PatternLib),
181            )),
182            "symbol:codec/algol" => Some(resolved(
183                "Algol codec",
184                Some(sim_codec_algol::RECIPES),
185                || Box::new(sim_codec_algol::AlgolCodecLib::new(CodecId(201))),
186            )),
187            "symbol:codec/scheme-r7rs-small" => Some(resolved(
188                "Scheme codec",
189                Some(sim_lib_lang_scheme::RECIPES),
190                || Box::new(sim_lib_lang_scheme::SchemeCodecLib::new(CodecId(202))),
191            )),
192            "symbol:midi/digest" => Some(resolved(
193                "MIDI digest",
194                Some(sim_lib_midi_core::RECIPES),
195                || Box::new(sim_lib_midi_core::MidiDigestLib),
196            )),
197            _ => None,
198        }
199    }
200}
201
202fn resolved<F>(title: &str, recipes: Option<EmbeddedDir>, make: F) -> ResolvedLoadable
203where
204    F: Fn() -> Box<dyn Lib + Send + Sync> + Send + Sync + 'static,
205{
206    ResolvedLoadable {
207        title: title.to_owned(),
208        recipes,
209        factory: Arc::new(make),
210    }
211}