From 6326c44941da64a10da412b64f49ac1117ae884d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Sun, 29 Dec 2024 18:41:19 +0100 Subject: [PATCH] FIX: TRUE and FALSE can also be functions Previously the engine was internally transforming TRUE() to TRUE Note that the friendly giant implements this only for compatibility reasons --- base/src/expressions/parser/mod.rs | 33 +++++++++++++++++++++++++++++- base/src/functions/logical.rs | 16 +++++++++++++++ base/src/functions/mod.rs | 4 ++-- base/src/test/mod.rs | 1 + base/src/test/test_true_false.rs | 25 ++++++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 base/src/test/test_true_false.rs diff --git a/base/src/expressions/parser/mod.rs b/base/src/expressions/parser/mod.rs index fd24551..62a4d47 100644 --- a/base/src/expressions/parser/mod.rs +++ b/base/src/expressions/parser/mod.rs @@ -642,7 +642,38 @@ impl Parser { position: 0, message: "Unexpected end of input.".to_string(), }, - TokenType::Boolean(value) => Node::BooleanKind(value), + TokenType::Boolean(value) => { + // Could be a function call "TRUE()" + let next_token = self.lexer.peek_token(); + if next_token == TokenType::LeftParenthesis { + self.lexer.advance_token(); + // We parse all the arguments, although technically this is moot + // But is has the upside of transforming `=TRUE( 4 )` into `=TRUE(4)` + let args = match self.parse_function_args() { + Ok(s) => s, + Err(e) => return e, + }; + if let Err(err) = self.lexer.expect(TokenType::RightParenthesis) { + return Node::ParseErrorKind { + formula: self.lexer.get_formula(), + position: err.position, + message: err.message, + }; + } + if value { + return Node::FunctionKind { + kind: Function::True, + args, + }; + } else { + return Node::FunctionKind { + kind: Function::False, + args, + }; + } + } + Node::BooleanKind(value) + } TokenType::Compare(_) => { // A primary Node cannot start with an operator Node::ParseErrorKind { diff --git a/base/src/functions/logical.rs b/base/src/functions/logical.rs index 655875d..a23e91e 100644 --- a/base/src/functions/logical.rs +++ b/base/src/functions/logical.rs @@ -7,6 +7,22 @@ use crate::{ use super::util::compare_values; impl Model { + pub(crate) fn fn_true(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + if args.is_empty() { + CalcResult::Boolean(true) + } else { + CalcResult::new_args_number_error(cell) + } + } + + pub(crate) fn fn_false(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { + if args.is_empty() { + CalcResult::Boolean(false) + } else { + CalcResult::new_args_number_error(cell) + } + } + pub(crate) fn fn_if(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult { if args.len() == 2 || args.len() == 3 { let cond_result = self.get_boolean(&args[0], cell); diff --git a/base/src/functions/mod.rs b/base/src/functions/mod.rs index 7009f05..882d44e 100644 --- a/base/src/functions/mod.rs +++ b/base/src/functions/mod.rs @@ -949,7 +949,7 @@ impl Model { match kind { // Logical Function::And => self.fn_and(args, cell), - Function::False => CalcResult::Boolean(false), + Function::False => self.fn_false(args, cell), Function::If => self.fn_if(args, cell), Function::Iferror => self.fn_iferror(args, cell), Function::Ifna => self.fn_ifna(args, cell), @@ -957,7 +957,7 @@ impl Model { Function::Not => self.fn_not(args, cell), Function::Or => self.fn_or(args, cell), Function::Switch => self.fn_switch(args, cell), - Function::True => CalcResult::Boolean(true), + Function::True => self.fn_true(args, cell), Function::Xor => self.fn_xor(args, cell), // Math and trigonometry Function::Sin => self.fn_sin(args, cell), diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index 109771b..7e76f92 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -41,6 +41,7 @@ mod test_sheet_markup; mod test_sheets; mod test_styles; mod test_trigonometric; +mod test_true_false; mod test_workbook; mod test_worksheet; pub(crate) mod util; diff --git a/base/src/test/test_true_false.rs b/base/src/test/test_true_false.rs new file mode 100644 index 0000000..dc5b093 --- /dev/null +++ b/base/src/test/test_true_false.rs @@ -0,0 +1,25 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn true_false_arguments() { + let mut model = new_empty_model(); + model._set("A1", "=TRUE( )"); + model._set("A2", "=FALSE( )"); + model._set("A3", "=TRUE( 4 )"); + model._set("A4", "=FALSE( 4 )"); + + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"TRUE"); + assert_eq!(model._get_text("A2"), *"FALSE"); + + assert_eq!(model._get_formula("A1"), *"=TRUE()"); + assert_eq!(model._get_formula("A2"), *"=FALSE()"); + + assert_eq!(model._get_text("A3"), *"#ERROR!"); + assert_eq!(model._get_text("A4"), *"#ERROR!"); + assert_eq!(model._get_formula("A3"), *"=TRUE(4)"); + assert_eq!(model._get_formula("A4"), *"=FALSE(4)"); +}