Skip to main content

sim_lib_numbers_bool/
implementation.rs

1#![forbid(unsafe_code)]
2
3//! The `numbers/bool` library: its domain object, literal and value shapes, and
4//! the `Lib` that installs the bool ops and promotions into the integer and
5//! float domains.
6
7use std::sync::Arc;
8
9use sim_kernel::{
10    AbiVersion, DefaultFactory, Dependency, Export, Expr, Factory, Lib, LibManifest, LibTarget,
11    Linker, NumberDomain, NumberLiteral, Object, PromotionRule, Result, Symbol, Value,
12    ValuePromotionRule, Version,
13};
14use sim_lib_numbers_core::{
15    DomainNumberValueShape, NumberDomainTableSpec, ScalarBinaryOp, ScalarOps, domains,
16    install_scalar_ops, number_domain_table,
17};
18use sim_shape::shape_value;
19
20use crate::literal::{
21    NumberLiteralClass, NumberLiteralShape, class_surface_or_symbol, shape_surface_or_symbol,
22    value_instance_shape_symbol,
23};
24
25/// The `numbers/bool` domain symbol shared by this crate's literals, values,
26/// and ops.
27pub fn number_domain() -> Symbol {
28    domains::bool()
29}
30
31fn literal_class_symbol() -> Symbol {
32    domains::literal_class("bool")
33}
34
35pub(crate) fn literal_instance_shape_symbol() -> Symbol {
36    Symbol::qualified(literal_class_symbol().to_string(), "instance-shape")
37}
38
39fn value_shape_symbol() -> Symbol {
40    value_instance_shape_symbol()
41}
42
43fn u8_domain() -> Symbol {
44    domains::u8()
45}
46
47fn i64_domain() -> Symbol {
48    domains::i64()
49}
50
51fn f64_domain() -> Symbol {
52    domains::f64()
53}
54
55fn add_symbol() -> Symbol {
56    Symbol::qualified("math", "add")
57}
58
59fn sub_symbol() -> Symbol {
60    Symbol::qualified("math", "sub")
61}
62
63fn mul_symbol() -> Symbol {
64    Symbol::qualified("math", "mul")
65}
66
67#[sim_citizen_derive::non_citizen(
68    reason = "numbers/bool number-domain marker; reconstruct by loading the bool number lib",
69    kind = "marker",
70    descriptor = "numbers/bool"
71)]
72/// The boolean number domain at the base of the promotion lattice: parses
73/// `true`/`false` literals and declares the widening edges into the integer
74/// and float domains.
75pub struct BoolNumberDomain;
76
77impl NumberDomain for BoolNumberDomain {
78    fn symbol(&self) -> Symbol {
79        number_domain()
80    }
81
82    fn parse_priority(&self) -> i32 {
83        -10
84    }
85
86    fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
87        match text {
88            "true" => cx
89                .factory()
90                .number_literal(number_domain(), "true".to_owned())
91                .map(Some),
92            "false" => cx
93                .factory()
94                .number_literal(number_domain(), "false".to_owned())
95                .map(Some),
96            _ => Ok(None),
97        }
98    }
99
100    fn encode_literal(
101        &self,
102        cx: &mut sim_kernel::Cx,
103        value: Value,
104    ) -> Result<Option<NumberLiteral>> {
105        match value.object().as_expr(cx)? {
106            Expr::Number(number) if number.domain == number_domain() => Ok(Some(number)),
107            Expr::Bool(value) => Ok(Some(NumberLiteral {
108                domain: number_domain(),
109                canonical: if value { "true" } else { "false" }.to_owned(),
110            })),
111            _ => Ok(None),
112        }
113    }
114
115    fn promotions(&self) -> Vec<PromotionRule> {
116        vec![
117            PromotionRule {
118                from_domain: number_domain(),
119                to_domain: u8_domain(),
120                cost: 1,
121                convert: promote_bool_to_u8,
122            },
123            PromotionRule {
124                from_domain: number_domain(),
125                to_domain: i64_domain(),
126                cost: 2,
127                convert: promote_bool_to_i64,
128            },
129            PromotionRule {
130                from_domain: number_domain(),
131                to_domain: f64_domain(),
132                cost: 4,
133                convert: promote_bool_to_f64,
134            },
135        ]
136    }
137}
138
139impl Object for BoolNumberDomain {
140    fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
141        Ok("#<number-domain numbers/bool>".to_owned())
142    }
143
144    fn as_any(&self) -> &dyn std::any::Any {
145        self
146    }
147}
148
149impl sim_kernel::ObjectCompat for BoolNumberDomain {
150    fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
151        sim_lib_numbers_core::number_domain_class_stub(cx)
152    }
153    fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
154        Ok(Expr::Symbol(number_domain()))
155    }
156    fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
157        let literal_class = class_surface_or_symbol(cx, literal_class_symbol())?;
158        let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol())?;
159        let value_shape = shape_surface_or_symbol(cx, value_shape_symbol())?;
160        number_domain_table(
161            cx,
162            NumberDomainTableSpec::new(
163                number_domain(),
164                "boolean",
165                "true|false",
166                -10,
167                literal_class,
168                instance_shape,
169                value_shape,
170            ),
171        )
172    }
173    fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
174        Some(self)
175    }
176}
177
178/// The library that installs the `numbers/bool` domain: its literal class and
179/// shapes, value shape, boolean ops, and widening promotion rules.
180///
181/// # Examples
182///
183/// ```
184/// use std::sync::Arc;
185/// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy};
186/// use sim_lib_numbers_bool::{BoolNumbersLib, number_domain};
187///
188/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
189/// cx.load_lib(&BoolNumbersLib::new()).unwrap();
190///
191/// let value = cx.factory().bool(true).unwrap();
192/// let number = cx.number_value_ref(value).unwrap().unwrap();
193/// assert_eq!(number.domain, number_domain());
194/// ```
195pub struct BoolNumbersLib;
196
197impl BoolNumbersLib {
198    /// Construct the bool library installer.
199    pub fn new() -> Self {
200        Self
201    }
202}
203
204impl Default for BoolNumbersLib {
205    fn default() -> Self {
206        Self::new()
207    }
208}
209
210impl Lib for BoolNumbersLib {
211    fn manifest(&self) -> LibManifest {
212        LibManifest {
213            id: number_domain(),
214            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
215            abi: AbiVersion { major: 0, minor: 1 },
216            target: LibTarget::HostRegistered,
217            requires: Vec::<Dependency>::new(),
218            capabilities: Vec::new(),
219            exports: vec![
220                Export::NumberDomain {
221                    symbol: number_domain(),
222                    number_domain_id: None,
223                },
224                Export::Class {
225                    symbol: literal_class_symbol(),
226                    class_id: None,
227                },
228                Export::Shape {
229                    symbol: literal_instance_shape_symbol(),
230                    shape_id: None,
231                },
232                Export::Shape {
233                    symbol: value_shape_symbol(),
234                    shape_id: None,
235                },
236            ],
237        }
238    }
239
240    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
241        let instance_shape = Arc::new(NumberLiteralShape::new(
242            number_domain(),
243            "BoolLiteral",
244            [
245                "number literal in the numbers/bool domain",
246                "matches Expr::Bool or Expr::Number where domain == numbers/bool",
247            ],
248        ));
249        let literal_class = Arc::new(NumberLiteralClass::new(
250            literal_class_symbol(),
251            number_domain(),
252            "boolean",
253            "true|false",
254            instance_shape.clone(),
255        ));
256        let value_shape = Arc::new(DomainNumberValueShape::new(
257            number_domain(),
258            "BoolValue",
259            [
260                "number value in the numbers/bool domain",
261                "accepts any NumberValue where domain == numbers/bool",
262            ],
263        ));
264        linker.number_domain_value(
265            number_domain(),
266            DefaultFactory
267                .opaque(Arc::new(BoolNumberDomain))
268                .expect("number domain should be boxable"),
269        )?;
270        let class_id = linker.class_value(
271            literal_class_symbol(),
272            DefaultFactory
273                .opaque(literal_class.clone())
274                .expect("number literal class should be boxable"),
275        )?;
276        literal_class.set_id(class_id);
277        linker.shape_value(
278            literal_instance_shape_symbol(),
279            shape_value(literal_instance_shape_symbol(), instance_shape),
280        )?;
281        linker.shape_value(
282            value_shape_symbol(),
283            shape_value(value_shape_symbol(), value_shape),
284        )?;
285        for rule in BoolNumberDomain.promotions() {
286            linker.promotion_rule(rule.clone());
287            linker.value_promotion_rule(bool_value_promotion_rule(&rule));
288        }
289        let binary = [
290            (
291                add_symbol(),
292                bool_add_rule as BoolRuleFn,
293                bool_add_value_rule as ValueRuleFn,
294            ),
295            (sub_symbol(), bool_sub_rule, bool_sub_value_rule),
296            (mul_symbol(), bool_mul_rule, bool_mul_value_rule),
297        ]
298        .into_iter()
299        .map(|(operator, literal_apply, value_apply)| ScalarBinaryOp {
300            operator,
301            literal_cost: 0,
302            literal_apply,
303            value_cost: 1,
304            value_apply,
305        })
306        .collect();
307        let ops = ScalarOps {
308            domain: number_domain(),
309            binary,
310            unary: Vec::new(),
311            reduction: Vec::new(),
312        };
313        install_scalar_ops(linker, &ops);
314        Ok(())
315    }
316}
317
318type BoolRuleFn = fn(&mut sim_kernel::Cx, NumberLiteral, NumberLiteral) -> Result<Value>;
319type ValueRuleFn = fn(&mut sim_kernel::Cx, Value, Value) -> Result<Value>;
320
321fn bool_add_rule(
322    cx: &mut sim_kernel::Cx,
323    left: NumberLiteral,
324    right: NumberLiteral,
325) -> Result<Value> {
326    let out = parse_bool_literal(left, "left")? || parse_bool_literal(right, "right")?;
327    cx.factory().bool(out)
328}
329
330fn bool_sub_rule(
331    cx: &mut sim_kernel::Cx,
332    left: NumberLiteral,
333    right: NumberLiteral,
334) -> Result<Value> {
335    let out = parse_bool_literal(left, "left")? ^ parse_bool_literal(right, "right")?;
336    cx.factory().bool(out)
337}
338
339fn bool_mul_rule(
340    cx: &mut sim_kernel::Cx,
341    left: NumberLiteral,
342    right: NumberLiteral,
343) -> Result<Value> {
344    let out = parse_bool_literal(left, "left")? && parse_bool_literal(right, "right")?;
345    cx.factory().bool(out)
346}
347
348fn bool_add_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
349    let left = expect_bool_literal(cx, left, "left")?;
350    let right = expect_bool_literal(cx, right, "right")?;
351    bool_add_rule(cx, left, right)
352}
353
354fn bool_sub_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
355    let left = expect_bool_literal(cx, left, "left")?;
356    let right = expect_bool_literal(cx, right, "right")?;
357    bool_sub_rule(cx, left, right)
358}
359
360fn bool_mul_value_rule(cx: &mut sim_kernel::Cx, left: Value, right: Value) -> Result<Value> {
361    let left = expect_bool_literal(cx, left, "left")?;
362    let right = expect_bool_literal(cx, right, "right")?;
363    bool_mul_rule(cx, left, right)
364}
365
366fn parse_bool_literal(number: NumberLiteral, side: &str) -> Result<bool> {
367    if number.domain != number_domain() {
368        return Err(sim_kernel::Error::Eval(format!(
369            "{side} operand expected number domain {}, found {}",
370            number_domain(),
371            number.domain
372        )));
373    }
374    match number.canonical.as_str() {
375        "true" => Ok(true),
376        "false" => Ok(false),
377        other => Err(sim_kernel::Error::Eval(format!(
378            "{side} operand was not a valid bool literal: {}",
379            other
380        ))),
381    }
382}
383
384fn expect_bool_literal(cx: &mut sim_kernel::Cx, value: Value, side: &str) -> Result<NumberLiteral> {
385    let Some(number) = cx.number_value_ref(value)? else {
386        return Err(sim_kernel::Error::Eval(format!(
387            "{side} operand expected number domain {}, found non-number",
388            number_domain()
389        )));
390    };
391    if number.domain != number_domain() {
392        return Err(sim_kernel::Error::Eval(format!(
393            "{side} operand expected number domain {}, found {}",
394            number_domain(),
395            number.domain
396        )));
397    }
398    match number.literal {
399        Some(literal) => Ok(literal),
400        None => Err(sim_kernel::Error::Eval(format!(
401            "{side} operand in {} does not have a canonical literal form",
402            number_domain()
403        ))),
404    }
405}
406
407fn bool_value_promotion_rule(rule: &PromotionRule) -> ValuePromotionRule {
408    let convert = if rule.to_domain == u8_domain() {
409        promote_bool_value_to_u8
410    } else if rule.to_domain == i64_domain() {
411        promote_bool_value_to_i64
412    } else {
413        promote_bool_value_to_f64
414    };
415    ValuePromotionRule {
416        from_domain: rule.from_domain.clone(),
417        to_domain: rule.to_domain.clone(),
418        cost: rule.cost,
419        convert,
420    }
421}
422
423fn promote_bool_value_to_u8(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
424    let literal = expect_bool_literal(cx, value, "operand")?;
425    let promoted = promote_bool_to_u8(cx, literal)?;
426    cx.factory()
427        .number_literal(promoted.domain, promoted.canonical)
428}
429
430fn promote_bool_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
431    let literal = expect_bool_literal(cx, value, "operand")?;
432    let promoted = promote_bool_to_i64(cx, literal)?;
433    cx.factory()
434        .number_literal(promoted.domain, promoted.canonical)
435}
436
437fn promote_bool_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
438    let literal = expect_bool_literal(cx, value, "operand")?;
439    let promoted = promote_bool_to_f64(cx, literal)?;
440    cx.factory()
441        .number_literal(promoted.domain, promoted.canonical)
442}
443
444fn promote_bool_to_u8(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
445    Ok(NumberLiteral {
446        domain: u8_domain(),
447        canonical: if parse_bool_literal(number, "operand")? {
448            "1"
449        } else {
450            "0"
451        }
452        .to_owned(),
453    })
454}
455
456fn promote_bool_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
457    Ok(NumberLiteral {
458        domain: i64_domain(),
459        canonical: if parse_bool_literal(number, "operand")? {
460            "1"
461        } else {
462            "0"
463        }
464        .to_owned(),
465    })
466}
467
468fn promote_bool_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
469    Ok(NumberLiteral {
470        domain: f64_domain(),
471        canonical: if parse_bool_literal(number, "operand")? {
472            "1"
473        } else {
474            "0"
475        }
476        .to_owned(),
477    })
478}