Skip to main content

sim_lib_numbers_func/implementation/
domain.rs

1//! `Func` number-domain registration: the domain library, class symbols, and
2//! value-shape wiring that install the function domain into the runtime.
3
4use std::sync::Arc;
5
6use sim_kernel::{
7    AbiVersion, ClassId, DefaultFactory, Dependency, Export, Expr, Factory, Lib, LibManifest,
8    LibTarget, Linker, NumberDomain, NumberLiteral, Object, Result, Symbol, Value,
9    ValuePromotionRule, Version,
10};
11use sim_lib_numbers_cas::CasExpr;
12use sim_lib_numbers_core::{DomainNumberValueShape, domains};
13use sim_shape::shape_value;
14
15use super::function::{
16    CallFunction, FnBuilder, GradFunction, build_func_class, call_symbol, fn_symbol, grad_symbol,
17};
18use super::value::{Func, FuncMetadata, build_constant_func_value, build_func_value};
19
20/// Returns the domain symbol that names the `Func` number domain (`numbers/func`).
21///
22/// # Examples
23///
24/// ```
25/// use sim_lib_numbers_func::func_domain_symbol;
26///
27/// assert_eq!(func_domain_symbol().to_string(), "numbers/func");
28/// ```
29pub fn func_domain_symbol() -> Symbol {
30    domains::func()
31}
32
33/// Returns the class symbol for the constructible `Func` value class (`numbers/Func`).
34///
35/// # Examples
36///
37/// ```
38/// use sim_lib_numbers_func::func_class_symbol;
39///
40/// assert_eq!(func_class_symbol().to_string(), "numbers/Func");
41/// ```
42pub fn func_class_symbol() -> Symbol {
43    domains::domain("Func")
44}
45
46pub fn value_shape_symbol() -> Symbol {
47    sim_lib_numbers_core::value_shape_symbol(&func_domain_symbol())
48}
49
50#[sim_citizen_derive::non_citizen(
51    reason = "numbers/func number-domain marker; reconstruct by loading the function number lib",
52    kind = "marker"
53)]
54pub struct FuncNumberDomain;
55
56impl NumberDomain for FuncNumberDomain {
57    fn symbol(&self) -> Symbol {
58        func_domain_symbol()
59    }
60
61    fn parse_priority(&self) -> i32 {
62        -100
63    }
64
65    fn parse_literal(&self, _cx: &mut sim_kernel::Cx, _text: &str) -> Result<Option<Value>> {
66        Ok(None)
67    }
68
69    fn encode_literal(
70        &self,
71        _cx: &mut sim_kernel::Cx,
72        _value: Value,
73    ) -> Result<Option<NumberLiteral>> {
74        Ok(None)
75    }
76}
77
78impl Object for FuncNumberDomain {
79    fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
80        Ok("#<number-domain numbers/func>".to_owned())
81    }
82
83    fn as_any(&self) -> &dyn std::any::Any {
84        self
85    }
86}
87
88impl sim_kernel::ObjectCompat for FuncNumberDomain {
89    fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
90        sim_lib_numbers_core::number_domain_class_stub(cx)
91    }
92    fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
93        Ok(Expr::Symbol(func_domain_symbol()))
94    }
95    fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
96        let value_shape = cx
97            .registry()
98            .shape_by_symbol(&value_shape_symbol())
99            .cloned()
100            .unwrap_or(cx.factory().symbol(value_shape_symbol())?);
101        let func_class = cx
102            .registry()
103            .class_by_symbol(&func_class_symbol())
104            .cloned()
105            .unwrap_or(cx.factory().symbol(func_class_symbol())?);
106        cx.factory().table(vec![
107            (
108                Symbol::new("symbol"),
109                cx.factory().symbol(func_domain_symbol())?,
110            ),
111            (
112                Symbol::new("kind"),
113                cx.factory().string("number-domain".to_owned())?,
114            ),
115            (
116                Symbol::new("numeric-family"),
117                cx.factory().string("function".to_owned())?,
118            ),
119            (
120                Symbol::new("canonical-form"),
121                cx.factory()
122                    .string("callable symbolic function".to_owned())?,
123            ),
124            (
125                Symbol::new("parse-priority"),
126                cx.factory().string("-100".to_owned())?,
127            ),
128            (Symbol::new("constructor-class"), func_class),
129            (Symbol::new("value-shape"), value_shape),
130            (Symbol::new("builder"), cx.factory().symbol(fn_symbol())?),
131            (Symbol::new("call"), cx.factory().symbol(call_symbol())?),
132            (Symbol::new("grad"), cx.factory().symbol(grad_symbol())?),
133        ])
134    }
135    fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
136        Some(self)
137    }
138}
139
140/// Library that installs the `Func` number domain: its domain object, value
141/// class and shape, the `fn`/`call`/`grad` callables, and the promotion rules
142/// that lift scalar number values into constant functions.
143pub struct FuncNumbersLib;
144
145impl FuncNumbersLib {
146    /// Creates a new `FuncNumbersLib` ready to be loaded into a runtime.
147    pub fn new() -> Self {
148        Self
149    }
150}
151
152impl Default for FuncNumbersLib {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158impl Lib for FuncNumbersLib {
159    fn manifest(&self) -> LibManifest {
160        LibManifest {
161            id: func_domain_symbol(),
162            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
163            abi: AbiVersion { major: 0, minor: 1 },
164            target: LibTarget::HostRegistered,
165            requires: Vec::<Dependency>::new(),
166            capabilities: Vec::new(),
167            exports: vec![
168                Export::NumberDomain {
169                    symbol: func_domain_symbol(),
170                    number_domain_id: None,
171                },
172                Export::Class {
173                    symbol: func_class_symbol(),
174                    class_id: None,
175                },
176                Export::Shape {
177                    symbol: value_shape_symbol(),
178                    shape_id: None,
179                },
180                Export::Function {
181                    symbol: fn_symbol(),
182                    function_id: None,
183                },
184                Export::Function {
185                    symbol: call_symbol(),
186                    function_id: None,
187                },
188                Export::Function {
189                    symbol: grad_symbol(),
190                    function_id: None,
191                },
192            ],
193        }
194    }
195
196    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
197        let value_shape = Arc::new(DomainNumberValueShape::new(
198            func_domain_symbol(),
199            "FuncValue",
200            [
201                "callable number value in the numbers/func domain",
202                "accepts any NumberValue where domain == numbers/func",
203            ],
204        ));
205
206        linker.number_domain_value(
207            func_domain_symbol(),
208            DefaultFactory
209                .opaque(Arc::new(FuncNumberDomain))
210                .expect("number domain should be boxable"),
211        )?;
212        register_func_value_class(linker)?;
213        linker.shape_value(
214            value_shape_symbol(),
215            shape_value(value_shape_symbol(), value_shape),
216        )?;
217        for (symbol, value) in [
218            (
219                fn_symbol(),
220                DefaultFactory
221                    .opaque(Arc::new(FnBuilder))
222                    .expect("fn builder should be boxable"),
223            ),
224            (
225                call_symbol(),
226                DefaultFactory
227                    .opaque(Arc::new(CallFunction))
228                    .expect("call helper should be boxable"),
229            ),
230            (
231                grad_symbol(),
232                DefaultFactory
233                    .opaque(Arc::new(GradFunction))
234                    .expect("grad helper should be boxable"),
235            ),
236        ] {
237            linker.function_value(symbol, value)?;
238        }
239        for from_domain in promoted_domains() {
240            linker.value_promotion_rule(ValuePromotionRule {
241                from_domain,
242                to_domain: func_domain_symbol(),
243                cost: 1,
244                convert: promote_value_to_func,
245            });
246        }
247        super::value::register_value_ops(linker);
248        Ok(())
249    }
250}
251
252fn register_func_value_class(linker: &mut Linker<'_>) -> Result<ClassId> {
253    let func_class = build_func_class();
254    let class_id = linker.class_value(
255        func_class_symbol(),
256        DefaultFactory
257            .opaque(func_class.clone())
258            .expect("function class should be boxable"),
259    )?;
260    func_class.set_id(class_id);
261    Ok(class_id)
262}
263
264fn install_func_value_citizen(linker: &mut Linker<'_>) -> Result<()> {
265    register_func_value_class(linker).map(|_| ())
266}
267
268fn conformance_func_value_citizen(cx: &mut sim_kernel::Cx) -> Result<()> {
269    let var = Symbol::new("x");
270    let value = build_func_value(
271        cx,
272        Func::new(
273            vec![var.clone()],
274            Some(CasExpr::Var(var)),
275            None,
276            FuncMetadata::default(),
277        ),
278    )?;
279    sim_citizen::check_value_fixture(cx, value)
280}
281
282sim_citizen::inventory::submit! {
283    sim_citizen::CitizenInfo {
284        symbol: "numbers/Func",
285        version: 0,
286        crate_name: env!("CARGO_PKG_NAME"),
287        arity: 2,
288        install: install_func_value_citizen,
289        conformance: conformance_func_value_citizen,
290    }
291}
292
293fn promoted_domains() -> Vec<Symbol> {
294    vec![
295        domains::bool(),
296        domains::f32(),
297        domains::f64(),
298        domains::i64(),
299        domains::bigint(),
300        domains::rational(),
301        domains::complex(),
302        domains::cas(),
303        domains::continued_fraction(),
304    ]
305}
306
307fn promote_value_to_func(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
308    build_constant_func_value(cx, value)
309}