Skip to main content

sim_lib_numbers_core/
scalar.rs

1//! Scalar-domain spec, literal matcher, and the shared op-loop installer.
2//!
3//! Each scalar number domain crate repeats the same `load()` registration loop
4//! (binary/unary/reduction ops, each in both literal and value form). This is
5//! the shared installer: a domain crate describes its ops as data
6//! ([`ScalarOps`]) and calls [`install_scalar_ops`].
7
8use sim_kernel::{
9    Cx, Expr, Factory, Linker, NumberBinaryOp, NumberLiteral, NumberReductionOp, NumberUnaryOp,
10    Symbol, Value, ValueNumberBinaryOp, ValueNumberReductionOp, ValueNumberUnaryOp,
11};
12
13/// The `ObjectCompat::class` stub every number-domain object returns: the
14/// registered `core/NumberDomain` class, or a fresh stub for it. This body was
15/// byte-identical across all ~12 numeric-domain crates before OVERLAP6.09; each
16/// domain's `fn class` now delegates here.
17pub fn number_domain_class_stub(cx: &mut Cx) -> sim_kernel::Result<sim_kernel::ClassRef> {
18    if let Some(value) = cx
19        .registry()
20        .class_by_symbol(&Symbol::qualified("core", "NumberDomain"))
21    {
22        return Ok(value.clone());
23    }
24    sim_kernel::DefaultFactory.class_stub(
25        sim_kernel::CORE_NUMBER_DOMAIN_CLASS_ID,
26        Symbol::qualified("core", "NumberDomain"),
27    )
28}
29
30use crate::domains;
31
32/// Tests whether an expression is a literal in some scalar domain.
33pub trait ScalarLiteralMatcher {
34    /// Whether `expr` is a number literal this matcher accepts.
35    fn matches_expr(&self, expr: &Expr) -> bool;
36}
37
38/// A matcher accepting `Expr::Number` literals in exactly one domain.
39///
40/// # Examples
41///
42/// ```
43/// use sim_kernel::{Expr, NumberLiteral};
44/// use sim_lib_numbers_core::{DomainLiteralMatcher, ScalarLiteralMatcher, domains};
45///
46/// let matcher = DomainLiteralMatcher::new(domains::i64());
47/// let lit = Expr::Number(NumberLiteral {
48///     domain: domains::i64(),
49///     canonical: "42".to_owned(),
50/// });
51/// assert!(matcher.matches_expr(&lit));
52/// assert!(!matcher.matches_expr(&Expr::String("42".to_owned())));
53/// ```
54pub struct DomainLiteralMatcher {
55    domain: Symbol,
56}
57
58impl DomainLiteralMatcher {
59    /// Build a matcher accepting only literals in `domain`.
60    pub fn new(domain: Symbol) -> Self {
61        Self { domain }
62    }
63
64    /// The domain this matcher accepts.
65    pub fn domain(&self) -> &Symbol {
66        &self.domain
67    }
68}
69
70impl ScalarLiteralMatcher for DomainLiteralMatcher {
71    fn matches_expr(&self, expr: &Expr) -> bool {
72        matches!(expr, Expr::Number(number) if number.domain == self.domain)
73    }
74}
75
76/// Static identity of a scalar number domain (data only).
77///
78/// A concrete domain crate fills this in once and derives its stable
79/// literal-class and instance-shape symbols from it, rather than spelling them
80/// out by hand.
81///
82/// # Examples
83///
84/// ```
85/// use sim_lib_numbers_core::{ScalarDomainSpec, domains};
86///
87/// let spec = ScalarDomainSpec {
88///     domain: domains::i64(),
89///     numeric_family: "integer",
90///     canonical_form: "i64",
91///     parse_priority: 20,
92/// };
93/// assert_eq!(spec.literal_class_symbol(), domains::literal_class("i64"));
94/// ```
95pub struct ScalarDomainSpec {
96    /// The domain symbol, e.g. `numbers/i64`.
97    pub domain: Symbol,
98    /// The numeric family label, e.g. `"integer"`.
99    pub numeric_family: &'static str,
100    /// The canonical form label, e.g. `"i64"`.
101    pub canonical_form: &'static str,
102    /// The literal parse priority.
103    pub parse_priority: i32,
104}
105
106impl ScalarDomainSpec {
107    /// A literal matcher for this domain.
108    pub fn matcher(&self) -> DomainLiteralMatcher {
109        DomainLiteralMatcher::new(self.domain.clone())
110    }
111
112    /// The literal class symbol, e.g. `numbers/i64-literal`.
113    pub fn literal_class_symbol(&self) -> Symbol {
114        domains::literal_class(self.canonical_form)
115    }
116
117    /// The literal instance-shape symbol, e.g. `numbers/i64-literal/instance-shape`.
118    pub fn literal_instance_shape_symbol(&self) -> Symbol {
119        Symbol::qualified(self.literal_class_symbol().to_string(), "instance-shape")
120    }
121}
122
123/// One binary op in both literal and value form.
124pub struct ScalarBinaryOp {
125    /// The operator symbol this op implements (e.g. `+`).
126    pub operator: Symbol,
127    /// Dispatch cost of the literal (parsed-form) implementation.
128    pub literal_cost: u16,
129    /// The literal-form implementation over two same-domain number literals.
130    pub literal_apply: fn(&mut Cx, NumberLiteral, NumberLiteral) -> sim_kernel::Result<Value>,
131    /// Dispatch cost of the value (opaque-object) implementation.
132    pub value_cost: u16,
133    /// The value-form implementation over two same-domain number values.
134    pub value_apply: fn(&mut Cx, Value, Value) -> sim_kernel::Result<Value>,
135}
136
137/// One unary op in both literal and value form.
138pub struct ScalarUnaryOp {
139    /// The operator symbol this op implements (e.g. `neg`).
140    pub operator: Symbol,
141    /// Dispatch cost of the literal (parsed-form) implementation.
142    pub literal_cost: u16,
143    /// The literal-form implementation over one number literal.
144    pub literal_apply: fn(&mut Cx, NumberLiteral) -> sim_kernel::Result<Value>,
145    /// Dispatch cost of the value (opaque-object) implementation.
146    pub value_cost: u16,
147    /// The value-form implementation over one number value.
148    pub value_apply: fn(&mut Cx, Value) -> sim_kernel::Result<Value>,
149}
150
151/// One reduction op in both literal and value form.
152pub struct ScalarReductionOp {
153    /// The operator symbol this op implements (e.g. `sum`).
154    pub operator: Symbol,
155    /// Dispatch cost of the literal (parsed-form) implementation.
156    pub literal_cost: u16,
157    /// The literal-form implementation over a vector of number literals.
158    pub literal_apply: fn(&mut Cx, Vec<NumberLiteral>) -> sim_kernel::Result<Value>,
159    /// Dispatch cost of the value (opaque-object) implementation.
160    pub value_cost: u16,
161    /// The value-form implementation over a vector of number values.
162    pub value_apply: fn(&mut Cx, Vec<Value>) -> sim_kernel::Result<Value>,
163}
164
165/// The full op set for one scalar domain.
166pub struct ScalarOps {
167    /// The domain all ops in this set operate within.
168    pub domain: Symbol,
169    /// The binary ops to register for this domain.
170    pub binary: Vec<ScalarBinaryOp>,
171    /// The unary ops to register for this domain.
172    pub unary: Vec<ScalarUnaryOp>,
173    /// The reduction ops to register for this domain.
174    pub reduction: Vec<ScalarReductionOp>,
175}
176
177/// Register every op in `ops` (literal and value form) against `linker`.
178pub fn install_scalar_ops(linker: &mut Linker<'_>, ops: &ScalarOps) {
179    for op in &ops.binary {
180        linker.number_binary_op(NumberBinaryOp {
181            operator: op.operator.clone(),
182            left_domain: ops.domain.clone(),
183            right_domain: ops.domain.clone(),
184            cost: op.literal_cost,
185            apply: op.literal_apply,
186        });
187        linker.value_number_binary_op(ValueNumberBinaryOp {
188            operator: op.operator.clone(),
189            left_domain: ops.domain.clone(),
190            right_domain: ops.domain.clone(),
191            cost: op.value_cost,
192            apply: op.value_apply,
193        });
194    }
195    for op in &ops.unary {
196        linker.number_unary_op(NumberUnaryOp {
197            operator: op.operator.clone(),
198            operand_domain: ops.domain.clone(),
199            cost: op.literal_cost,
200            apply: op.literal_apply,
201        });
202        linker.value_number_unary_op(ValueNumberUnaryOp {
203            operator: op.operator.clone(),
204            operand_domain: ops.domain.clone(),
205            cost: op.value_cost,
206            apply: op.value_apply,
207        });
208    }
209    for op in &ops.reduction {
210        linker.number_reduction_op(NumberReductionOp {
211            operator: op.operator.clone(),
212            operand_domain: ops.domain.clone(),
213            cost: op.literal_cost,
214            apply: op.literal_apply,
215        });
216        linker.value_number_reduction_op(ValueNumberReductionOp {
217            operator: op.operator.clone(),
218            operand_domain: ops.domain.clone(),
219            cost: op.value_cost,
220            apply: op.value_apply,
221        });
222    }
223}