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