sheetkit_core/formula/functions/
logical.rs1use crate::cell::CellValue;
4use crate::error::{Error, Result};
5use crate::formula::ast::Expr;
6use crate::formula::eval::{coerce_to_bool, Evaluator};
7use crate::formula::functions::check_arg_count;
8
9pub fn fn_true(args: &[Expr], _ctx: &mut Evaluator) -> Result<CellValue> {
11 check_arg_count("TRUE", args, 0, 0)?;
12 Ok(CellValue::Bool(true))
13}
14
15pub fn fn_false(args: &[Expr], _ctx: &mut Evaluator) -> Result<CellValue> {
17 check_arg_count("FALSE", args, 0, 0)?;
18 Ok(CellValue::Bool(false))
19}
20
21pub fn fn_iferror(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
23 check_arg_count("IFERROR", args, 2, 2)?;
24 match ctx.eval_expr(&args[0]) {
25 Ok(CellValue::Error(_)) | Err(_) => ctx.eval_expr(&args[1]),
26 Ok(v) => Ok(v),
27 }
28}
29
30pub fn fn_ifna(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
32 check_arg_count("IFNA", args, 2, 2)?;
33 match ctx.eval_expr(&args[0]) {
34 Ok(CellValue::Error(ref e)) if e == "#N/A" => ctx.eval_expr(&args[1]),
35 Ok(v) => Ok(v),
36 Err(e) => {
37 let msg = e.to_string();
38 if msg.contains("#N/A") {
39 ctx.eval_expr(&args[1])
40 } else {
41 Err(e)
42 }
43 }
44 }
45}
46
47pub fn fn_ifs(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
49 if args.len() < 2 || !args.len().is_multiple_of(2) {
50 return Err(Error::WrongArgCount {
51 name: "IFS".to_string(),
52 expected: "even number >= 2".to_string(),
53 got: args.len(),
54 });
55 }
56 let mut i = 0;
57 while i < args.len() {
58 let cond = ctx.eval_expr(&args[i])?;
59 if coerce_to_bool(&cond)? {
60 return ctx.eval_expr(&args[i + 1]);
61 }
62 i += 2;
63 }
64 Ok(CellValue::Error("#N/A".to_string()))
65}
66
67pub fn fn_switch(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
69 if args.len() < 3 {
70 return Err(Error::WrongArgCount {
71 name: "SWITCH".to_string(),
72 expected: "3 or more".to_string(),
73 got: args.len(),
74 });
75 }
76 let expr_val = ctx.eval_expr(&args[0])?;
77 let pairs_end = if args.len().is_multiple_of(2) {
78 args.len() - 1
80 } else {
81 args.len()
82 };
83 let mut i = 1;
84 while i + 1 < pairs_end {
85 let case_val = ctx.eval_expr(&args[i])?;
86 if values_equal(&expr_val, &case_val) {
87 return ctx.eval_expr(&args[i + 1]);
88 }
89 i += 2;
90 }
91 if args.len().is_multiple_of(2) {
93 ctx.eval_expr(&args[args.len() - 1])
94 } else {
95 Ok(CellValue::Error("#N/A".to_string()))
96 }
97}
98
99fn values_equal(a: &CellValue, b: &CellValue) -> bool {
100 match (a, b) {
101 (CellValue::Number(x), CellValue::Number(y)) => (x - y).abs() < f64::EPSILON,
102 (CellValue::String(x), CellValue::String(y)) => x.eq_ignore_ascii_case(y),
103 (CellValue::Bool(x), CellValue::Bool(y)) => x == y,
104 (CellValue::Empty, CellValue::Empty) => true,
105 _ => false,
106 }
107}
108
109pub fn fn_xor(args: &[Expr], ctx: &mut Evaluator) -> Result<CellValue> {
111 check_arg_count("XOR", args, 1, 255)?;
112 let values = ctx.flatten_args_to_values(args)?;
113 let mut true_count = 0u32;
114 for v in &values {
115 if matches!(v, CellValue::Empty) {
116 continue;
117 }
118 if coerce_to_bool(v)? {
119 true_count += 1;
120 }
121 }
122 Ok(CellValue::Bool(
123 !true_count.is_multiple_of(2) && true_count > 0,
124 ))
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::formula::eval::{evaluate, CellSnapshot, Evaluator};
131 use crate::formula::parser::parse_formula;
132
133 fn eval(formula: &str) -> CellValue {
134 let snap = CellSnapshot::new("Sheet1".to_string());
135 let expr = parse_formula(formula).unwrap();
136 evaluate(&expr, &snap).unwrap()
137 }
138
139 #[test]
140 fn test_true_function() {
141 let snap = CellSnapshot::new("Sheet1".to_string());
142 let mut evaluator = Evaluator::new(&snap);
143 let result = fn_true(&[], &mut evaluator).unwrap();
144 assert_eq!(result, CellValue::Bool(true));
145 }
146
147 #[test]
148 fn test_false_function() {
149 let snap = CellSnapshot::new("Sheet1".to_string());
150 let mut evaluator = Evaluator::new(&snap);
151 let result = fn_false(&[], &mut evaluator).unwrap();
152 assert_eq!(result, CellValue::Bool(false));
153 }
154
155 #[test]
156 fn test_iferror_no_error() {
157 assert_eq!(eval(r#"IFERROR(42,"error")"#), CellValue::Number(42.0));
158 }
159
160 #[test]
161 fn test_iferror_with_error() {
162 assert_eq!(
163 eval(r#"IFERROR(1/0,"error")"#),
164 CellValue::String("error".to_string())
165 );
166 }
167
168 #[test]
169 fn test_ifna_no_na() {
170 assert_eq!(eval(r#"IFNA(42,"not found")"#), CellValue::Number(42.0));
171 }
172
173 #[test]
174 fn test_ifs_first_true() {
175 assert_eq!(
176 eval(r#"IFS(TRUE,"first",TRUE,"second")"#),
177 CellValue::String("first".to_string())
178 );
179 }
180
181 #[test]
182 fn test_ifs_second_true() {
183 assert_eq!(
184 eval(r#"IFS(FALSE,"first",TRUE,"second")"#),
185 CellValue::String("second".to_string())
186 );
187 }
188
189 #[test]
190 fn test_ifs_none_true() {
191 assert_eq!(
192 eval(r#"IFS(FALSE,"first",FALSE,"second")"#),
193 CellValue::Error("#N/A".to_string())
194 );
195 }
196
197 #[test]
198 fn test_switch_match() {
199 assert_eq!(
200 eval(r#"SWITCH(2,1,"one",2,"two",3,"three")"#),
201 CellValue::String("two".to_string())
202 );
203 }
204
205 #[test]
206 fn test_switch_default() {
207 assert_eq!(
208 eval(r#"SWITCH(99,1,"one",2,"two","other")"#),
209 CellValue::String("other".to_string())
210 );
211 }
212
213 #[test]
214 fn test_switch_no_match_no_default() {
215 assert_eq!(
216 eval(r#"SWITCH(99,1,"one",2,"two",3,"three")"#),
217 CellValue::Error("#N/A".to_string())
218 );
219 }
220
221 #[test]
222 fn test_xor_odd_true() {
223 assert_eq!(eval("XOR(TRUE,FALSE,TRUE,TRUE)"), CellValue::Bool(true));
224 }
225
226 #[test]
227 fn test_xor_even_true() {
228 assert_eq!(eval("XOR(TRUE,TRUE)"), CellValue::Bool(false));
229 }
230}