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