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}