diff --git a/expression-core/src/evaluate.rs b/expression-core/src/evaluate.rs index fd28f06..fefa8db 100644 --- a/expression-core/src/evaluate.rs +++ b/expression-core/src/evaluate.rs @@ -87,12 +87,18 @@ impl Function { Ok(ExpressionValue::String(evaluated_args.concat())) } - Function::Ceil(expr) => { + Function::Ceil(expr, None) => { let evaluated_decimal = expr.evaluate(event)?.to_decimal()?; Ok(ExpressionValue::Number( evaluated_decimal.with_scale_round(0, RoundingMode::Ceiling), )) } + Function::Floor(expr, None) => { + let evaluated_decimal = expr.evaluate(event)?.to_decimal()?; + Ok(ExpressionValue::Number( + evaluated_decimal.with_scale_round(0, RoundingMode::Floor), + )) + } Function::Round(expr, None) => { let evaluated_decimal = expr.evaluate(event)?.to_decimal()?; Ok(ExpressionValue::Number( @@ -110,6 +116,28 @@ impl Function { evaluated_decimal.with_scale_round(round_digits, RoundingMode::HalfUp), )) } + Function::Ceil(expr, Some(digit_expr)) => { + let evaluated_decimal = expr.evaluate(event)?.to_decimal()?; + let round_digits = digit_expr + .evaluate(event)? + .to_decimal()? + .to_i64() + .ok_or(ExpressionError::ExpectedDecimal)?; + Ok(ExpressionValue::Number( + evaluated_decimal.with_scale_round(round_digits, RoundingMode::Ceiling), + )) + } + Function::Floor(expr, Some(digit_expr)) => { + let evaluated_decimal = expr.evaluate(event)?.to_decimal()?; + let round_digits = digit_expr + .evaluate(event)? + .to_decimal()? + .to_i64() + .ok_or(ExpressionError::ExpectedDecimal)?; + Ok(ExpressionValue::Number( + evaluated_decimal.with_scale_round(round_digits, RoundingMode::Floor), + )) + } } } } @@ -303,13 +331,52 @@ mod tests { #[test] fn test_evaluate_ceil() { - let expr = Expression::Function(Function::Ceil(Box::new(Expression::Decimal( - "12.3".parse::().unwrap(), - )))); + let expr = Expression::Function(Function::Ceil( + Box::new(Expression::Decimal("12.3".parse::().unwrap())), + None, + )); let event = Default::default(); evaluate_and_compare(expr, &event, ExpressionValue::Number(13.into())); } + #[test] + fn test_evaluate_ceil_with_arg() { + let expr = Expression::Function(Function::Ceil( + Box::new(Expression::Decimal("12.351".parse::().unwrap())), + Some(Box::new(Expression::Decimal(1.into()))), + )); + let event = Default::default(); + evaluate_and_compare( + expr, + &event, + ExpressionValue::Number("12.4".parse::().unwrap()), + ); + } + + #[test] + fn test_evaluate_floor() { + let expr = Expression::Function(Function::Floor( + Box::new(Expression::Decimal("12.3".parse::().unwrap())), + None, + )); + let event = Default::default(); + evaluate_and_compare(expr, &event, ExpressionValue::Number(12.into())); + } + + #[test] + fn test_evaluate_floor_with_arg() { + let expr = Expression::Function(Function::Floor( + Box::new(Expression::Decimal("12.351".parse::().unwrap())), + Some(Box::new(Expression::Decimal(1.into()))), + )); + let event = Default::default(); + evaluate_and_compare( + expr, + &event, + ExpressionValue::Number("12.3".parse::().unwrap()), + ); + } + #[test] fn test_evaluate_concat() { let expr = Expression::Function(Function::Concat(vec![ diff --git a/expression-core/src/grammar.pest b/expression-core/src/grammar.pest index 4c1b16b..d841a19 100644 --- a/expression-core/src/grammar.pest +++ b/expression-core/src/grammar.pest @@ -1,9 +1,10 @@ function = { function_name ~ "(" ~ function_args ~ ")" } -function_name = _{ ceil | concat | round } -ceil = { "ceil" } -concat = { "concat" } -round = { "round" } +function_name = _{ ceil | concat | round | floor } +ceil = { "ceil" | "CEIL" | "Ceil" } +concat = { "concat" | "CONCAT" | "Concat" } +round = { "round" | "ROUND" | "Round" } +floor = { "floor" | "FLOOR" | "Floor" } function_args = _{ expr ~ ("," ~ expr)* } diff --git a/expression-core/src/parser.rs b/expression-core/src/parser.rs index 3c29c95..4471c2a 100644 --- a/expression-core/src/parser.rs +++ b/expression-core/src/parser.rs @@ -23,8 +23,9 @@ pub type ParseResult = Result; #[derive(Debug, PartialEq)] pub enum Function { Concat(Vec), - Ceil(Box), + Ceil(Box, Option>), Round(Box, Option>), + Floor(Box, Option>), } #[derive(Debug, PartialEq)] @@ -72,7 +73,6 @@ fn parse_function(pairs: Pairs) -> ParseResult { let mut iter = pairs.into_iter(); let name = iter.next().unwrap(); let function = match name.as_rule() { - Rule::ceil => Function::Ceil(Box::new(parse_expr(iter)?)), Rule::concat => { let args = iter .map(|r| parse_expr(r.into_inner())) @@ -80,31 +80,36 @@ fn parse_function(pairs: Pairs) -> ParseResult { Function::Concat(args) } - Rule::round => { - let mut args = iter - .map(|r| { - let expr = parse_expr(r.into_inner())?; - Ok(Box::new(expr)) - }) - .collect::>>>(); - - match args.len() { - 1 => Function::Round(args.remove(0)?, None), - 2 => Function::Round(args.remove(0)?, Some(args.remove(0)?)), - n => { - return Err(ParseError::WrongNumberOfArguments( - "round".to_owned(), - "1..2".to_owned(), - n, - )) - } - } - } + Rule::ceil => parse_function_with_args(Function::Ceil, iter)?, + Rule::round => parse_function_with_args(Function::Round, iter)?, + Rule::floor => parse_function_with_args(Function::Floor, iter)?, rule => unreachable!("Expected function name, got :{:?}", rule), }; Ok(function) } +fn parse_function_with_args(f: F, iter: Pairs) -> ParseResult +where + F: Fn(Box, Option>) -> Function, +{ + let mut args = iter + .map(|r| { + let expr = parse_expr(r.into_inner())?; + Ok(Box::new(expr)) + }) + .collect::>>>(); + + match args.len() { + 1 => Ok(f(args.remove(0)?, None)), + 2 => Ok(f(args.remove(0)?, Some(args.remove(0)?))), + n => Err(ParseError::WrongNumberOfArguments( + "round".to_owned(), + "1..2".to_owned(), + n, + )), + } +} + fn parse_event_attribute(mut pairs: Pairs) -> EventAttribute { let mut inner = pairs.next().unwrap().into_inner(); match inner.next().unwrap().as_rule() { @@ -217,16 +222,54 @@ mod tests { ); } + #[test] + fn test_parse_concat_uppercase() { + parse_and_compare( + "CONCAT('a', 'b')", + Expression::Function(Function::Concat(vec![ + Expression::String("a".to_owned()), + Expression::String("b".to_owned()), + ])), + ); + } + + #[test] + fn test_parse_concat_capitalized() { + parse_and_compare( + "Concat('a', 'b')", + Expression::Function(Function::Concat(vec![ + Expression::String("a".to_owned()), + Expression::String("b".to_owned()), + ])), + ); + } + #[test] fn test_parse_ceil() { parse_and_compare( "ceil(123)", - Expression::Function(Function::Ceil(Box::new(Expression::Decimal(123.into())))), + Expression::Function(Function::Ceil( + Box::new(Expression::Decimal(123.into())), + None, + )), ); } #[test] - fn test_parse_round() { + fn test_parse_ceil_one_arg() { + parse_and_compare( + "ceil(123, -1)", + Expression::Function(Function::Ceil( + Box::new(Expression::Decimal(123.into())), + Some(Box::new(Expression::UnaryMinus(Box::new( + Expression::Decimal(1.into()), + )))), + )), + ); + } + + #[test] + fn test_parse_round_one_arg() { parse_and_compare( "round(123, 1)", Expression::Function(Function::Round( @@ -236,7 +279,7 @@ mod tests { ); } #[test] - fn test_parse_round_one_arg() { + fn test_parse_round() { parse_and_compare( "round(123)", Expression::Function(Function::Round( @@ -245,4 +288,25 @@ mod tests { )), ); } + + #[test] + fn test_parse_floor_one_arg() { + parse_and_compare( + "floor(123, 1)", + Expression::Function(Function::Floor( + Box::new(Expression::Decimal(123.into())), + Some(Box::new(Expression::Decimal(1.into()))), + )), + ); + } + #[test] + fn test_parse_floor() { + parse_and_compare( + "floor(123)", + Expression::Function(Function::Floor( + Box::new(Expression::Decimal(123.into())), + None, + )), + ); + } }