Skip to main content

improv_core_model/
value.rs

1//! Value types and runtime values, including error values that propagate
2//! through the dataflow graph.
3
4use crate::ids::MeasureId;
5use serde::{Deserialize, Serialize};
6
7/// The declared type of a measure.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum ValueType {
10    Number,
11    Boolean,
12    Text,
13    DateTime,
14    Enum,
15}
16
17/// A runtime value in a cell. `Error` lets structural/runtime errors flow
18/// through the computation graph as first-class values (as the design requires).
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum Value {
21    Number(f64),
22    Boolean(bool),
23    Text(String),
24    DateTime(chrono::DateTime<chrono::Utc>),
25    /// Index into an enum domain.
26    Enum(u32),
27    Error(ValueError),
28}
29
30impl Value {
31    pub fn type_of(&self) -> Option<ValueType> {
32        match self {
33            Value::Number(_) => Some(ValueType::Number),
34            Value::Boolean(_) => Some(ValueType::Boolean),
35            Value::Text(_) => Some(ValueType::Text),
36            Value::DateTime(_) => Some(ValueType::DateTime),
37            Value::Enum(_) => Some(ValueType::Enum),
38            Value::Error(_) => None,
39        }
40    }
41
42    pub fn is_error(&self) -> bool {
43        matches!(self, Value::Error(_))
44    }
45
46    pub fn as_number(&self) -> Option<f64> {
47        match self {
48            Value::Number(n) => Some(*n),
49            _ => None,
50        }
51    }
52}
53
54/// The kind of an error value. The first four are the spreadsheet errors a
55/// formula produces (named as Excel shows them, see [`ValueErrorKind::code`]);
56/// the rest carry Improv's own structural and external-function failures.
57///
58/// Variant names are persisted (an error literal in a stored formula), so
59/// existing names keep their meaning: `DivisionByZero` is `#DIV/0!` and
60/// `TypeMismatch` is `#VALUE!`.
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
62pub enum ValueErrorKind {
63    /// `#VALUE!`: wrong value type for an operation.
64    TypeMismatch,
65    /// Dimensions don't line up (a structural error).
66    DimensionMismatch,
67    /// `#DIV/0!`.
68    DivisionByZero,
69    /// A required input cell had no value.
70    MissingInput,
71    Custom(String),
72    /// `#NULL!`.
73    Null,
74    /// `#REF!`.
75    Ref,
76    /// `#NAME?`.
77    Name,
78    /// `#NUM!`: a numeric domain error (`SQRT(-1)`, `LN(0)`, overflow).
79    Num,
80    /// `#N/A`: no value available (`NA()`, a failed lookup).
81    Na,
82}
83
84impl ValueErrorKind {
85    /// The spreadsheet error text: `#DIV/0!`, `#N/A`, …; Improv's own kinds
86    /// show as `#ERR`.
87    pub fn code(&self) -> &'static str {
88        match self {
89            ValueErrorKind::Null => "#NULL!",
90            ValueErrorKind::DivisionByZero => "#DIV/0!",
91            ValueErrorKind::TypeMismatch => "#VALUE!",
92            ValueErrorKind::Ref => "#REF!",
93            ValueErrorKind::Name => "#NAME?",
94            ValueErrorKind::Num => "#NUM!",
95            ValueErrorKind::Na => "#N/A",
96            _ => "#ERR",
97        }
98    }
99
100    /// `ERROR.TYPE`'s number for a spreadsheet error (1 `#NULL!` … 7 `#N/A`),
101    /// or `None` for Improv's own kinds.
102    pub fn error_type(&self) -> Option<u8> {
103        Some(match self {
104            ValueErrorKind::Null => 1,
105            ValueErrorKind::DivisionByZero => 2,
106            ValueErrorKind::TypeMismatch => 3,
107            ValueErrorKind::Ref => 4,
108            ValueErrorKind::Name => 5,
109            ValueErrorKind::Num => 6,
110            ValueErrorKind::Na => 7,
111            _ => return None,
112        })
113    }
114
115    /// The kind for an `ERROR.TYPE` number (the inverse of [`Self::error_type`]).
116    pub fn from_error_type(n: u8) -> Option<Self> {
117        Some(match n {
118            1 => ValueErrorKind::Null,
119            2 => ValueErrorKind::DivisionByZero,
120            3 => ValueErrorKind::TypeMismatch,
121            4 => ValueErrorKind::Ref,
122            5 => ValueErrorKind::Name,
123            6 => ValueErrorKind::Num,
124            7 => ValueErrorKind::Na,
125            _ => return None,
126        })
127    }
128
129    /// The kind an error text names (`"#DIV/0!"`, case-insensitive).
130    pub fn from_code(code: &str) -> Option<Self> {
131        (1..=7)
132            .filter_map(Self::from_error_type)
133            .find(|k| k.code().eq_ignore_ascii_case(code.trim()))
134    }
135}
136
137/// An error value, carrying enough context to trace it back to its source.
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct ValueError {
140    pub kind: ValueErrorKind,
141    pub source_measure: Option<MeasureId>,
142}
143
144impl ValueError {
145    pub fn new(kind: ValueErrorKind) -> Self {
146        ValueError {
147            kind,
148            source_measure: None,
149        }
150    }
151
152    pub fn from_measure(kind: ValueErrorKind, measure: MeasureId) -> Self {
153        ValueError {
154            kind,
155            source_measure: Some(measure),
156        }
157    }
158}
159
160impl From<ValueError> for Value {
161    fn from(e: ValueError) -> Value {
162        Value::Error(e)
163    }
164}