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    descriptor = "numbers/fixed"
211)]
212struct FixedNumberDomain {
213    spec: DomainSpec,
214}
215
216impl NumberDomain for FixedNumberDomain {
217    fn symbol(&self) -> Symbol {
218        domain_symbol(self.spec)
219    }
220
221    fn parse_priority(&self) -> i32 {
222        self.spec.parse_priority
223    }
224
225    fn parse_literal(&self, cx: &mut sim_kernel::Cx, text: &str) -> Result<Option<Value>> {
226        if text.contains(['.', '/']) {
227            return Ok(None);
228        }
229        let canonical = match self.spec.name {
230            "i8" => text.parse::<i8>().ok().map(|value| value.to_string()),
231            "u8" => text.parse::<u8>().ok().map(|value| value.to_string()),
232            "i16" => text.parse::<i16>().ok().map(|value| value.to_string()),
233            "u16" => text.parse::<u16>().ok().map(|value| value.to_string()),
234            "i32" => text.parse::<i32>().ok().map(|value| value.to_string()),
235            "u32" => text.parse::<u32>().ok().map(|value| value.to_string()),
236            "u64" => text.parse::<u64>().ok().map(|value| value.to_string()),
237            "i128" => text.parse::<i128>().ok().map(|value| value.to_string()),
238            "u128" => text.parse::<u128>().ok().map(|value| value.to_string()),
239            "isize" => text.parse::<isize>().ok().map(|value| value.to_string()),
240            "usize" => text.parse::<usize>().ok().map(|value| value.to_string()),
241            _ => None,
242        };
243        match canonical {
244            Some(canonical) => cx
245                .factory()
246                .number_literal(self.symbol(), canonical)
247                .map(Some),
248            None => Ok(None),
249        }
250    }
251
252    fn encode_literal(
253        &self,
254        cx: &mut sim_kernel::Cx,
255        value: Value,
256    ) -> Result<Option<NumberLiteral>> {
257        match value.object().as_expr(cx)? {
258            Expr::Number(number) if number.domain == self.symbol() => Ok(Some(number)),
259            _ => Ok(None),
260        }
261    }
262}
263
264impl Object for FixedNumberDomain {
265    fn display(&self, _cx: &mut sim_kernel::Cx) -> Result<String> {
266        Ok(format!("#<number-domain {}>", domain_symbol(self.spec)))
267    }
268
269    fn as_any(&self) -> &dyn std::any::Any {
270        self
271    }
272}
273
274impl sim_kernel::ObjectCompat for FixedNumberDomain {
275    fn class(&self, cx: &mut sim_kernel::Cx) -> Result<sim_kernel::ClassRef> {
276        sim_lib_numbers_core::number_domain_class_stub(cx)
277    }
278    fn as_expr(&self, _cx: &mut sim_kernel::Cx) -> Result<Expr> {
279        Ok(Expr::Symbol(domain_symbol(self.spec)))
280    }
281    fn as_table(&self, cx: &mut sim_kernel::Cx) -> Result<Value> {
282        let literal_class = class_surface_or_symbol(cx, literal_class_symbol(self.spec))?;
283        let instance_shape = shape_surface_or_symbol(cx, literal_instance_shape_symbol(self.spec))?;
284        let value_shape = shape_surface_or_symbol(cx, value_shape_symbol(self.spec))?;
285        number_domain_table(
286            cx,
287            NumberDomainTableSpec::new(
288                domain_symbol(self.spec),
289                "integer",
290                self.spec.name,
291                self.spec.parse_priority,
292                literal_class,
293                instance_shape,
294                value_shape,
295            ),
296        )
297    }
298    fn as_number_domain(&self) -> Option<&dyn NumberDomain> {
299        Some(self)
300    }
301}
302
303#[derive(Clone, Copy)]
304struct PromotionSpec {
305    from: &'static str,
306    to: &'static str,
307    cost: u16,
308    literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
309    value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
310}
311
312fn promotion_specs() -> Vec<PromotionSpec> {
313    vec![
314        spec("i8", "i16", 1, promote_to_i16, promote_value_to_i16),
315        spec("i16", "i32", 1, promote_to_i32, promote_value_to_i32),
316        spec("i32", "i64", 1, promote_to_i64, promote_value_to_i64),
317        spec("i64", "i128", 1, promote_to_i128, promote_value_to_i128),
318        spec("u8", "u16", 1, promote_to_u16, promote_value_to_u16),
319        spec("u16", "u32", 1, promote_to_u32, promote_value_to_u32),
320        spec("u32", "u64", 1, promote_to_u64, promote_value_to_u64),
321        spec("u64", "u128", 1, promote_to_u128, promote_value_to_u128),
322        spec("u8", "i16", 1, promote_to_i16, promote_value_to_i16),
323        spec("u16", "i32", 1, promote_to_i32, promote_value_to_i32),
324        spec("u32", "i64", 1, promote_to_i64, promote_value_to_i64),
325        spec("u64", "i128", 1, promote_to_i128, promote_value_to_i128),
326        spec("isize", "i128", 1, promote_to_i128, promote_value_to_i128),
327        spec("usize", "u128", 1, promote_to_u128, promote_value_to_u128),
328        spec("i8", "f64", 50, promote_to_f64, promote_value_to_f64),
329        spec("u8", "f64", 50, promote_to_f64, promote_value_to_f64),
330        spec("i16", "f64", 50, promote_to_f64, promote_value_to_f64),
331        spec("u16", "f64", 50, promote_to_f64, promote_value_to_f64),
332        spec("i32", "f64", 50, promote_to_f64, promote_value_to_f64),
333        spec("u32", "f64", 50, promote_to_f64, promote_value_to_f64),
334        spec("i64", "f64", 50, promote_to_f64, promote_value_to_f64),
335        spec("u64", "f64", 50, promote_to_f64, promote_value_to_f64),
336        spec("i128", "f64", 50, promote_to_f64, promote_value_to_f64),
337        spec("u128", "f64", 50, promote_to_f64, promote_value_to_f64),
338        spec("isize", "f64", 50, promote_to_f64, promote_value_to_f64),
339        spec("usize", "f64", 50, promote_to_f64, promote_value_to_f64),
340        spec("i8", "f32", 100, promote_to_f32, promote_value_to_f32),
341        spec("u8", "f32", 100, promote_to_f32, promote_value_to_f32),
342        spec("i16", "f32", 100, promote_to_f32, promote_value_to_f32),
343        spec("u16", "f32", 100, promote_to_f32, promote_value_to_f32),
344        spec("i32", "f32", 100, promote_to_f32, promote_value_to_f32),
345        spec("u32", "f32", 100, promote_to_f32, promote_value_to_f32),
346        spec("i64", "f32", 100, promote_to_f32, promote_value_to_f32),
347        spec("u64", "f32", 100, promote_to_f32, promote_value_to_f32),
348        spec("i128", "f32", 100, promote_to_f32, promote_value_to_f32),
349        spec("u128", "f32", 100, promote_to_f32, promote_value_to_f32),
350        spec("isize", "f32", 100, promote_to_f32, promote_value_to_f32),
351        spec("usize", "f32", 100, promote_to_f32, promote_value_to_f32),
352    ]
353}
354
355fn spec(
356    from: &'static str,
357    to: &'static str,
358    cost: u16,
359    literal_convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
360    value_convert: fn(&mut sim_kernel::Cx, Value) -> Result<Value>,
361) -> PromotionSpec {
362    PromotionSpec {
363        from,
364        to,
365        cost,
366        literal_convert,
367        value_convert,
368    }
369}
370
371fn promotion_rules() -> Vec<PromotionRule> {
372    promotion_specs()
373        .into_iter()
374        .map(|spec| PromotionRule {
375            from_domain: domains::domain(spec.from),
376            to_domain: domains::domain(spec.to),
377            cost: spec.cost,
378            convert: spec.literal_convert,
379        })
380        .collect()
381}
382
383fn value_promotion_rules() -> Vec<ValuePromotionRule> {
384    promotion_specs()
385        .into_iter()
386        .map(|spec| ValuePromotionRule {
387            from_domain: domains::domain(spec.from),
388            to_domain: domains::domain(spec.to),
389            cost: spec.cost,
390            convert: spec.value_convert,
391        })
392        .collect()
393}
394
395fn promote_value_to_i16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
396    promote_value_to_target(cx, value, "i16", promote_to_i16)
397}
398fn promote_value_to_i32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
399    promote_value_to_target(cx, value, "i32", promote_to_i32)
400}
401fn promote_value_to_i64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
402    promote_value_to_target(cx, value, "i64", promote_to_i64)
403}
404fn promote_value_to_i128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
405    promote_value_to_target(cx, value, "i128", promote_to_i128)
406}
407fn promote_value_to_u16(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
408    promote_value_to_target(cx, value, "u16", promote_to_u16)
409}
410fn promote_value_to_u32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
411    promote_value_to_target(cx, value, "u32", promote_to_u32)
412}
413fn promote_value_to_u64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
414    promote_value_to_target(cx, value, "u64", promote_to_u64)
415}
416fn promote_value_to_u128(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
417    promote_value_to_target(cx, value, "u128", promote_to_u128)
418}
419fn promote_value_to_f32(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
420    promote_value_to_target(cx, value, "f32", promote_to_f32)
421}
422fn promote_value_to_f64(cx: &mut sim_kernel::Cx, value: Value) -> Result<Value> {
423    promote_value_to_target(cx, value, "f64", promote_to_f64)
424}
425
426fn promote_value_to_target(
427    cx: &mut sim_kernel::Cx,
428    value: Value,
429    target: &str,
430    convert: fn(&mut sim_kernel::Cx, NumberLiteral) -> Result<NumberLiteral>,
431) -> Result<Value> {
432    let Some(number) = cx.number_value_ref(value)? else {
433        return Err(sim_kernel::Error::Eval(format!(
434            "fixed promotion to {} expected a number value",
435            domains::domain(target)
436        )));
437    };
438    let literal = number.literal.ok_or_else(|| {
439        sim_kernel::Error::Eval(format!(
440            "fixed promotion from {} to {} requires a canonical literal form",
441            number.domain,
442            domains::domain(target)
443        ))
444    })?;
445    let promoted = convert(cx, literal)?;
446    cx.factory()
447        .number_literal(promoted.domain, promoted.canonical)
448}
449
450fn promote_to_i16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
451    promote_to_target(number, "i16")
452}
453fn promote_to_i32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
454    promote_to_target(number, "i32")
455}
456fn promote_to_i64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
457    promote_to_target(number, "i64")
458}
459fn promote_to_i128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
460    promote_to_target(number, "i128")
461}
462fn promote_to_u16(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
463    promote_to_target(number, "u16")
464}
465fn promote_to_u32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
466    promote_to_target(number, "u32")
467}
468fn promote_to_u64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
469    promote_to_target(number, "u64")
470}
471fn promote_to_u128(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
472    promote_to_target(number, "u128")
473}
474fn promote_to_f32(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
475    promote_to_target(number, "f32")
476}
477fn promote_to_f64(_cx: &mut sim_kernel::Cx, number: NumberLiteral) -> Result<NumberLiteral> {
478    promote_to_target(number, "f64")
479}
480
481fn promote_to_target(number: NumberLiteral, target: &str) -> Result<NumberLiteral> {
482    Ok(NumberLiteral {
483        domain: domains::domain(target),
484        canonical: number.canonical,
485    })
486}
487
488fn domain_symbol(spec: DomainSpec) -> Symbol {
489    domains::domain(spec.name)
490}
491
492fn literal_class_symbol(spec: DomainSpec) -> Symbol {
493    domains::literal_class(spec.name)
494}
495
496fn literal_instance_shape_symbol(spec: DomainSpec) -> Symbol {
497    Symbol::qualified(literal_class_symbol(spec).to_string(), "instance-shape")
498}
499
500fn value_shape_symbol(spec: DomainSpec) -> Symbol {
501    domains::value_shape(&domain_symbol(spec))
502}