Skip to main content

pmcp_workbook_runtime/sheet_ir/
eval_value.rs

1//! The range-capable value the 13 whitelisted-function bodies consume (finding #4).
2//!
3//! The scalar evaluator ([`super::scalar_eval`]) evaluates ONLY scalar leaf
4//! arithmetic — a single [`CellValue`] scalar per leaf. But the Excel-semantics
5//! layer ([`super::semantics`]) needs to express RANGE arguments: `SUM(B2:B10)`,
6//! `VLOOKUP(table_array, …)`, `INDEX(range, n)`, `MATCH(value, range)`.
7//! [`EvalValue`] is the value the function bodies receive so they can accept
8//! either a scalar OR a rectangular range of cells. Ranges NEVER enter the
9//! scalar evaluator — they stay a semantics-layer concern (D-04, finding #4).
10
11use serde::Serialize;
12
13use super::value::CellValue;
14
15/// A range-capable value: either a single [`CellValue`] scalar, or a
16/// rectangular range of cells (`Vec<Vec<CellValue>>`, row-major).
17///
18/// Derive note: `PartialEq` but NOT `Eq` — [`CellValue::Number`] carries an
19/// `f64`, exactly as the underlying [`CellValue`].
20#[derive(Debug, Clone, PartialEq, Serialize, schemars::JsonSchema)]
21pub enum EvalValue {
22    /// A single scalar cell value (the only thing that ever lowers to the evaluator).
23    Scalar(CellValue),
24    /// A rectangular range of cells, row-major (`rows[r][c]`). Used by
25    /// `SUM`/`SUMIF`/`VLOOKUP`/`INDEX`/`MATCH`; NEVER enters the evaluator (finding #4).
26    Range(Vec<Vec<CellValue>>),
27}
28
29impl EvalValue {
30    /// View this value as a scalar, if it is one.
31    pub fn as_scalar(&self) -> Option<&CellValue> {
32        match self {
33            EvalValue::Scalar(cv) => Some(cv),
34            EvalValue::Range(_) => None,
35        }
36    }
37
38    /// View this value as a range, if it is one.
39    pub fn as_range(&self) -> Option<&Vec<Vec<CellValue>>> {
40        match self {
41            EvalValue::Range(rows) => Some(rows),
42            EvalValue::Scalar(_) => None,
43        }
44    }
45
46    /// Construct a scalar [`EvalValue`] from a [`CellValue`].
47    pub fn scalar(cv: CellValue) -> EvalValue {
48        EvalValue::Scalar(cv)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::excel_error::ExcelError;
56
57    #[test]
58    fn scalar_helper_and_as_scalar_round_trip() {
59        let v = EvalValue::scalar(CellValue::Number(42.0));
60        assert_eq!(v.as_scalar(), Some(&CellValue::Number(42.0)));
61        assert_eq!(v.as_range(), None);
62    }
63
64    #[test]
65    fn as_range_returns_the_rows() {
66        let rows = vec![
67            vec![CellValue::Number(1.0), CellValue::Number(2.0)],
68            vec![CellValue::Number(3.0), CellValue::Number(4.0)],
69        ];
70        let v = EvalValue::Range(rows.clone());
71        assert_eq!(v.as_range(), Some(&rows));
72        assert_eq!(v.as_scalar(), None);
73    }
74
75    #[test]
76    fn scalar_can_carry_an_error() {
77        let v = EvalValue::Scalar(CellValue::Error(ExcelError::Na));
78        assert_eq!(v.as_scalar(), Some(&CellValue::Error(ExcelError::Na)));
79    }
80
81    #[test]
82    fn serializes_externally_tagged() {
83        let v = EvalValue::Scalar(CellValue::Number(238.728));
84        let j = serde_json::to_value(&v).unwrap();
85        assert_eq!(j["Scalar"]["Number"], 238.728);
86    }
87}