Skip to main content

sim_kernel/env/
numbers.rs

1use crate::{
2    error::{Error, Result},
3    id::Symbol,
4    number_domain::{
5        NumberBinaryOp, NumberReductionOp, NumberUnaryOp, NumberValueRef, ValueNumberBinaryOp,
6        ValueNumberReductionOp, ValueNumberUnaryOp,
7    },
8    value::Value,
9};
10
11use super::{
12    Cx,
13    selection::{CandidateSelection, ScoredCandidate, choose_best_candidate},
14};
15
16#[derive(Clone)]
17struct PreparedBinaryOp<T> {
18    op: T,
19    left: Value,
20    right: Value,
21}
22
23#[derive(Clone)]
24struct PreparedUnaryOp<T> {
25    op: T,
26    operand: Value,
27}
28
29#[derive(Clone)]
30struct PreparedReductionOp<T> {
31    op: T,
32    operands: Vec<Value>,
33}
34
35enum BinaryCandidate {
36    Literal(PreparedBinaryOp<NumberBinaryOp>),
37    Value(PreparedBinaryOp<ValueNumberBinaryOp>),
38}
39
40enum UnaryCandidate {
41    Literal(PreparedUnaryOp<NumberUnaryOp>),
42    Value(PreparedUnaryOp<ValueNumberUnaryOp>),
43}
44
45enum ReductionCandidate {
46    Literal(PreparedReductionOp<NumberReductionOp>),
47    Value(PreparedReductionOp<ValueNumberReductionOp>),
48}
49
50impl Cx {
51    /// Parses a number literal by trying registered number domains in
52    /// `sorted_number_domains` order.
53    ///
54    /// That ordering is only a parse-disambiguation rule. Codecs that already
55    /// have an explicit source `NumberLiteral` must preserve its domain instead
56    /// of relying on this order to recover the same domain from canonical text.
57    pub fn parse_number_literal(&mut self, text: &str) -> Result<Option<crate::NumberLiteral>> {
58        let domains = self.registry_mut().sorted_number_domains();
59        for (_symbol, value) in domains {
60            let Some(domain) = value.object().as_number_domain() else {
61                continue;
62            };
63            let Some(parsed) = domain.parse_literal(self, text)? else {
64                continue;
65            };
66            let Some(number) = domain.encode_literal(self, parsed)? else {
67                return Err(Error::Eval(format!(
68                    "number domain {} parsed {text} but cannot encode it back to a literal",
69                    domain.symbol()
70                )));
71            };
72            return Ok(Some(number));
73        }
74
75        Ok(None)
76    }
77
78    /// Encodes a value to a number literal via the first domain that accepts it.
79    pub fn encode_number_value(&mut self, value: Value) -> Result<Option<crate::NumberLiteral>> {
80        let domains = self.registry_mut().sorted_number_domains();
81        for (_symbol, domain_value) in domains {
82            let Some(domain) = domain_value.object().as_number_domain() else {
83                continue;
84            };
85            if let Some(number) = domain.encode_literal(self, value.clone())? {
86                return Ok(Some(number));
87            }
88        }
89        Ok(None)
90    }
91
92    /// Resolves a value to a [`NumberValueRef`] carrying its domain and literal.
93    pub fn number_value_ref(&mut self, value: Value) -> Result<Option<NumberValueRef>> {
94        if let Some(number) = value.object().as_number_value() {
95            let domain = number.number_domain(self)?;
96            let literal = number.number_literal(self)?;
97            return Ok(Some(NumberValueRef {
98                domain,
99                value,
100                literal,
101            }));
102        }
103
104        let Some(literal) = self.encode_number_value(value.clone())? else {
105            return Ok(None);
106        };
107        Ok(Some(NumberValueRef {
108            domain: literal.domain.clone(),
109            value,
110            literal: Some(literal),
111        }))
112    }
113
114    /// Applies a binary numeric operator to two values, selecting the best
115    /// registered rule after promoting operands.
116    ///
117    /// Errors with [`Error::AmbiguousNumberDispatch`] when no single rule wins.
118    pub fn apply_value_number_binary_op(
119        &mut self,
120        operator: &Symbol,
121        left: Value,
122        right: Value,
123    ) -> Result<Value> {
124        let left_ref = self.require_number_value(operator, left.clone(), "left")?;
125        let right_ref = self.require_number_value(operator, right.clone(), "right")?;
126        let literal_candidates = self
127            .registry()
128            .number_binary_ops()
129            .iter()
130            .filter(|op| &op.operator == operator)
131            .cloned()
132            .collect::<Vec<_>>();
133        let value_candidates = self
134            .registry()
135            .value_number_binary_ops()
136            .iter()
137            .filter(|op| &op.operator == operator)
138            .cloned()
139            .collect::<Vec<_>>();
140        if literal_candidates.is_empty() && value_candidates.is_empty() {
141            return Err(Error::Eval(format!(
142                "operator {operator} has no registered number rules"
143            )));
144        }
145
146        let mut scored = Vec::new();
147        for op in literal_candidates {
148            let Some((cost, promoted_left, promoted_right)) =
149                self.promote_literal_operands(&op, left_ref.clone(), right_ref.clone())?
150            else {
151                continue;
152            };
153            scored.push(ScoredCandidate {
154                cost,
155                candidate: BinaryCandidate::Literal(PreparedBinaryOp {
156                    op,
157                    left: promoted_left,
158                    right: promoted_right,
159                }),
160            });
161        }
162
163        for op in value_candidates {
164            let Some((cost, promoted_left, promoted_right)) =
165                self.promote_value_operands(&op, left_ref.clone(), right_ref.clone())?
166            else {
167                continue;
168            };
169            scored.push(ScoredCandidate {
170                cost,
171                candidate: BinaryCandidate::Value(PreparedBinaryOp {
172                    op,
173                    left: promoted_left,
174                    right: promoted_right,
175                }),
176            });
177        }
178
179        match choose_best_candidate(scored) {
180            CandidateSelection::None => Err(Error::NoPromotionPath {
181                operator: operator.clone(),
182                left_domain: left_ref.domain,
183                right_domain: right_ref.domain,
184            }),
185            CandidateSelection::Unique(BinaryCandidate::Literal(best)) => {
186                let left = self.expect_literal(best.left)?;
187                let right = self.expect_literal(best.right)?;
188                (best.op.apply)(self, left, right)
189            }
190            CandidateSelection::Unique(BinaryCandidate::Value(best)) => {
191                (best.op.apply)(self, best.left, best.right)
192            }
193            CandidateSelection::Ambiguous(best) => Err(Error::AmbiguousNumberDispatch {
194                operator: operator.clone(),
195                candidates: best
196                    .into_iter()
197                    .map(|candidate| match candidate {
198                        BinaryCandidate::Literal(candidate) => {
199                            (candidate.op.left_domain, candidate.op.right_domain)
200                        }
201                        BinaryCandidate::Value(candidate) => {
202                            (candidate.op.left_domain, candidate.op.right_domain)
203                        }
204                    })
205                    .collect(),
206            }),
207        }
208    }
209
210    /// Applies a binary numeric operator to two literals.
211    ///
212    /// Convenience over [`apply_value_number_binary_op`](Cx::apply_value_number_binary_op).
213    pub fn apply_number_binary_op(
214        &mut self,
215        operator: &Symbol,
216        left: crate::NumberLiteral,
217        right: crate::NumberLiteral,
218    ) -> Result<Value> {
219        let left_value = self.factory().number_literal(left.domain, left.canonical)?;
220        let right_value = self
221            .factory()
222            .number_literal(right.domain, right.canonical)?;
223        self.apply_value_number_binary_op(operator, left_value, right_value)
224    }
225
226    /// Applies a unary numeric operator to a value, selecting the best
227    /// registered rule after promoting the operand.
228    pub fn apply_value_number_unary_op(
229        &mut self,
230        operator: &Symbol,
231        operand: Value,
232    ) -> Result<Value> {
233        let operand_ref = self.require_number_value(operator, operand.clone(), "operand")?;
234        let literal_candidates = self
235            .registry()
236            .number_unary_ops()
237            .iter()
238            .filter(|op| &op.operator == operator)
239            .cloned()
240            .collect::<Vec<_>>();
241        let value_candidates = self
242            .registry()
243            .value_number_unary_ops()
244            .iter()
245            .filter(|op| &op.operator == operator)
246            .cloned()
247            .collect::<Vec<_>>();
248        if literal_candidates.is_empty() && value_candidates.is_empty() {
249            return Err(Error::Eval(format!(
250                "operator {operator} has no registered number rules"
251            )));
252        }
253
254        let mut scored = Vec::new();
255        for op in literal_candidates {
256            let Some((cost, promoted_operand)) =
257                self.promote_literal_operand(operand_ref.clone(), &op.operand_domain)?
258            else {
259                continue;
260            };
261            scored.push(ScoredCandidate {
262                cost: cost as u32 + op.cost as u32,
263                candidate: UnaryCandidate::Literal(PreparedUnaryOp {
264                    op,
265                    operand: promoted_operand,
266                }),
267            });
268        }
269
270        for op in value_candidates {
271            let Some((cost, promoted_operand)) =
272                self.promote_number_value(operand_ref.clone(), &op.operand_domain)?
273            else {
274                continue;
275            };
276            scored.push(ScoredCandidate {
277                cost: cost as u32 + op.cost as u32,
278                candidate: UnaryCandidate::Value(PreparedUnaryOp {
279                    op,
280                    operand: promoted_operand.value,
281                }),
282            });
283        }
284
285        match choose_best_candidate(scored) {
286            CandidateSelection::None => Err(Error::Eval(format!(
287                "operator {operator} has no registered number rule for {}",
288                operand_ref.domain
289            ))),
290            CandidateSelection::Unique(UnaryCandidate::Literal(best)) => {
291                let operand = self.expect_literal(best.operand)?;
292                (best.op.apply)(self, operand)
293            }
294            CandidateSelection::Unique(UnaryCandidate::Value(best)) => {
295                (best.op.apply)(self, best.operand)
296            }
297            CandidateSelection::Ambiguous(best) => Err(Error::AmbiguousNumberDispatch {
298                operator: operator.clone(),
299                candidates: best
300                    .into_iter()
301                    .map(|candidate| match candidate {
302                        UnaryCandidate::Literal(candidate) => (
303                            candidate.op.operand_domain.clone(),
304                            candidate.op.operand_domain,
305                        ),
306                        UnaryCandidate::Value(candidate) => (
307                            candidate.op.operand_domain.clone(),
308                            candidate.op.operand_domain,
309                        ),
310                    })
311                    .collect(),
312            }),
313        }
314    }
315
316    /// Applies a unary numeric operator to a literal.
317    ///
318    /// Convenience over [`apply_value_number_unary_op`](Cx::apply_value_number_unary_op).
319    pub fn apply_number_unary_op(
320        &mut self,
321        operator: &Symbol,
322        operand: crate::NumberLiteral,
323    ) -> Result<Value> {
324        let operand = self
325            .factory()
326            .number_literal(operand.domain, operand.canonical)?;
327        self.apply_value_number_unary_op(operator, operand)
328    }
329
330    /// Applies a reduction numeric operator across many values, selecting the
331    /// best registered rule after promoting all operands to a common domain.
332    pub fn apply_value_number_reduction_op(
333        &mut self,
334        operator: &Symbol,
335        operands: Vec<Value>,
336    ) -> Result<Value> {
337        let operand_refs = operands
338            .into_iter()
339            .map(|operand| self.require_number_value(operator, operand, "operand"))
340            .collect::<Result<Vec<_>>>()?;
341        let literal_candidates = self
342            .registry()
343            .number_reduction_ops()
344            .iter()
345            .filter(|op| &op.operator == operator)
346            .cloned()
347            .collect::<Vec<_>>();
348        let value_candidates = self
349            .registry()
350            .value_number_reduction_ops()
351            .iter()
352            .filter(|op| &op.operator == operator)
353            .cloned()
354            .collect::<Vec<_>>();
355        if literal_candidates.is_empty() && value_candidates.is_empty() {
356            return Err(Error::Eval(format!(
357                "operator {operator} has no registered number rules"
358            )));
359        }
360
361        let mut scored = Vec::new();
362        for op in literal_candidates {
363            let Some((cost, promoted_operands)) = self
364                .promote_literal_operands_for_reduction(operand_refs.clone(), &op.operand_domain)?
365            else {
366                continue;
367            };
368            scored.push(ScoredCandidate {
369                cost: cost as u32 + op.cost as u32,
370                candidate: ReductionCandidate::Literal(PreparedReductionOp {
371                    op,
372                    operands: promoted_operands,
373                }),
374            });
375        }
376
377        for op in value_candidates {
378            let Some((cost, promoted_operands)) =
379                self.promote_number_values(operand_refs.clone(), &op.operand_domain)?
380            else {
381                continue;
382            };
383            scored.push(ScoredCandidate {
384                cost: cost as u32 + op.cost as u32,
385                candidate: ReductionCandidate::Value(PreparedReductionOp {
386                    op,
387                    operands: promoted_operands
388                        .into_iter()
389                        .map(|item| item.value)
390                        .collect(),
391                }),
392            });
393        }
394
395        let domains = operand_refs
396            .iter()
397            .map(|operand| operand.domain.to_string())
398            .collect::<Vec<_>>()
399            .join(", ");
400        match choose_best_candidate(scored) {
401            CandidateSelection::None => Err(Error::Eval(format!(
402                "operator {operator} has no registered number rule for [{domains}]"
403            ))),
404            CandidateSelection::Unique(ReductionCandidate::Literal(best)) => {
405                let operands = best
406                    .operands
407                    .into_iter()
408                    .map(|operand| self.expect_literal(operand))
409                    .collect::<Result<Vec<_>>>()?;
410                (best.op.apply)(self, operands)
411            }
412            CandidateSelection::Unique(ReductionCandidate::Value(best)) => {
413                (best.op.apply)(self, best.operands)
414            }
415            CandidateSelection::Ambiguous(best) => Err(Error::AmbiguousNumberDispatch {
416                operator: operator.clone(),
417                candidates: best
418                    .into_iter()
419                    .map(|candidate| match candidate {
420                        ReductionCandidate::Literal(candidate) => (
421                            candidate.op.operand_domain.clone(),
422                            candidate.op.operand_domain,
423                        ),
424                        ReductionCandidate::Value(candidate) => (
425                            candidate.op.operand_domain.clone(),
426                            candidate.op.operand_domain,
427                        ),
428                    })
429                    .collect(),
430            }),
431        }
432    }
433
434    /// Applies a reduction numeric operator across many literals.
435    ///
436    /// Convenience over [`apply_value_number_reduction_op`](Cx::apply_value_number_reduction_op).
437    pub fn apply_number_reduction_op(
438        &mut self,
439        operator: &Symbol,
440        operands: Vec<crate::NumberLiteral>,
441    ) -> Result<Value> {
442        let operands = operands
443            .into_iter()
444            .map(|operand| {
445                self.factory()
446                    .number_literal(operand.domain, operand.canonical)
447            })
448            .collect::<Result<Vec<_>>>()?;
449        self.apply_value_number_reduction_op(operator, operands)
450    }
451
452    fn require_number_value(
453        &mut self,
454        operator: &Symbol,
455        value: Value,
456        side: &str,
457    ) -> Result<NumberValueRef> {
458        self.number_value_ref(value)?.ok_or_else(|| {
459            Error::Eval(format!(
460                "operator {operator} {side} operand is not a registered number"
461            ))
462        })
463    }
464
465    fn expect_literal(&mut self, value: Value) -> Result<crate::NumberLiteral> {
466        self.number_value_ref(value)?.and_then(|number| number.literal).ok_or_else(|| {
467            Error::Eval("value-level numeric dispatch selected a literal-only rule for a non-literal value".to_owned())
468        })
469    }
470}