formualizer_eval/
coercion.rs1use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
2
3pub fn to_number_strict(value: &LiteralValue) -> Result<f64, ExcelError> {
11 match value {
12 LiteralValue::Number(n) => Ok(*n),
13 LiteralValue::Int(i) => Ok(*i as f64),
14 LiteralValue::Boolean(b) => Ok(if *b { 1.0 } else { 0.0 }),
15 LiteralValue::Empty => Ok(0.0),
16 other if other.as_serial_number().is_some() => Ok(other.as_serial_number().unwrap()),
18 LiteralValue::Error(e) => Err(e.clone()),
19 _ => Err(ExcelError::new(ExcelErrorKind::Value)
20 .with_message("Cannot convert to number (strict)")),
21 }
22}
23
24pub fn to_number_lenient(value: &LiteralValue) -> Result<f64, ExcelError> {
27 match value {
28 LiteralValue::Text(s) => s.trim().parse::<f64>().map_err(|_| {
29 ExcelError::new(ExcelErrorKind::Value)
30 .with_message(format!("Cannot convert '{s}' to number"))
31 }),
32 _ => to_number_strict(value),
33 }
34}
35
36pub fn to_number_lenient_with_locale(
38 value: &LiteralValue,
39 loc: &crate::locale::Locale,
40) -> Result<f64, ExcelError> {
41 match value {
42 LiteralValue::Text(s) => loc.parse_number_invariant(s).ok_or_else(|| {
43 ExcelError::new(ExcelErrorKind::Value)
44 .with_message(format!("Cannot convert '{s}' to number"))
45 }),
46 _ => to_number_strict(value),
47 }
48}
49
50pub fn to_logical(value: &LiteralValue) -> Result<bool, ExcelError> {
55 match value {
56 LiteralValue::Boolean(b) => Ok(*b),
57 LiteralValue::Number(n) => Ok(*n != 0.0),
58 LiteralValue::Int(i) => Ok(*i != 0),
59 LiteralValue::Text(s) => match s.to_ascii_lowercase().as_str() {
60 "true" => Ok(true),
61 "false" => Ok(false),
62 _ => Err(ExcelError::new(ExcelErrorKind::Value)
63 .with_message("Cannot convert text to logical")),
64 },
65 LiteralValue::Empty => Ok(false),
66 LiteralValue::Error(e) => Err(e.clone()),
67 _ => Err(ExcelError::new(ExcelErrorKind::Value).with_message("Cannot convert to logical")),
68 }
69}
70
71pub fn to_text_invariant(value: &LiteralValue) -> String {
73 match value {
74 LiteralValue::Text(s) => s.clone(),
75 LiteralValue::Number(n) => n.to_string(),
76 LiteralValue::Int(i) => i.to_string(),
77 LiteralValue::Boolean(b) => if *b { "TRUE" } else { "FALSE" }.into(),
78 LiteralValue::Error(e) => e.to_string(),
79 LiteralValue::Empty => "".into(),
80 other => format!("{other:?}"),
81 }
82}
83
84pub fn sanitize_numeric(n: f64) -> Result<f64, ExcelError> {
86 if n.is_nan() || n.is_infinite() {
87 return Err(ExcelError::new_num());
88 }
89 Ok(n)
90}
91
92pub fn to_datetime_serial(value: &LiteralValue) -> Result<f64, ExcelError> {
94 match value.as_serial_number() {
95 Some(n) => Ok(n),
96 None => Err(ExcelError::new(ExcelErrorKind::Value)
97 .with_message("Cannot convert to date/time serial")),
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn number_lenient_parses_text_and_booleans() {
107 assert_eq!(
108 to_number_lenient(&LiteralValue::Text(" 42 ".into())).unwrap(),
109 42.0
110 );
111 assert_eq!(
112 to_number_lenient(&LiteralValue::Boolean(true)).unwrap(),
113 1.0
114 );
115 assert_eq!(to_number_lenient(&LiteralValue::Empty).unwrap(), 0.0);
116 }
117
118 #[test]
119 fn number_strict_rejects_text() {
120 assert!(to_number_strict(&LiteralValue::Text("1".into())).is_err());
121 }
122
123 #[test]
124 fn logical_from_number_and_text() {
125 assert!(to_logical(&LiteralValue::Int(5)).unwrap());
126 assert!(!to_logical(&LiteralValue::Number(0.0)).unwrap());
127 assert!(to_logical(&LiteralValue::Text("TRUE".into())).unwrap());
128 assert!(to_logical(&LiteralValue::Text("true".into())).unwrap());
129 assert!(to_logical(&LiteralValue::Text(" True ".into())).is_err());
130 }
131
132 #[test]
133 fn sanitize_numeric_nan_inf() {
134 assert!(sanitize_numeric(f64::NAN).is_err());
135 assert!(sanitize_numeric(f64::INFINITY).is_err());
136 assert_eq!(sanitize_numeric(1.5).unwrap(), 1.5);
137 }
138}