Skip to main content

sim_lib_numbers_fixed/
implementation.rs

1#![forbid(unsafe_code)]
2
3//! The fixed-width integer library: the per-domain spec table and the `Lib`
4//! that installs every `i8`..`i128` and `u8`..`u128` domain with its literal
5//! and value shapes and widening promotion edges.
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, domains, number_domain_table,
16};
17use sim_shape::shape_value;
18
19use crate::literal::{
20    NumberLiteralClass, NumberLiteralShape, class_surface_or_symbol, shape_surface_or_symbol,
21};
22
23#[derive(Clone, Copy)]
24struct DomainSpec {
25    name: &'static str,
26    parse_priority: i32,
27}
28
29const FIXED_DOMAINS: [DomainSpec; 11] = [
30    DomainSpec {
31        name: "i8",
32        parse_priority: 1,
33    },
34    DomainSpec {
35        name: "u8",
36        parse_priority: 1,
37    },
38    DomainSpec {
39        name: "i16",
40        parse_priority: 1,
41    },
42    DomainSpec {
43        name: "u16",
44        parse_priority: 1,
45    },
46    DomainSpec {
47        name: "i32",
48        parse_priority: 1,
49    },
50    DomainSpec {
51        name: "u32",
52        parse_priority: 1,
53    },
54    DomainSpec {
55        name: "u64",
56        parse_priority: 1,
57    },
58    DomainSpec {
59        name: "i128",
60        parse_priority: 5,
61    },
62    DomainSpec {
63        name: "u128",
64        parse_priority: 5,
65    },
66    DomainSpec {
67        name: "isize",
68        parse_priority: 1,
69    },
70    DomainSpec {
71        name: "usize",
72        parse_priority: 1,
73    },
74];
75
76/// The library that installs every fixed-width integer domain (`numbers/i8`
77/// through `numbers/i128`, `numbers/u8` through `numbers/u128`, and the
78/// pointer-width `isize`/`usize`): their literal classes and shapes, value
79/// shapes, and the widening promotion edges through the integer lattice.
80///
81/// # Examples
82///
83/// ```
84/// use std::sync::Arc;
85/// use sim_kernel::{Cx, DefaultFactory, NoopEvalPolicy};
86/// use sim_lib_numbers_core::domains;
87/// use sim_lib_numbers_fixed::FixedNumbersLib;
88///
89/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
90/// cx.load_lib(&FixedNumbersLib::new()).unwrap();
91///
92/// let edges = cx.registry().value_promotion_rules();
93/// assert!(edges.iter().any(|rule| {
94///     rule.from_domain == domains::i8() && rule.to_domain == domains::i16()
95/// }));
96/// ```
97pub struct FixedNumbersLib;
98
99impl FixedNumbersLib {
100    /// Construct the fixed-width integer library installer.
101    pub fn new() -> Self {
102        Self
103    }
104}
105
106impl Default for FixedNumbersLib {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112impl Lib for FixedNumbersLib {
113    fn manifest(&self) -> LibManifest {
114        let mut exports = Vec::new();
115        for spec in FIXED_DOMAINS {
116            exports.push(Export::NumberDomain {
117                symbol: domain_symbol(spec),
118                number_domain_id: None,
119            });
120            exports.push(Export::Class {
121                symbol: literal_class_symbol(spec),
122                class_id: None,
123            });
124            exports.push(Export::Shape {
125                symbol: literal_instance_shape_symbol(spec),
126                shape_id: None,
127            });
128            exports.push(Export::Shape {
129                symbol: value_shape_symbol(spec),
130                shape_id: None,
131            });
132        }
133        LibManifest {
134            id: domains::fixed(),
135            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
136            abi: AbiVersion { major: 0, minor: 1 },
137            target: LibTarget::HostRegistered,
138            requires: Vec::<Dependency>::new(),
139            capabilities: Vec::new(),
140            exports,
141        }
142    }
143
144    fn load(&self, _cx: &mut sim_kernel::LoadCx, linker: &mut Linker<'_>) -> Result<()> {
145        for spec in FIXED_DOMAINS {
146            install_domain(linker, spec)?;
147        }
148        for rule in promotion_rules() {
149            linker.promotion_rule(rule);
150        }
151        for rule in value_promotion_rules() {
152            linker.value_promotion_rule(rule);
153        }
154        Ok(())
155    }
156}
157
158fn install_domain(linker: &mut Linker<'_>, spec: DomainSpec) -> Result<()> {
159    let domain = Arc::new(FixedNumberDomain { spec });
160    let literal_shape = Arc::new(NumberLiteralShape::new(
161        domain_symbol(spec),
162        "FixedIntegerLiteral",
163        [
164            "number literal in a fixed integer domain",
165            "matches Expr::Number where domain matches the fixed integer domain",
166        ],
167    ));
168    let literal_class = Arc::new(NumberLiteralClass::new(
169        literal_class_symbol(spec),
170        domain_symbol(spec),
171        "integer",
172        spec.name,
173        literal_shape.clone(),
174    ));
175    let value_shape = Arc::new(DomainNumberValueShape::new(
176        domain_symbol(spec),
177        "FixedIntegerValue",
178        [
179            "number value in a fixed integer domain",
180            "accepts any NumberValue where domain matches the fixed integer domain",
181        ],
182    ));
183    linker.number_domain_value(
184        domain_symbol(spec),
185        DefaultFactory
186            .opaque(domain)
187            .expect("number domain should be boxable"),
188    )?;
189    let class_id = linker.class_value(
190        literal_class_symbol(spec),
191        DefaultFactory
192            .opaque(literal_class.clone())
193            .expect("number literal class should be boxable"),
194    )?;
195    literal_class.set_id(class_id);
196    linker.shape_value(
197        literal_instance_shape_symbol(spec),
198        shape_value(literal_instance_shape_symbol(spec), literal_shape),
199    )?;
200    linker.shape_value(
201        value_shape_symbol(spec),
202        shape_value(value_shape_symbol(spec), value_shape),
203    )?;
204    Ok(())
205}
206
207#[sim_citizen_derive::non_citizen(
208    reason = "fixed-width number-domain marker; reconstruct by loading the fixed number lib",
209    kind = "marker"
210)]
211struct FixedNumberDomain {
212    spec: DomainSpec,
213}
214
215impl NumberDomain for FixedNumberDomain {
216    fn symbol(&self) -> Symbol {
217        domain_symbol(self.spec)
218    }
219
220    fn parse_priority(&self) -> i32 {
221        self.spec.parse_priority
222    }
223
224    fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
225        if text.contains(['.', '/']) {
226            return Ok(None);
227        }
228        let canonical = match self.spec.name {
229            "i8" => text.parse::<i8>().ok().map(|value| value.to_string()),
230            "u8" => text.parse::<u8>().ok().map(|value| value.to_string()),
231            "i16" => text.parse::<i16>().ok().map(|value| value.to_string()),
232            "u16" => text.parse::<u16>().ok().map(|value| value.to_string()),
233            "i32" => text.parse::<i32>().ok().map(|value| value.to_string()),
234            "u32" => text.parse::<u32>().ok().map(|value| value.to_string()),
235            "u64" => text.parse::<u64>().ok().map(|value| value.to_string()),
236            "i128" => text.parse::<i128>().ok().map(|value| value.to_string()),
237            "u128" => text.parse::<u128>().ok().map(|value| value.to_string()),
238            "isize" => text.parse::<isize>().ok().map(|value| value.to_string()),
239            "usize" => text.parse::<usize>().ok().map(|value| value.to_string()),
240            _ => None,
241        };
242        match canonical {
243            Some(canonical) => cx
244                .factory()
245                .number_literal(self.symbol(), canonical)
246                .map(Some),
247            None => Ok(None),
248        }
249    }
250
251    fn encode_literal(
252        &self,
253        cx: &mut sim_kernel::Cx,
254        value: Value,
255    ) -> Result<Option<NumberLiteral>> {
256        match value.object().as_expr(cx)? {
257            Expr::Number(number) if number.domain == self.symbol() => Ok(Some(number)),
258            _ => Ok(None),
259        }
260    }
261}
262
263impl Object for FixedNumberDomain {
264    fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
265        Ok(format!("#<number-domain {}>", domain_symbol(self.spec)))
266    }
267
268    fn as_any(&self) -> &dyn std::any::Any {
269        self
270    }
271}
272
273impl sim_kernel::ObjectCompat for FixedNumberDomain {
274    fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
275        sim_lib_numbers_core::number_domain_class_stub(cx)
276    }
277    fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
278        Ok(Expr::Symbol(domain_symbol(self.spec)))
279    }
280    fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
281        let literal_class = class_surface_or_symbol(cx, literal_class_symbol(self.spec))?;
282        let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol(self.spec))?;
283        let value_shape = shape_surface_or_symbol(cx, value_shape_symbol(self.spec))?;
284        number_domain_table(
285            cx,
286            NumberDomainTableSpec::new(
287                domain_symbol(self.spec),
288                "integer",
289                self.spec.name,
290                self.spec.parse_priority,
291                literal_class,
292                instance_shape,
293                value_shape,
294            ),
295        )
296    }
297    fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
298        Some(self)
299    }
300}
301
302#[derive(Clone, Copy)]
303struct PromotionSpec {
304    from: &'static str,
305    to: &'static str,
306    cost: u16,
307    literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
308    value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
309}
310
311fn promotion_specs() -> Vec<PromotionSpec> {
312    vec![
313        spec("i8", "i16", 1, promote_to_i16, promote_value_to_i16),
314        spec("i16", "i32", 1, promote_to_i32, promote_value_to_i32),
315        spec("i32", "i64", 1, promote_to_i64, promote_value_to_i64),
316        spec("i64", "i128", 1, promote_to_i128, promote_value_to_i128),
317        spec("u8", "u16", 1, promote_to_u16, promote_value_to_u16),
318        spec("u16", "u32", 1, promote_to_u32, promote_value_to_u32),
319        spec("u32", "u64", 1, promote_to_u64, promote_value_to_u64),
320        spec("u64", "u128", 1, promote_to_u128, promote_value_to_u128),
321        spec("u8", "i16", 1, promote_to_i16, promote_value_to_i16),
322        spec("u16", "i32", 1, promote_to_i32, promote_value_to_i32),
323        spec("u32", "i64", 1, promote_to_i64, promote_value_to_i64),
324        spec("u64", "i128", 1, promote_to_i128, promote_value_to_i128),
325        spec("isize", "i128", 1, promote_to_i128, promote_value_to_i128),
326        spec("usize", "u128", 1, promote_to_u128, promote_value_to_u128),
327        spec("i8", "f64", 50, promote_to_f64, promote_value_to_f64),
328        spec("u8", "f64", 50, promote_to_f64, promote_value_to_f64),
329        spec("i16", "f64", 50, promote_to_f64, promote_value_to_f64),
330        spec("u16", "f64", 50, promote_to_f64, promote_value_to_f64),
331        spec("i32", "f64", 50, promote_to_f64, promote_value_to_f64),
332        spec("u32", "f64", 50, promote_to_f64, promote_value_to_f64),
333        spec("i64", "f64", 50, promote_to_f64, promote_value_to_f64),
334        spec("u64", "f64", 50, promote_to_f64, promote_value_to_f64),
335        spec("i128", "f64", 50, promote_to_f64, promote_value_to_f64),
336        spec("u128", "f64", 50, promote_to_f64, promote_value_to_f64),
337        spec("isize", "f64", 50, promote_to_f64, promote_value_to_f64),
338        spec("usize", "f64", 50, promote_to_f64, promote_value_to_f64),
339        spec("i8", "f32", 100, promote_to_f32, promote_value_to_f32),
340        spec("u8", "f32", 100, promote_to_f32, promote_value_to_f32),
341        spec("i16", "f32", 100, promote_to_f32, promote_value_to_f32),
342        spec("u16", "f32", 100, promote_to_f32, promote_value_to_f32),
343        spec("i32", "f32", 100, promote_to_f32, promote_value_to_f32),
344        spec("u32", "f32", 100, promote_to_f32, promote_value_to_f32),
345        spec("i64", "f32", 100, promote_to_f32, promote_value_to_f32),
346        spec("u64", "f32", 100, promote_to_f32, promote_value_to_f32),
347        spec("i128", "f32", 100, promote_to_f32, promote_value_to_f32),
348        spec("u128", "f32", 100, promote_to_f32, promote_value_to_f32),
349        spec("isize", "f32", 100, promote_to_f32, promote_value_to_f32),
350        spec("usize", "f32", 100, promote_to_f32, promote_value_to_f32),
351    ]
352}
353
354fn spec(
355    from: &'static str,
356    to: &'static str,
357    cost: u16,
358    literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
359    value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
360) -> PromotionSpec {
361    PromotionSpec {
362        from,
363        to,
364        cost,
365        literal_convert,
366        value_convert,
367    }
368}
369
370fn promotion_rules() -> Vec<PromotionRule> {
371    promotion_specs()
372        .into_iter()
373        .map(|spec| PromotionRule {
374            from_domain: domains::domain(spec.from),
375            to_domain: domains::domain(spec.to),
376            cost: spec.cost,
377            convert: spec.literal_convert,
378        })
379        .collect()
380}
381
382fn value_promotion_rules() -> Vec<ValuePromotionRule> {
383    promotion_specs()
384        .into_iter()
385        .map(|spec| ValuePromotionRule {
386            from_domain: domains::domain(spec.from),
387            to_domain: domains::domain(spec.to),
388            cost: spec.cost,
389            convert: spec.value_convert,
390        })
391        .collect()
392}
393
394fn promote_value_to_i16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
395    promote_value_to_target(cx, value, "i16", promote_to_i16)
396}
397fn promote_value_to_i32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
398    promote_value_to_target(cx, value, "i32", promote_to_i32)
399}
400fn promote_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
401    promote_value_to_target(cx, value, "i64", promote_to_i64)
402}
403fn promote_value_to_i128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
404    promote_value_to_target(cx, value, "i128", promote_to_i128)
405}
406fn promote_value_to_u16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
407    promote_value_to_target(cx, value, "u16", promote_to_u16)
408}
409fn promote_value_to_u32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
410    promote_value_to_target(cx, value, "u32", promote_to_u32)
411}
412fn promote_value_to_u64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
413    promote_value_to_target(cx, value, "u64", promote_to_u64)
414}
415fn promote_value_to_u128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
416    promote_value_to_target(cx, value, "u128", promote_to_u128)
417}
418fn promote_value_to_f32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
419    promote_value_to_target(cx, value, "f32", promote_to_f32)
420}
421fn promote_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
422    promote_value_to_target(cx, value, "f64", promote_to_f64)
423}
424
425fn promote_value_to_target(
426    cx: &mut sim_kernel::Cx,
427    value: Value,
428    target: &str,
429    convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
430) -> Result<Value> {
431    let Some(number) = cx.number_value_ref(value)? else {
432        return Err(sim_kernel::Error::Eval(format!(
433            "fixed promotion to {} expected a number value",
434            domains::domain(target)
435        )));
436    };
437    let literal = number.literal.ok_or_else(|| {
438        sim_kernel::Error::Eval(format!(
439            "fixed promotion from {} to {} requires a canonical literal form",
440            number.domain,
441            domains::domain(target)
442        ))
443    })?;
444    let promoted = convert(cx, literal)?;
445    cx.factory()
446        .number_literal(promoted.domain, promoted.canonical)
447}
448
449fn promote_to_i16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
450    promote_to_target(number, "i16")
451}
452fn promote_to_i32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
453    promote_to_target(number, "i32")
454}
455fn promote_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
456    promote_to_target(number, "i64")
457}
458fn promote_to_i128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
459    promote_to_target(number, "i128")
460}
461fn promote_to_u16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
462    promote_to_target(number, "u16")
463}
464fn promote_to_u32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
465    promote_to_target(number, "u32")
466}
467fn promote_to_u64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
468    promote_to_target(number, "u64")
469}
470fn promote_to_u128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
471    promote_to_target(number, "u128")
472}
473fn promote_to_f32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
474    promote_to_target(number, "f32")
475}
476fn promote_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
477    promote_to_target(number, "f64")
478}
479
480fn promote_to_target(number: NumberLiteral, target: &str) -> Result<NumberLiteral> {
481    Ok(NumberLiteral {
482        domain: domains::domain(target),
483        canonical: number.canonical,
484    })
485}
486
487fn domain_symbol(spec: DomainSpec) -> Symbol {
488    domains::domain(spec.name)
489}
490
491fn literal_class_symbol(spec: DomainSpec) -> Symbol {
492    domains::literal_class(spec.name)
493}
494
495fn literal_instance_shape_symbol(spec: DomainSpec) -> Symbol {
496    Symbol::qualified(literal_class_symbol(spec).to_string(), "instance-shape")
497}
498
499fn value_shape_symbol(spec: DomainSpec) -> Symbol {
500    domains::value_shape(&domain_symbol(spec))
501}