Skip to main content

sim_lib_numbers_complex/implementation/
literal.rs

1//! The `numbers/complex` domain object and `ComplexNumbersLib`: the domain and
2//! operator symbols and the `Lib` that registers the domain, its shapes, value
3//! class, ops, and inbound promotion edges.
4
5use std::sync::Arc;
6
7use sim_kernel::{
8    AbiVersion, ClassRef, Cx, DefaultFactory, Dependency, Export, Expr, Factory, Lib, LibManifest,
9    LibTarget, Linker, NumberDomain, NumberLiteral, Object, PromotionRule, Result, Symbol, Value,
10    ValuePromotionRule, Version,
11};
12use sim_lib_numbers_core::{
13    NumberLiteralClass, NumberLiteralShape, ScalarBinaryOp, ScalarOps, ScalarReductionOp,
14    ScalarUnaryOp, class_surface_or_symbol, domains, install_scalar_ops, shape_surface_or_symbol,
15};
16use sim_shape::shape_value;
17
18use super::ops::{
19    ComplexRuleFn, ValueRuleFn, canonical_complex, parse_complex_literal, register_promotions,
20};
21use super::surface::NumberValueShape;
22use super::value::{build_complex_value_class, complex_value_class_symbol};
23
24/// The `numbers/complex` domain symbol shared by this crate's literals, values,
25/// and ops.
26pub fn number_domain() -> Symbol {
27    domains::complex()
28}
29
30/// The symbol of the complex literal class (the `Expr::Number` literal shape in
31/// canonical `a+bi` form).
32pub fn literal_class_symbol() -> Symbol {
33    domains::literal_class("complex")
34}
35
36/// The symbol of the shape matching individual complex literals, derived from
37/// [`literal_class_symbol`].
38pub fn literal_instance_shape_symbol() -> Symbol {
39    Symbol::qualified(literal_class_symbol().to_string(), "instance-shape")
40}
41
42/// The symbol of the shape matching opaque complex values in the
43/// `numbers/complex` domain.
44pub fn value_shape_symbol() -> Symbol {
45    domains::value_shape(&number_domain())
46}
47
48/// The `numbers/f64` domain symbol, source of the f64 -> complex promotion edge.
49pub fn f64_domain() -> Symbol {
50    domains::f64()
51}
52
53/// The `numbers/i64` domain symbol, source of the i64 -> complex promotion edge.
54pub fn i64_domain() -> Symbol {
55    domains::i64()
56}
57
58/// The `numbers/rational` domain symbol, source of the rational -> complex
59/// promotion edge.
60pub fn rational_domain() -> Symbol {
61    domains::rational()
62}
63
64/// The `math/add` operator symbol this domain installs a complex rule for.
65pub fn add_symbol() -> Symbol {
66    Symbol::qualified("math", "add")
67}
68
69/// The `math/sub` operator symbol this domain installs a complex rule for.
70pub fn sub_symbol() -> Symbol {
71    Symbol::qualified("math", "sub")
72}
73
74/// The `math/mul` operator symbol this domain installs a complex rule for.
75pub fn mul_symbol() -> Symbol {
76    Symbol::qualified("math", "mul")
77}
78
79/// The `math/div` operator symbol this domain installs a complex rule for.
80pub fn div_symbol() -> Symbol {
81    Symbol::qualified("math", "div")
82}
83
84/// The `math/neg` operator symbol this domain installs a complex rule for.
85pub fn neg_symbol() -> Symbol {
86    Symbol::qualified("math", "neg")
87}
88
89/// The `math/sum` reduction operator symbol this domain installs a complex rule
90/// for.
91pub fn sum_symbol() -> Symbol {
92    Symbol::qualified("math", "sum")
93}
94
95/// The `math/product` reduction operator symbol this domain installs a complex
96/// rule for.
97pub fn product_symbol() -> Symbol {
98    Symbol::qualified("math", "product")
99}
100
101#[sim_citizen_derive::non_citizen(
102    reason = "numbers/complex number-domain marker; reconstruct by loading the complex number lib",
103    kind = "marker",
104    descriptor = "numbers/complex"
105)]
106/// The complex number domain at the sink of the scalar promotion lattice:
107/// parses `a+bi` literals and accepts the widening edges from `f64`, `i64`, and
108/// `rational`.
109pub struct ComplexNumberDomain;
110
111impl NumberDomain for ComplexNumberDomain {
112    fn symbol(&self) -> Symbol {
113        number_domain()
114    }
115
116    fn parse_priority(&self) -> i32 {
117        -10
118    }
119
120    fn parse_literal(&self, cx: &mut Cx, text: &str) -> Result<Option<Value>> {
121        let Some((real, imag)) = parse_complex_literal(text) else {
122            return Ok(None);
123        };
124        cx.factory()
125            .number_literal(number_domain(), canonical_complex(real, imag))
126            .map(Some)
127    }
128
129    fn encode_literal(&self, cx: &mut Cx, value: Value) -> Result<Option<NumberLiteral>> {
130        let expr = value.object().as_expr(cx)?;
131        match expr {
132            Expr::Number(number) if number.domain == self.symbol() => Ok(Some(number)),
133            _ => Ok(None),
134        }
135    }
136
137    fn promotions(&self) -> Vec<PromotionRule> {
138        Vec::new()
139    }
140}
141
142impl Object for ComplexNumberDomain {
143    fn display(&self, _cx: &mut Cx) -> Result<String> {
144        Ok("#<number-domain numbers/complex>".to_owned())
145    }
146
147    fn as_any(&self) -> &dyn std::any::Any {
148        self
149    }
150}
151
152impl sim_kernel::ObjectCompat for ComplexNumberDomain {
153    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
154        sim_lib_numbers_core::number_domain_class_stub(cx)
155    }
156    fn as_expr(&self, _cx: &mut Cx) -> Result<Expr> {
157        Ok(Expr::Symbol(number_domain()))
158    }
159    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
160        let literal_class = class_surface_or_symbol(cx, literal_class_symbol())?;
161        let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol())?;
162        let value_shape = shape_surface_or_symbol(cx, value_shape_symbol())?;
163        cx.factory().table(vec![
164            (Symbol::new("symbol"), cx.factory().symbol(number_domain())?),
165            (
166                Symbol::new("kind"),
167                cx.factory().string("number-domain".to_owned())?,
168            ),
169            (
170                Symbol::new("numeric-family"),
171                cx.factory().string("complex".to_owned())?,
172            ),
173            (
174                Symbol::new("canonical-form"),
175                cx.factory().string("a+bi".to_owned())?,
176            ),
177            (
178                Symbol::new("parse-priority"),
179                cx.factory().string("-10".to_owned())?,
180            ),
181            (Symbol::new("literal-class"), literal_class),
182            (Symbol::new("instance-shape"), instance_shape),
183            (Symbol::new("value-shape"), value_shape),
184        ])
185    }
186    fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
187        Some(self)
188    }
189}
190
191/// The library that installs the `numbers/complex` domain: its literal class
192/// and shapes, the `ComplexValue` class, the complex ops, and the inbound
193/// promotion rules from `f64`, `i64`, and `rational`.
194///
195/// # Examples
196///
197/// ```
198/// use std::sync::Arc;
199/// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy};
200/// use sim_lib_numbers_complex::{ComplexNumbersLib, number_domain, complex_value};
201///
202/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
203/// cx.load_lib(&ComplexNumbersLib::new()).unwrap();
204///
205/// let value = complex_value(&mut cx, 3.0, -4.0).unwrap();
206/// let number = cx.number_value_ref(value).unwrap().unwrap();
207/// assert_eq!(number.domain, number_domain());
208/// ```
209pub struct ComplexNumbersLib;
210
211impl ComplexNumbersLib {
212    /// Creates a new `numbers/complex` domain library.
213    pub fn new() -> Self {
214        Self
215    }
216}
217
218impl Default for ComplexNumbersLib {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224impl Lib for ComplexNumbersLib {
225    fn manifest(&self) -> LibManifest {
226        LibManifest {
227            id: number_domain(),
228            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
229            abi: AbiVersion { major: 0, minor: 1 },
230            target: LibTarget::HostRegistered,
231            requires: Vec::<Dependency>::new(),
232            capabilities: Vec::new(),
233            exports: vec![
234                Export::NumberDomain {
235                    symbol: number_domain(),
236                    number_domain_id: None,
237                },
238                Export::Class {
239                    symbol: literal_class_symbol(),
240                    class_id: None,
241                },
242                Export::Class {
243                    symbol: complex_value_class_symbol(),
244                    class_id: None,
245                },
246                Export::Shape {
247                    symbol: literal_instance_shape_symbol(),
248                    shape_id: None,
249                },
250                Export::Shape {
251                    symbol: value_shape_symbol(),
252                    shape_id: None,
253                },
254            ],
255        }
256    }
257
258    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
259        let instance_shape = Arc::new(NumberLiteralShape::new(
260            number_domain(),
261            "ComplexLiteral",
262            [
263                "number literal in the numbers/complex domain",
264                "matches Expr::Number where domain == numbers/complex",
265            ],
266        ));
267        let literal_class = Arc::new(NumberLiteralClass::new(
268            literal_class_symbol(),
269            number_domain(),
270            "complex",
271            "a+bi",
272            literal_instance_shape_symbol(),
273            instance_shape.clone(),
274        ));
275        let value_shape = Arc::new(NumberValueShape::new(
276            number_domain(),
277            "ComplexValue",
278            [
279                "number value in the numbers/complex domain",
280                "accepts any NumberValue where domain == numbers/complex",
281            ],
282        ));
283        linker.number_domain_value(
284            number_domain(),
285            DefaultFactory
286                .opaque(Arc::new(ComplexNumberDomain))
287                .expect("number domain should be boxable"),
288        )?;
289        let class_id = linker.class_value(
290            literal_class_symbol(),
291            DefaultFactory
292                .opaque(literal_class.clone())
293                .expect("number literal class should be boxable"),
294        )?;
295        literal_class.set_id(class_id);
296        register_complex_value_class(linker)?;
297        linker.shape_value(
298            literal_instance_shape_symbol(),
299            shape_value(literal_instance_shape_symbol(), instance_shape),
300        )?;
301        linker.shape_value(
302            value_shape_symbol(),
303            shape_value(value_shape_symbol(), value_shape),
304        )?;
305        register_promotions(linker);
306        for rule in [
307            ValuePromotionRule {
308                from_domain: f64_domain(),
309                to_domain: number_domain(),
310                cost: 1,
311                convert: super::ops::promote_f64_value_to_complex,
312            },
313            ValuePromotionRule {
314                from_domain: i64_domain(),
315                to_domain: number_domain(),
316                cost: 1,
317                convert: super::ops::promote_i64_value_to_complex,
318            },
319            ValuePromotionRule {
320                from_domain: rational_domain(),
321                to_domain: number_domain(),
322                cost: 1,
323                convert: super::ops::promote_rational_value_to_complex,
324            },
325        ] {
326            linker.value_promotion_rule(rule);
327        }
328        let binary = [
329            (
330                add_symbol(),
331                super::ops::complex_add_rule as ComplexRuleFn,
332                super::ops::complex_add_value_rule as ValueRuleFn,
333            ),
334            (
335                sub_symbol(),
336                super::ops::complex_sub_rule,
337                super::ops::complex_sub_value_rule,
338            ),
339            (
340                mul_symbol(),
341                super::ops::complex_mul_rule,
342                super::ops::complex_mul_value_rule,
343            ),
344            (
345                div_symbol(),
346                super::ops::complex_div_rule,
347                super::ops::complex_div_value_rule,
348            ),
349        ]
350        .into_iter()
351        .map(|(operator, literal_apply, value_apply)| ScalarBinaryOp {
352            operator,
353            literal_cost: 0,
354            literal_apply,
355            value_cost: 1,
356            value_apply,
357        })
358        .collect();
359        let ops = ScalarOps {
360            domain: number_domain(),
361            binary,
362            unary: vec![ScalarUnaryOp {
363                operator: neg_symbol(),
364                literal_cost: 0,
365                literal_apply: super::ops::complex_neg_rule,
366                value_cost: 1,
367                value_apply: super::ops::complex_neg_value_rule,
368            }],
369            reduction: vec![
370                ScalarReductionOp {
371                    operator: sum_symbol(),
372                    literal_cost: 0,
373                    literal_apply: super::ops::complex_sum_rule,
374                    value_cost: 1,
375                    value_apply: super::ops::complex_sum_value_rule,
376                },
377                ScalarReductionOp {
378                    operator: product_symbol(),
379                    literal_cost: 0,
380                    literal_apply: super::ops::complex_product_rule,
381                    value_cost: 1,
382                    value_apply: super::ops::complex_product_value_rule,
383                },
384            ],
385        };
386        install_scalar_ops(linker, &ops);
387        Ok(())
388    }
389}
390
391fn register_complex_value_class(linker: &mut Linker<'_>) -> Result<()> {
392    let complex_class = build_complex_value_class();
393    let class_id = linker.class_value(
394        complex_value_class_symbol(),
395        DefaultFactory
396            .opaque(complex_class.clone())
397            .expect("complex value class should be boxable"),
398    )?;
399    complex_class.set_id(class_id);
400    Ok(())
401}
402
403fn install_complex_value_citizen(linker: &mut Linker<'_>) -> Result<()> {
404    register_complex_value_class(linker)
405}
406
407fn conformance_complex_value_citizen(cx: &mut sim_kernel::Cx) -> Result<()> {
408    let value = super::value::complex_value(cx, 1.5, -2.25)?;
409    sim_citizen::check_value_fixture(cx, value)
410}
411
412sim_citizen::inventory::submit! {
413    sim_citizen::CitizenInfo {
414        symbol: "numbers/Complex",
415        version: 1,
416        crate_name: env!("CARGO_PKG_NAME"),
417        arity: 2,
418        install: install_complex_value_citizen,
419        conformance: conformance_complex_value_citizen,
420    }
421}