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}