sheetkit_core/formula/functions/
information.rs

1//! Information formula functions: ISERR, ISNA, ISLOGICAL, ISEVEN, ISODD,
2//! TYPE, N, NA, ERROR.TYPE.
3
4use crate::cell::CellValue;
5use crate::error::Result;
6use crate::formula::ast::Expr;
7use crate::formula::eval::{coerce_to_number, Evaluator};
8use crate::formula::functions::check_arg_count;
9
10/// ISERR(value) - TRUE if value is an error other than #N/A
11pub fn fn_iserr(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
12    check_arg_count("ISERR", args, 1, 1)?;
13    let v = ctx.eval_expr(&args[0])?;
14    let result = match &v {
15        CellValue::Error(e) => e != "#N/A",
16        _ => false,
17    };
18    Ok(CellValue::Bool(result))
19}
20
21/// ISNA(value) - TRUE if value is #N/A
22pub fn fn_isna(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
23    check_arg_count("ISNA", args, 1, 1)?;
24    let v = ctx.eval_expr(&args[0])?;
25    let result = matches!(&v, CellValue::Error(e) if e == "#N/A");
26    Ok(CellValue::Bool(result))
27}
28
29/// ISLOGICAL(value) - TRUE if value is a boolean
30pub fn fn_islogical(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
31    check_arg_count("ISLOGICAL", args, 1, 1)?;
32    let v = ctx.eval_expr(&args[0])?;
33    Ok(CellValue::Bool(matches!(v, CellValue::Bool(_))))
34}
35
36/// ISEVEN(number) - TRUE if the integer part of number is even
37pub fn fn_iseven(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
38    check_arg_count("ISEVEN", args, 1, 1)?;
39    let v = ctx.eval_expr(&args[0])?;
40    let n = coerce_to_number(&v)?;
41    let int_part = n.trunc() as i64;
42    Ok(CellValue::Bool(int_part % 2 == 0))
43}
44
45/// ISODD(number) - TRUE if the integer part of number is odd
46pub fn fn_isodd(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
47    check_arg_count("ISODD", args, 1, 1)?;
48    let v = ctx.eval_expr(&args[0])?;
49    let n = coerce_to_number(&v)?;
50    let int_part = n.trunc() as i64;
51    Ok(CellValue::Bool(int_part % 2 != 0))
52}
53
54/// TYPE(value) - returns type code: 1=number, 2=text, 4=boolean, 16=error, 64=array
55pub fn fn_type(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
56    check_arg_count("TYPE", args, 1, 1)?;
57    let v = ctx.eval_expr(&args[0])?;
58    let code = match v {
59        CellValue::Number(_) | CellValue::Date(_) | CellValue::Empty => 1.0,
60        CellValue::String(_) | CellValue::RichString(_) => 2.0,
61        CellValue::Bool(_) => 4.0,
62        CellValue::Error(_) => 16.0,
63        CellValue::Formula { .. } => 1.0,
64    };
65    Ok(CellValue::Number(code))
66}
67
68/// N(value) - convert to number: numbers stay, bools become 0/1, errors stay, everything else 0
69pub fn fn_n(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
70    check_arg_count("N", args, 1, 1)?;
71    let v = ctx.eval_expr(&args[0])?;
72    match v {
73        CellValue::Number(n) => Ok(CellValue::Number(n)),
74        CellValue::Date(n) => Ok(CellValue::Number(n)),
75        CellValue::Bool(b) => Ok(CellValue::Number(if b { 1.0 } else { 0.0 })),
76        CellValue::Error(_) => Ok(v),
77        _ => Ok(CellValue::Number(0.0)),
78    }
79}
80
81/// NA() - returns #N/A error
82pub fn fn_na(args: &[Expr], _ctx: &mut Evaluator) -> Result<CellValue> {
83    check_arg_count("NA", args, 0, 0)?;
84    Ok(CellValue::Error("#N/A".to_string()))
85}
86
87/// ERROR.TYPE(value) - returns error type code.
88/// 1=#NULL!, 2=#DIV/0!, 3=#VALUE!, 4=#REF!, 5=#NAME?, 6=#NUM!, 7=#N/A.
89/// Returns #N/A if value is not an error.
90pub fn fn_error_type(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
91    check_arg_count("ERROR.TYPE", args, 1, 1)?;
92    let v = ctx.eval_expr(&args[0])?;
93    match v {
94        CellValue::Error(ref e) => {
95            let code = match e.as_str() {
96                "#NULL!" => 1.0,
97                "#DIV/0!" => 2.0,
98                "#VALUE!" => 3.0,
99                "#REF!" => 4.0,
100                "#NAME?" => 5.0,
101                "#NUM!" => 6.0,
102                "#N/A" => 7.0,
103                _ => return Ok(CellValue::Error("#N/A".to_string())),
104            };
105            Ok(CellValue::Number(code))
106        }
107        _ => Ok(CellValue::Error("#N/A".to_string())),
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::cell::CellValue;
114    use crate::formula::eval::{evaluate, CellSnapshot};
115    use crate::formula::parser::parse_formula;
116
117    fn eval_with_data(formula: &str, data: &[(&str, u32, u32, CellValue)]) -> CellValue {
118        let mut snap = CellSnapshot::new("Sheet1".to_string());
119        for (sheet, col, row, val) in data {
120            snap.set_cell(sheet, *col, *row, val.clone());
121        }
122        let expr = parse_formula(formula).unwrap();
123        evaluate(&expr, &snap).unwrap()
124    }
125
126    fn eval(formula: &str) -> CellValue {
127        eval_with_data(formula, &[])
128    }
129
130    // ISERR tests
131
132    #[test]
133    fn iserr_div_zero() {
134        let data = vec![("Sheet1", 1, 1, CellValue::Error("#DIV/0!".to_string()))];
135        assert_eq!(eval_with_data("ISERR(A1)", &data), CellValue::Bool(true));
136    }
137
138    #[test]
139    fn iserr_na_is_false() {
140        let data = vec![("Sheet1", 1, 1, CellValue::Error("#N/A".to_string()))];
141        assert_eq!(eval_with_data("ISERR(A1)", &data), CellValue::Bool(false));
142    }
143
144    #[test]
145    fn iserr_not_error() {
146        assert_eq!(eval("ISERR(42)"), CellValue::Bool(false));
147    }
148
149    // ISNA tests
150
151    #[test]
152    fn isna_true() {
153        assert_eq!(eval("ISNA(NA())"), CellValue::Bool(true));
154    }
155
156    #[test]
157    fn isna_false_for_other_error() {
158        let data = vec![("Sheet1", 1, 1, CellValue::Error("#DIV/0!".to_string()))];
159        assert_eq!(eval_with_data("ISNA(A1)", &data), CellValue::Bool(false));
160    }
161
162    #[test]
163    fn isna_false_for_number() {
164        assert_eq!(eval("ISNA(1)"), CellValue::Bool(false));
165    }
166
167    // ISLOGICAL tests
168
169    #[test]
170    fn islogical_true() {
171        assert_eq!(eval("ISLOGICAL(TRUE)"), CellValue::Bool(true));
172    }
173
174    #[test]
175    fn islogical_false_for_number() {
176        assert_eq!(eval("ISLOGICAL(1)"), CellValue::Bool(false));
177    }
178
179    // ISEVEN tests
180
181    #[test]
182    fn iseven_true() {
183        assert_eq!(eval("ISEVEN(4)"), CellValue::Bool(true));
184    }
185
186    #[test]
187    fn iseven_false() {
188        assert_eq!(eval("ISEVEN(3)"), CellValue::Bool(false));
189    }
190
191    #[test]
192    fn iseven_zero() {
193        assert_eq!(eval("ISEVEN(0)"), CellValue::Bool(true));
194    }
195
196    // ISODD tests
197
198    #[test]
199    fn isodd_true() {
200        assert_eq!(eval("ISODD(3)"), CellValue::Bool(true));
201    }
202
203    #[test]
204    fn isodd_false() {
205        assert_eq!(eval("ISODD(4)"), CellValue::Bool(false));
206    }
207
208    // TYPE tests
209
210    #[test]
211    fn type_number() {
212        assert_eq!(eval("TYPE(42)"), CellValue::Number(1.0));
213    }
214
215    #[test]
216    fn type_text() {
217        assert_eq!(eval("TYPE(\"hello\")"), CellValue::Number(2.0));
218    }
219
220    #[test]
221    fn type_boolean() {
222        assert_eq!(eval("TYPE(TRUE)"), CellValue::Number(4.0));
223    }
224
225    #[test]
226    fn type_error() {
227        assert_eq!(eval("TYPE(NA())"), CellValue::Number(16.0));
228    }
229
230    // N tests
231
232    #[test]
233    fn n_number() {
234        assert_eq!(eval("N(42)"), CellValue::Number(42.0));
235    }
236
237    #[test]
238    fn n_true() {
239        assert_eq!(eval("N(TRUE)"), CellValue::Number(1.0));
240    }
241
242    #[test]
243    fn n_false() {
244        assert_eq!(eval("N(FALSE)"), CellValue::Number(0.0));
245    }
246
247    #[test]
248    fn n_string() {
249        assert_eq!(eval("N(\"text\")"), CellValue::Number(0.0));
250    }
251
252    #[test]
253    fn n_error() {
254        assert_eq!(eval("N(NA())"), CellValue::Error("#N/A".to_string()));
255    }
256
257    // NA test
258
259    #[test]
260    fn na_returns_error() {
261        assert_eq!(eval("NA()"), CellValue::Error("#N/A".to_string()));
262    }
263
264    // ERROR.TYPE tests
265
266    #[test]
267    fn error_type_div_zero() {
268        let data = vec![("Sheet1", 1, 1, CellValue::Error("#DIV/0!".to_string()))];
269        assert_eq!(
270            eval_with_data("ERROR.TYPE(A1)", &data),
271            CellValue::Number(2.0)
272        );
273    }
274
275    #[test]
276    fn error_type_na() {
277        assert_eq!(eval("ERROR.TYPE(NA())"), CellValue::Number(7.0));
278    }
279
280    #[test]
281    fn error_type_value_error() {
282        let data = vec![("Sheet1", 1, 1, CellValue::Error("#VALUE!".to_string()))];
283        assert_eq!(
284            eval_with_data("ERROR.TYPE(A1)", &data),
285            CellValue::Number(3.0)
286        );
287    }
288
289    #[test]
290    fn error_type_not_error() {
291        assert_eq!(eval("ERROR.TYPE(42)"), CellValue::Error("#N/A".to_string()));
292    }
293}