From 7b17575a6681ea79b17c5a34aa2838d2ea313158 Mon Sep 17 00:00:00 2001 From: stefan-gorules <127550877+stefan-gorules@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:23:31 +0200 Subject: [PATCH] fix: negative value checks (#89) * fix: negative value checks; * offset unary fix; * disable unary csv test; --- core/expression/Cargo.toml | 3 + core/expression/src/parser/standard/mod.rs | 87 ++++---- core/expression/src/parser/unary/constants.rs | 2 +- core/expression/src/parser/unary/mod.rs | 47 ++-- core/expression/src/vm.rs | 2 +- core/expression/tests/data/standard.csv | 204 ++++++++++++++++++ core/expression/tests/data/unary.csv | 42 ++++ core/expression/tests/isolate.rs | 39 ++++ 8 files changed, 353 insertions(+), 73 deletions(-) create mode 100644 core/expression/tests/data/standard.csv create mode 100644 core/expression/tests/data/unary.csv diff --git a/core/expression/Cargo.toml b/core/expression/Cargo.toml index 9a904122..32449b26 100644 --- a/core/expression/Cargo.toml +++ b/core/expression/Cargo.toml @@ -25,7 +25,10 @@ rust_decimal = { workspace = true, features = ["maths-nopanic"] } rust_decimal_macros = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } criterion = { workspace = true } +csv = "1.2.2" +serde_json5 = "0.1.0" [features] default = ["regex-deprecated"] diff --git a/core/expression/src/parser/standard/mod.rs b/core/expression/src/parser/standard/mod.rs index 875acfd9..32dcbd95 100644 --- a/core/expression/src/parser/standard/mod.rs +++ b/core/expression/src/parser/standard/mod.rs @@ -37,7 +37,7 @@ where } pub fn parse(&self) -> ParserResult<&'b Node<'b>> { - let result = self.parse_expression(0)?; + let result = self.expression(0)?; if !self.iterator.is_done() { let token = self.iterator.current(); return Err(FailedToParse { @@ -48,8 +48,8 @@ where return Ok(result); } - fn parse_expression(&self, precedence: u8) -> ParserResult<&'b Node<'b>> { - let mut node_left = self.parse_primary()?; + fn expression(&self, precedence: u8) -> ParserResult<&'b Node<'b>> { + let mut node_left = self.primary_expression()?; let mut token = self.iterator.current(); while !self.iterator.is_done() { @@ -58,8 +58,8 @@ where if op.precedence >= precedence { self.iterator.next()?; let node_right = match op.associativity { - Associativity::Left => self.parse_expression(op.precedence + 1)?, - _ => self.parse_expression(op.precedence)?, + Associativity::Left => self.expression(op.precedence + 1)?, + _ => self.expression(op.precedence)?, }; node_left = self.iterator.node(Node::Binary { @@ -77,36 +77,36 @@ where } if precedence == 0 { - node_left = self.parse_conditional(node_left)?; + node_left = self.conditional(node_left)?; } Ok(node_left) } - fn parse_primary(&self) -> ParserResult<&'b Node<'b>> { + fn primary_expression(&self) -> ParserResult<&'b Node<'b>> { let token = self.iterator.current(); if token.kind == TokenKind::Operator { if let Some(op) = UNARY_OPERATORS.get(token.value) { self.iterator.next()?; - let expr = self.parse_expression(op.precedence)?; + let expr = self.expression(op.precedence)?; let node = self.iterator.node(Node::Unary { operator: self.iterator.str_value(token.value), node: expr, })?; - return self.parse_postfix(node); + return self.postfix(node); } } - if let Some(interval_node) = self.parse_interval()? { - return self.parse_postfix(interval_node); + if let Some(interval_node) = self.interval()? { + return self.postfix(interval_node); } if token.kind == TokenKind::Bracket && token.value == "(" { self.iterator.next()?; - let expr = self.parse_expression(0)?; + let expr = self.expression(0)?; self.iterator.expect(TokenKind::Bracket, Some(&[")"]))?; - return self.parse_postfix(expr); + return self.postfix(expr); } if self.depth.get() > 0 { @@ -115,7 +115,7 @@ where self.iterator.next()?; } let node = self.iterator.node(Node::Pointer)?; - return self.parse_postfix(node); + return self.postfix(node); } } else if token.kind == TokenKind::Operator && (token.value == "#" || token.value == ".") { return Err(UnexpectedToken { @@ -124,10 +124,10 @@ where }); } - self.parse_primary_expression() + self.literal() } - fn parse_conditional(&self, node: &'b Node<'b>) -> ParserResult<&'b Node<'b>> { + fn conditional(&self, node: &'b Node<'b>) -> ParserResult<&'b Node<'b>> { let mut nd = self.iterator.node(node.clone())?; let mut expr1: &'b Node; let mut expr2: &'b Node; @@ -139,13 +139,13 @@ where let token = self.iterator.current(); if token.kind != TokenKind::Operator && token.value != ":" { - expr1 = self.parse_expression(0)?; + expr1 = self.expression(0)?; self.iterator.expect(TokenKind::Operator, Some(&[":"]))?; - expr2 = self.parse_expression(0)?; + expr2 = self.expression(0)?; } else { self.iterator.next()?; expr1 = node; - expr2 = self.parse_expression(0)?; + expr2 = self.expression(0)?; } nd = self.iterator.node(Node::Conditional { @@ -158,7 +158,7 @@ where Ok(nd) } - fn parse_primary_expression(&self) -> ParserResult<&'b Node<'b>> { + fn literal(&self) -> ParserResult<&'b Node<'b>> { let node: &'b Node; let token = self.iterator.current(); @@ -168,14 +168,14 @@ where match token.value { "true" | "false" => return self.iterator.bool(token), "null" => return self.iterator.null(token), - _ => node = self.parse_identifier_expression(token)?, + _ => node = self.identifier_expression(token)?, } } TokenKind::Number => return self.iterator.number(token), TokenKind::String => return self.iterator.string(token), _ => { if token.kind == TokenKind::Bracket && token.value == "[" { - node = self.parse_array(token)?; + node = self.array(token)?; } else { return Err(UnexpectedToken { expected: "identifier, string, number or opening bracket".to_string(), @@ -185,10 +185,10 @@ where } } - self.parse_postfix(node) + self.postfix(node) } - fn parse_interval(&self) -> ParserResult>> { + fn interval(&self) -> ParserResult>> { // Performance optimisation: skip if expression does not contain an interval for faster evaluation if !self.iterator.has_interval() { return Ok(None); @@ -205,7 +205,7 @@ where return Ok(None); }; - let Ok(left) = self.parse_primary_expression() else { + let Ok(left) = self.primary_expression() else { self.iterator.set_position(initial_position)?; return Ok(None); }; @@ -215,7 +215,7 @@ where return Ok(None); }; - let Ok(right) = self.parse_primary_expression() else { + let Ok(right) = self.primary_expression() else { self.iterator.set_position(initial_position)?; return Ok(None); }; @@ -236,7 +236,8 @@ where Ok(Some(interval_node)) } - fn parse_identifier_expression(&self, token: &Token) -> ParserResult<&'b Node<'b>> { + + fn identifier_expression(&self, token: &Token) -> ParserResult<&'b Node<'b>> { if self.iterator.current().kind != TokenKind::Bracket || self.iterator.current().value != "(" { @@ -253,7 +254,7 @@ where return match builtin.arity { Arity::Single => { - let arg = self.parse_expression(0)?; + let arg = self.expression(0)?; self.iterator.expect(TokenKind::Bracket, Some(&[")"]))?; self.iterator.node(Node::BuiltIn { @@ -262,9 +263,9 @@ where }) } Arity::Dual => { - let arg1 = self.parse_expression(0)?; + let arg1 = self.expression(0)?; self.iterator.expect(TokenKind::Operator, Some(&[","]))?; - let arg2 = self.parse_expression(0)?; + let arg2 = self.expression(0)?; self.iterator.expect(TokenKind::Bracket, Some(&[")"]))?; self.iterator.node(Node::BuiltIn { @@ -273,9 +274,9 @@ where }) } Arity::Closure => { - let arg1 = self.parse_expression(0)?; + let arg1 = self.expression(0)?; self.iterator.expect(TokenKind::Operator, Some(&[","]))?; - let arg2 = self.parse_closure()?; + let arg2 = self.closure()?; self.iterator.expect(TokenKind::Bracket, Some(&[")"]))?; self.iterator.node(Node::BuiltIn { @@ -286,12 +287,12 @@ where }; } - fn parse_array(&self, _token: &Token) -> ParserResult<&'b Node<'b>> { + fn array(&self, _token: &Token) -> ParserResult<&'b Node<'b>> { let mut nodes = Vec::new(); - self.iterator.expect(TokenKind::Bracket, Some(&["["]))?; - while self.iterator.current().kind != TokenKind::Bracket - && self.iterator.current().value != "]" + + while !(self.iterator.current().kind == TokenKind::Bracket + && self.iterator.current().value == "]") { if !nodes.is_empty() { self.iterator.expect(TokenKind::Operator, Some(&[","]))?; @@ -300,7 +301,7 @@ where } } - nodes.push(self.parse_primary()?); + nodes.push(self.expression(0)?); } self.iterator.expect(TokenKind::Bracket, Some(&["]"]))?; @@ -309,15 +310,15 @@ where self.iterator.node(node) } - fn parse_closure(&self) -> ParserResult<&'b Node<'b>> { + fn closure(&self) -> ParserResult<&'b Node<'b>> { self.depth.set(self.depth.get() + 1); - let node = self.parse_expression(0)?; + let node = self.expression(0)?; self.depth.set(self.depth.get() - 1); return self.iterator.node(Node::Closure(node)); } - fn parse_postfix(&self, node: &'b Node<'b>) -> ParserResult<&'b Node<'b>> { + fn postfix(&self, node: &'b Node<'b>) -> ParserResult<&'b Node<'b>> { let mut postfix_token = self.iterator.current(); let mut nd = self.iterator.node(node.clone())?; @@ -353,13 +354,13 @@ where c = self.iterator.current(); if c.kind != TokenKind::Bracket && c.value != "]" { - to = Some(self.parse_expression(0)?); + to = Some(self.expression(0)?); } nd = self.iterator.node(Node::Slice { node: nd, to, from })?; self.iterator.expect(TokenKind::Bracket, Some(&["]"]))?; } else { - from = Some(self.parse_expression(0)?); + from = Some(self.expression(0)?); c = self.iterator.current(); if c.kind == TokenKind::Operator && c.value == ":" { @@ -367,7 +368,7 @@ where c = self.iterator.current(); if c.kind != TokenKind::Bracket && c.value != "]" { - to = Some(self.parse_expression(0)?); + to = Some(self.expression(0)?); } nd = self.iterator.node(Node::Slice { node: nd, from, to })?; diff --git a/core/expression/src/parser/unary/constants.rs b/core/expression/src/parser/unary/constants.rs index e646e600..aab0f336 100644 --- a/core/expression/src/parser/unary/constants.rs +++ b/core/expression/src/parser/unary/constants.rs @@ -36,7 +36,7 @@ pub(crate) const BUILT_INS: Map<&'static str, BuiltIn> = phf_map! { "weekdayString" => BuiltIn { arity: Arity::Single }, }; -pub(crate) const OPERATORS: Map<&'static str, Operator> = phf_map! { +pub(crate) const STANDARD_OPERATORS: Map<&'static str, Operator> = phf_map! { "or" => Operator { precedence: 10, associativity: Left }, "and" => Operator { precedence: 15, associativity: Left }, "==" => Operator { precedence: 20, associativity: Left }, diff --git a/core/expression/src/parser/unary/mod.rs b/core/expression/src/parser/unary/mod.rs index febc8577..b469de64 100644 --- a/core/expression/src/parser/unary/mod.rs +++ b/core/expression/src/parser/unary/mod.rs @@ -5,7 +5,7 @@ use crate::lexer::token::{Token, TokenKind}; use crate::parser::definitions::{Arity, Associativity}; use crate::parser::error::{ParserError, ParserResult}; use crate::parser::iter::ParserIterator; -use crate::parser::unary::constants::{BUILT_INS, OPERATORS}; +use crate::parser::unary::constants::{BUILT_INS, STANDARD_OPERATORS}; mod constants; @@ -31,10 +31,10 @@ where } pub fn parse(&self) -> ParserResult<&'b Node<'b>> { - self.parse_expression(0, true) + self.expression(0, true) } - fn parse_expression(&self, precedence: u8, root: bool) -> ParserResult<&'b Node<'b>> { + fn expression(&self, precedence: u8, root: bool) -> ParserResult<&'b Node<'b>> { let mut node_left: &'b Node<'b> = self.iterator.node(MAIN_NODE)?; let mut token = self.iterator.current(); @@ -43,7 +43,7 @@ where TokenKind::Operator => { if token.value == "," { self.iterator.next()?; - let node_right: &'b Node<'b> = self.parse_expression(0, true)?; + let node_right: &'b Node<'b> = self.expression(0, true)?; token = self.iterator.current(); node_left = self.iterator.node(Node::Binary { @@ -51,17 +51,12 @@ where operator: "or", right: node_right, })?; - continue; - } else if let Some(op) = OPERATORS.get(token.value) { + } else if let Some(op) = STANDARD_OPERATORS.get(token.value) { if op.precedence >= precedence { self.iterator.next()?; let node_right = match op.associativity { - Associativity::Left => { - self.parse_expression(op.precedence + 1, false)? - } - Associativity::Right => { - self.parse_expression(op.precedence, false)? - } + Associativity::Left => self.expression(op.precedence + 1, false)?, + Associativity::Right => self.expression(op.precedence, false)?, }; node_left = self.iterator.node(Node::Binary { @@ -70,7 +65,6 @@ where right: node_right, })?; token = self.iterator.current(); - continue; } } else { return Err(ParserError::FailedToParse { @@ -82,7 +76,7 @@ where } } TokenKind::Identifier | TokenKind::String | TokenKind::Number => { - let node_right = self.parse_primary()?; + let node_right = self.literal()?; if !root { return Ok(node_right); } @@ -93,12 +87,11 @@ where operator: "==", right: node_right, })?; - continue; } TokenKind::Bracket => { let node_right: &Node; - if let Some(interval) = self.parse_interval(node_left)? { + if let Some(interval) = self.interval(node_left)? { node_left = interval; } else if token.value == "[" { let should_wrap = !self.iterator.lookup_back( @@ -106,7 +99,7 @@ where TokenKind::Operator, Some(&["not in", "in"]), ); - node_right = self.parse_array(token)?; + node_right = self.array(token)?; if should_wrap { node_left = self.iterator.node(Node::Binary { @@ -125,8 +118,6 @@ where ), }); } - - continue; } } } @@ -134,7 +125,7 @@ where Ok(node_left) } - fn parse_interval(&self, node: &'b Node<'b>) -> ParserResult>> { + fn interval(&self, node: &'b Node<'b>) -> ParserResult>> { // Performance optimisation: skip if expression does not contain an interval for faster evaluation if !self.iterator.has_interval() { return Ok(None); @@ -157,7 +148,7 @@ where return Ok(None); } - let Ok(left) = self.parse_primary() else { + let Ok(left) = self.literal() else { self.iterator.set_position(initial_position)?; return Ok(None); }; @@ -167,7 +158,7 @@ where return Ok(None); } - let Ok(right) = self.parse_primary() else { + let Ok(right) = self.literal() else { self.iterator.set_position(initial_position)?; return Ok(None); }; @@ -197,7 +188,7 @@ where Ok(Some(interval_node)) } - fn parse_array(&self, _token: &Token) -> ParserResult<&'b Node<'b>> { + fn array(&self, _token: &Token) -> ParserResult<&'b Node<'b>> { let mut nodes = Vec::new(); self.iterator.expect(TokenKind::Bracket, Some(&["["]))?; @@ -211,7 +202,7 @@ where } } - nodes.push(self.parse_primary()?); + nodes.push(self.literal()?); } self.iterator.expect(TokenKind::Bracket, Some(&["]"]))?; @@ -219,7 +210,7 @@ where self.iterator.node(node) } - fn parse_primary(&self) -> ParserResult<&'b Node<'b>> { + fn literal(&self) -> ParserResult<&'b Node<'b>> { let token = self.iterator.current(); match token.kind { @@ -228,7 +219,7 @@ where match token.value { "true" | "false" => self.iterator.bool(token), "null" => self.iterator.null(token), - _ => self.parse_prebuilt(token), + _ => self.builtin(token), } } TokenKind::Number => self.iterator.number(token), @@ -240,7 +231,7 @@ where } } - fn parse_prebuilt(&self, token: &Token<'a>) -> ParserResult<&'b Node<'b>> { + fn builtin(&self, token: &Token<'a>) -> ParserResult<&'b Node<'b>> { let current_token = self.iterator.current(); let valid_token = current_token.kind == TokenKind::Bracket && current_token.value == "("; @@ -260,7 +251,7 @@ where match built_in.arity { Arity::Single => { - let arg = self.parse_primary()?; + let arg = self.literal()?; let node = self.iterator.node(Node::BuiltIn { name: self.iterator.str_value(token.value), arguments: self.bump.alloc_slice_copy(&[arg]), diff --git a/core/expression/src/vm.rs b/core/expression/src/vm.rs index 6e18c084..062165fc 100644 --- a/core/expression/src/vm.rs +++ b/core/expression/src/vm.rs @@ -421,7 +421,7 @@ impl<'a> VM<'a> { let var = self.pop()?; match var { - Number(a) => self.stack.push(self.bump.alloc(Number(a.round()))), + Number(a) => self.stack.push(self.bump.alloc(Number(a.ceil()))), _ => { return Err(OpcodeErr { opcode: "Ceil".into(), diff --git a/core/expression/tests/data/standard.csv b/core/expression/tests/data/standard.csv new file mode 100644 index 00000000..c1b475c1 --- /dev/null +++ b/core/expression/tests/data/standard.csv @@ -0,0 +1,204 @@ +expression (string);input (json 5);output (json 5) + +# Boolean +true and true;;true +true and false;;false +false and true;;false +false and false;;false +true or true;;true +true or false;;true +false or true;;true +false or false;;false +not true;;false +not false;;true +x == true;{ "x": true };true +x == true;{ "x": false };false +x == false;{ "x": true };false +x == false;{ "x": false };true +x and true;{ "x": true };true +x and true;{ "x": false };false +x or true;{ "x": true };true +x or true;{ "x": false };true +not x;{ "x": true };false +not x;{ "x": false };true +true and x;{ "x": true };true +true and x;{ "x": false };false +true or x;{ "x": true };true +true or x;{ "x": false };true +true and not x;{ "x": true };false +true and not x;{ "x": false };true +false or x;{ "x": true };true +false or x;{ "x": false };false +false and x;{ "x": true };false +false and x;{ "x": false };false +false or not x;{ "x": true };false +false or not x;{ "x": false };true +not x and true;{ "x": true };false +not x and true;{ "x": false };true +x or not true;{ "x": true };true +x or not true;{ "x": false };false +x or not false;{ "x": true };true +x or not false;{ "x": false };true + +# Numbers +1 == 1;;true +1 == 2;;false +1 != 2;;true +1 != 1;;false +1 < 2;;true +1 < 1;;false +1 <= 1;;true +2 <= 1;;false +2 > 1;;true +1 > 1;;false +1 >= 1;;true +1 >= 2;;false +-1 == -1;;true +-1 == 1;;false +-1 != 1;;true +-1 != -1;;false +-1 < 1;;true +-1 < -1;;false +-1 <= -1;;true +1 <= -1;;false +1 > -1;;true +-1 > -1;;false +-1 >= -1;;true +-1 >= 1;;false +1 + 2 == 3;;true +1 + 2 == 4;;false +1 - 2 == -1;;true +1 - 2 == 0;;false +2 * 3 == 6;;true +2 * 3 == 7;;false +6 / 3 == 2;;true +6 / 3 == 2.5;;false +5 % 2 == 1;;true +6 % 2 == 0;;true +-5 + 5 == 0;;true +-5 * 5 == -25;;true +-5 * -5 == 25;;true +abs(-5) == 5;;true +abs(5) == 5;;true +2 ^ 3 == 8;;true +2 ^ -1 == 0.5;;true +max([3, 5]) == 5;;true +max([-3, -5]) == -3;;true +min([3, 5]) == 3;;true +min([-3, -5]) == -5;;true +3 + 4 * 2;;11 +(3 + 4) * 2;;14 +(10 - 4) / 2;;3 +abs(-5);;5 +sum([1, 2, 3, 4, 5]);;15 +avg([10, 20, 30]);;20 +min([5, 8, 2, 11, 7]);;2 +max([5, 8, 2, 11, 7]);;11 +floor(4.8);;4 +ceil(4.1);;5 +round(3.5);;4 +rand(100) >= 0 and rand(100) <= 100;;true +median([4, 2, 7, 5, 3]);;4 +mode([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]);;4 +5 % 2 == 1;;true +10 % 4 == 2;;true +true ? 10 == 10 : 20 == 30;;true +false ? 10 == 10 : 20 == 30;;false +true ? 10 == 20 : false ? 30 == 40 : true ? 50 == 60 : 70 == 80;;false + +# String +'hello' + " " + "world";; 'hello world' +len("Hello, World!");; 13 +lower("Hello, World!");; "hello, world!" +upper("Hello, World!");; "HELLO, WORLD!" +startsWith("Hello, World!", "Hello");; true +startsWith("Hello, World!", "World");; false +endsWith("Hello, World!", "World!");; true +endsWith("Hello, World!", "Hello!");; false +contains("Hello, World!", "lo");; true +contains("Hello, World!", "foo");; false +matches("Hello, World!", "H[a-z]+, W[a-z]+!");; true +matches("Hello, World!", "[0-9]+");; false +extract("2022-09-18", "(\d{4})-(\d{2})-(\d{2})");; ["2022-09-18", "2022", "09", "18"] + +# Array +len([1, 2, 3, 4, 5]);; 5 +sum([1, 2, 3, 4, 5]);; 15 +avg([10, 20, 30]);; 20 +min([5, 8, 2, 11, 7]);; 2 +max([5, 8, 2, 11, 7]);; 11 +contains([1, 2, 3, 4, 5], 3);; true +contains([1, 2, 3, 4, 5], 6);; false +count([1, 2, 3, 4, 5, 2], # == 2);; 2 +filter([1, 2, 3, 4, 5], # > 3);; [4, 5] +map([1, 2, 3, 4, 5], # * 2);; [2, 4, 6, 8, 10] +one([1, 2, 3, 4, 5], # == 3);; true +one([1, 2, 3, 4, 5], # == 6);; false +none([1, 2, 3, 4, 5], # > 5);; true +some([1, 2, 3, 4, 5], # > 3);; true +flatMap([[1, 2], [3, 4], [5, 6]], #);; [1, 2, 3, 4, 5, 6] + +# Dates +date("2023-09-18T12:00:00Z");; 1695038400 +date("2023-09-18T00:00:00Z");; 1694995200 +time("2023-09-18T12:00:00Z");; 43200 +time("2023-09-18T00:00:00Z");; 0 +duration("1h 30m");; 5400 +year("2023-09-18");; 2023 +monthOfYear("2023-09-18");; 9 +monthString("2023-09-18");; "Sep" +dayOfMonth("2023-09-18");; 18 +dayOfWeek("2023-09-18");; 1 +dayOfYear("2023-09-18");; 261 +weekOfYear("2023-09-18");; 38 +weekdayString("2023-09-18");; "Mon" + +# Decimal precision +0.1 + 0.2 == 0.3;; true +0.1 + 0.2;; 0.3 +0.1 * 0.2 == 0.02;; true +0.1 * 0.2;; 0.02 +0.3 - 0.1 == 0.2;; true +0.3 - 0.1;; 0.2 +0.3 / 0.1 == 3.0;; true +0.3 / 0.1;; 3 + +# More complex formulas +(10 + 5) * 3 / 2 > 15;; true +(100 - 25) / (5 * 2) < 10;; true +1000 / (10 - 2) + 50 == 175;; true +(3 + 4) * (8 - 6) == 14;; true +10^3 != 999;; true +1000 / 10^3 <= 1.1;; true +abs(-20) > 10;; true +floor(8.9) == 8;; true +ceil(8.1) == 9;; true +round(7.6) == 8;; true +10 % 3 != 0;; true +-8 + 2^3 == 0;; true +2 * -(-5) == 10;; true +20 / (5 / 2) == 8.0;; true +(4 + 2) * 3 - (5 / 2) + 1 < 18;; true +5^2 + 3 * 4 - 6 / 2 > 20;; true +4^3 - 2 * 5 + 7 / 2 == 57.5;; true +abs(-7 + 4) * (8 - 6^2) < 34;; true +5 in [1..10];; true +5 in (1..10);; true +5 not in [1..10);; false +5 not in (1..10);; false +-5 in [-10..-1];; true +-5 in [-10..-1);; true +-5 not in [-10..-1];; false +-5 not in [-10..-1);; false +-5 in [-5..5];; true +-5 in (-5..5);; false +-5 not in [-5..5);; false +-5 not in (-5..5);; true +10 > 5;; true +5 < 10;; true +10 >= 10;; true +5 <= 5;; true +(1 + 2) == 3;; true +(1 + 2) != 4;; true +(1 + 2) > 2;; true +(1 + 2) < 5;; true \ No newline at end of file diff --git a/core/expression/tests/data/unary.csv b/core/expression/tests/data/unary.csv new file mode 100644 index 00000000..0b3b6e5d --- /dev/null +++ b/core/expression/tests/data/unary.csv @@ -0,0 +1,42 @@ +expression (string);input (json 5);output (json 5) + +# Boolean +true;{ "$": true }; true +true;{ "$": false }; false +false;{ "$": true }; false +false;{ "$": false }; true + +# Number +> 5;{$: 10};true +< 10;{$: 5};true +>= 10;{$: 10};true +<= 5;{$: 5};true +[-10..0];{$: 5};true +(-10..0];{$: 5};false +[-10..0);{$: 5};true +(-10..0);{$: 5};false +[-15..-5];{$: 5};false +[-5..5];{$: 5};true +[-6..6];{$: 5};false +[-5..5];{$: 5};true +[0..0];{$: 5};true +> 5 and < 10;{$: 5};true +<= 10 and > 5;{$: 5};true +>= 10 or < 5;{$: 10};true +<= 5 or >= 10;{$: 10};true +[-10..0] and > -5;{$: -5};true +(-10..0] or > 0;{$: 5};true +[-10..0) and > -5;{$: -5};false +(-10..0) or > 0;{$: 5};true +[-15..-5] and <= -10;{$: -5};false +[-5..5] or > 0;{$: 5};true +[-5..5] or < 0;{$: 5};false +[-5..5] and > 0;{$: 5};true +[0..0] and < 5;{$: 5};true +> 10 or < -5;{$: 5};true +> 10, < -5;{$: 5};true +<= -10 and >= -15;{$: -5};true + +# String +'GB','US';{$: 'US'};true +'GB','US';{$: 'FR'};false \ No newline at end of file diff --git a/core/expression/tests/isolate.rs b/core/expression/tests/isolate.rs index 847369b1..153dae7f 100644 --- a/core/expression/tests/isolate.rs +++ b/core/expression/tests/isolate.rs @@ -1,4 +1,6 @@ +use anyhow::Context; use bumpalo::Bump; +use std::ops::Index; use serde_json::{json, Value}; @@ -706,3 +708,40 @@ fn isolate_test_decimals() { assert_eq!(result, Value::from(9223372036854775807i64)); } + +#[test] +fn test_standard_csv() { + let csv_data = include_str!("data/standard.csv"); + let mut r = csv::ReaderBuilder::new() + .delimiter(b';') + .from_reader(csv_data.as_bytes()); + + while let Some(maybe_row) = r.records().next() { + let Ok(row) = maybe_row else { + continue; + }; + + let (expression, input_str, output_str) = (row.index(0), row.index(1), row.index(2)); + if expression.starts_with("#") { + continue; + } + + let output: Value = serde_json5::from_str(output_str).unwrap(); + + let isolate = Isolate::default(); + if !input_str.is_empty() { + let input: Value = serde_json5::from_str(input_str).unwrap(); + isolate.inject_env(&input); + } + + let result = isolate + .run_standard(expression) + .context(format!("Expression: {expression}")) + .unwrap(); + + assert_eq!( + result, output, + "Expression {expression}. Expected: {output}, got: {result}" + ); + } +}