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