From ef8d34af568289fdf6b38da74a13d354592c5358 Mon Sep 17 00:00:00 2001 From: Elie ROUDNINSKI Date: Tue, 21 Jul 2026 17:28:43 +0200 Subject: [PATCH] Represent any and all as logical quantifiers Parse any(...) and all(...) as logical expressions that reduce Array(Bool) values instead of requiring registered functions. Remove the built-in AnyFunction and AllFunction exports and update AST tests for the new quantifier representation. --- engine/src/ast/field_expr.rs | 94 ++++--------- engine/src/ast/function_expr.rs | 232 +++++++++++++------------------- engine/src/ast/logical_expr.rs | 228 ++++++++++++++++++++++++++++++- engine/src/functions/all.rs | 131 ------------------ engine/src/functions/any.rs | 131 ------------------ engine/src/functions/mod.rs | 4 - engine/src/lib.rs | 12 +- 7 files changed, 354 insertions(+), 478 deletions(-) delete mode 100644 engine/src/functions/all.rs delete mode 100644 engine/src/functions/any.rs diff --git a/engine/src/ast/field_expr.rs b/engine/src/ast/field_expr.rs index d5b23d4a..f42e804f 100644 --- a/engine/src/ast/field_expr.rs +++ b/engine/src/ast/field_expr.rs @@ -797,10 +797,10 @@ mod tests { use super::*; use crate::ast::ValueExpr; use crate::ast::function_expr::{FunctionCallArgExpr, FunctionCallExpr}; - use crate::ast::logical_expr::LogicalExpr; + use crate::ast::logical_expr::{LogicalExpr, QuantifierArgExpr, QuantifierOp}; use crate::execution_context::ExecutionContext; use crate::functions::{ - AllFunction, CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, + CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, FunctionParam, FunctionParamError, SimpleFunctionDefinition, SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, }; @@ -820,19 +820,6 @@ mod tests { use std::net::IpAddr; use std::sync::LazyLock; - fn any_function<'a>(args: FunctionArgs<'_, 'a>) -> Option> { - match args.next()? { - Ok(v) => Some(LhsValue::Bool( - Array::try_from(v) - .unwrap() - .into_iter() - .any(|lhs| bool::try_from(lhs).unwrap()), - )), - Err(Type::Array(ref arr)) if arr.get_type() == Type::Bool => None, - _ => unreachable!(), - } - } - fn echo_function<'a>(args: FunctionArgs<'_, 'a>) -> Option> { args.next()?.ok() } @@ -972,21 +959,6 @@ mod tests { map.bytes.arr: Map(Array(Bytes)), http.parts: Array(Array(Bytes)), }; - builder - .add_function( - "any", - SimpleFunctionDefinition { - params: vec![SimpleFunctionParam { - arg_kind: SimpleFunctionArgKind::Field, - val_type: Type::Array(Type::Bool.into()), - }], - opt_params: vec![], - return_type: Type::Bool, - implementation: SimpleFunctionImpl::new(any_function), - }, - ) - .unwrap(); - builder.add_function("all", AllFunction::default()).unwrap(); builder .add_function( "echo", @@ -2670,53 +2642,37 @@ mod tests { let list = SCHEME.get_list(&Type::Int).unwrap(); let expr = assert_ok!( FilterParser::new(&SCHEME).lex_as(r#"any(tcp.ports[*] in $even)"#), - ComparisonExpr { - lhs: IndexExpr { - identifier: IdentifierExpr::FunctionCallExpr(FunctionCallExpr { - function: SCHEME.get_function("any").unwrap().to_owned(), - args: vec![FunctionCallArgExpr::Logical(LogicalExpr::Comparison( - ComparisonExpr { - lhs: IndexExpr { - identifier: IdentifierExpr::Field( - field("tcp.ports").to_owned() - ), - indexes: vec![FieldIndex::MapEach], - }, - op: ComparisonOpExpr::InList { - list: list.to_owned(), - name: ListName::from("even".to_string()), - }, - } - ))], - context: None, - }), - indexes: vec![], - }, - op: ComparisonOpExpr::IsTrue + LogicalExpr::Quantifier { + op: QuantifierOp::Any, + arg: Box::new(QuantifierArgExpr::Logical(LogicalExpr::Comparison( + ComparisonExpr { + lhs: IndexExpr { + identifier: IdentifierExpr::Field(field("tcp.ports").to_owned()), + indexes: vec![FieldIndex::MapEach], + }, + op: ComparisonOpExpr::InList { + list: list.to_owned(), + name: ListName::from("even".to_string()), + }, + } + ))), } ); - assert_eq!(expr.lhs.identifier.get_type(), Type::Bool); - assert_eq!(expr.lhs.get_type(), Type::Bool); assert_eq!(expr.get_type(), Type::Bool); assert_json!( expr, { - "lhs": { - "name": "any", - "args": [ - { - "kind": "SimpleExpr", - "value": { - "lhs": ["tcp.ports", {"kind": "MapEach"}], - "op": "InList", - "rhs": "even" - } - } - ] - }, - "op": "IsTrue" + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "lhs": ["tcp.ports", {"kind": "MapEach"}], + "op": "InList", + "rhs": "even" + } + } } ); diff --git a/engine/src/ast/function_expr.rs b/engine/src/ast/function_expr.rs index de5c5178..f6706efc 100644 --- a/engine/src/ast/function_expr.rs +++ b/engine/src/ast/function_expr.rs @@ -4,7 +4,7 @@ use super::visitor::{Visitor, VisitorMut}; use crate::FunctionRef; use crate::ast::field_expr::{ComparisonExpr, ComparisonOp, ComparisonOpExpr, IdentifierExpr}; use crate::ast::index_expr::IndexExpr; -use crate::ast::logical_expr::{LogicalExpr, UnaryOp}; +use crate::ast::logical_expr::{LogicalExpr, QuantifierOp, UnaryOp}; use crate::compiler::Compiler; use crate::filter::{CompiledExpr, CompiledValueExpr, CompiledValueResult}; use crate::functions::{ @@ -143,7 +143,10 @@ impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FunctionCallArgExpr { if c == '"' || (c == 'r' && (c2 == Some('#') || c2 == Some('"'))) { return RhsValue::lex_with(input, Type::Bytes) .map(|(literal, input)| (FunctionCallArgExpr::Literal(literal), input)); - } else if c == '(' || UnaryOp::lex(input).is_ok() { + } else if c == '(' + || UnaryOp::lex(input).is_ok() + || QuantifierOp::lex_call(input).is_some() + { return LogicalExpr::lex_with(input, parser) .map(|(lhs, input)| (FunctionCallArgExpr::Logical(lhs), input)); } else if c_is_field!(c) @@ -571,7 +574,9 @@ mod tests { use super::*; use crate::SimpleFunctionArgKind; use crate::ast::field_expr::{ComparisonExpr, ComparisonOpExpr, IdentifierExpr, OrderingOp}; - use crate::ast::logical_expr::{LogicalExpr, LogicalOp, ParenthesizedExpr}; + use crate::ast::logical_expr::{ + LogicalExpr, LogicalOp, ParenthesizedExpr, QuantifierArgExpr, QuantifierOp, + }; use crate::ast::parse::FilterParser; use crate::functions::{ FunctionArgKind, FunctionArgKindMismatchError, FunctionArgs, SimpleFunctionDefinition, @@ -583,19 +588,6 @@ mod tests { use std::convert::TryFrom; use std::sync::LazyLock; - fn any_function<'a>(args: FunctionArgs<'_, 'a>) -> Option> { - match args.next()? { - Ok(v) => Some(LhsValue::Bool( - Array::try_from(v) - .unwrap() - .into_iter() - .any(|lhs| bool::try_from(lhs).unwrap()), - )), - Err(Type::Array(ref arr)) if arr.get_type() == Type::Bool => None, - _ => unreachable!(), - } - } - fn regex_replace<'a>(args: FunctionArgs<'_, 'a>) -> Option> { args.next()?.ok() } @@ -645,7 +637,7 @@ mod tests { parser.set_max_nesting_depth(1); assert_err!( - parser.lex_as::( + parser.lex_as::( "any ( ( http.request.headers.is_empty or http.request.headers.is_empty ) )" ), LexErrorKind::NestingLimitExceeded { limit: 1 }, @@ -672,20 +664,6 @@ mod tests { ssl: Bool, tcp.port: Int, }; - builder - .add_function( - "any", - SimpleFunctionDefinition { - params: vec![SimpleFunctionParam { - arg_kind: SimpleFunctionArgKind::Field, - val_type: Type::Array(Type::Bool.into()), - }], - opt_params: vec![], - return_type: Type::Bool, - implementation: SimpleFunctionImpl::new(any_function), - }, - ) - .unwrap(); builder .add_function( "echo", @@ -958,9 +936,9 @@ mod tests { FilterParser::new(&SCHEME).lex_as( r#"any ( ( http.request.headers.is_empty or http.request.headers.is_empty ) )"# ), - FunctionCallExpr { - function: SCHEME.get_function("any").unwrap().to_owned(), - args: vec![FunctionCallArgExpr::Logical(LogicalExpr::Parenthesized( + LogicalExpr::Quantifier { + op: QuantifierOp::Any, + arg: Box::new(QuantifierArgExpr::Logical(LogicalExpr::Parenthesized( Box::new(ParenthesizedExpr { expr: LogicalExpr::Combining { op: LogicalOp::Or, @@ -992,37 +970,33 @@ mod tests { ] } }) - ))], - context: None, + ))), }, "" ); - assert_eq!(expr.return_type(), Type::Bool); assert_eq!(expr.get_type(), Type::Bool); assert_json!( expr, { - "name": "any", - "args": [ - { - "kind": "SimpleExpr", - "value": { - "items": [ - { - "lhs": "http.request.headers.is_empty", - "op": "IsTrue", - }, - { - "lhs": "http.request.headers.is_empty", - "op": "IsTrue", - } - ], - "op": "Or", - } + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "items": [ + { + "lhs": "http.request.headers.is_empty", + "op": "IsTrue", + }, + { + "lhs": "http.request.headers.is_empty", + "op": "IsTrue", + } + ], + "op": "Or", } - ] + } } ); @@ -1114,9 +1088,9 @@ mod tests { let expr = assert_ok!( FilterParser::new(&SCHEME) .lex_as("any(lower(http.request.headers.names[*])[*] contains \"c\")"), - FunctionCallExpr { - function: SCHEME.get_function("any").unwrap().to_owned(), - args: vec![FunctionCallArgExpr::Logical(LogicalExpr::Comparison( + LogicalExpr::Quantifier { + op: QuantifierOp::Any, + arg: Box::new(QuantifierArgExpr::Logical(LogicalExpr::Comparison( ComparisonExpr { lhs: IndexExpr { identifier: IdentifierExpr::FunctionCallExpr(FunctionCallExpr { @@ -1136,41 +1110,37 @@ mod tests { }, op: ComparisonOpExpr::Contains("c".to_string().into(),) } - ))], - context: None, + ))), }, "" ); - assert_eq!(expr.return_type(), Type::Bool); assert_eq!(expr.get_type(), Type::Bool); assert_json!( expr, { - "args": [ - { - "kind": "SimpleExpr", - "value": { - "lhs": [ - { - "args": [ - { - "kind": "IndexExpr", - "value": ["http.request.headers.names", {"kind": "MapEach"}] - } - ], - "name": "lower" - },{ - "kind": "MapEach" - } - ], - "op": "Contains", - "rhs": "c" - } + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "lhs": [ + { + "args": [ + { + "kind": "IndexExpr", + "value": ["http.request.headers.names", {"kind": "MapEach"}] + } + ], + "name": "lower" + },{ + "kind": "MapEach" + } + ], + "op": "Contains", + "rhs": "c" } - ], - "name": "any" + } } ); @@ -1208,9 +1178,9 @@ mod tests { let expr = assert_ok!( FilterParser::new(&SCHEME) .lex_as("any(not(http.request.headers.names[*] in {\"Cookie\" \"Cookies\"}))"), - FunctionCallExpr { - function: SCHEME.get_function("any").unwrap().to_owned(), - args: vec![FunctionCallArgExpr::Logical(LogicalExpr::Unary { + LogicalExpr::Quantifier { + op: QuantifierOp::Any, + arg: Box::new(QuantifierArgExpr::Logical(LogicalExpr::Unary { op: UnaryOp::Not, arg: Box::new(LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { expr: LogicalExpr::Comparison(ComparisonExpr { @@ -1229,49 +1199,45 @@ mod tests { ])), }) },))) - })], - context: None, + })), }, "" ); - assert_eq!(expr.return_type(), Type::Bool); assert_eq!(expr.get_type(), Type::Bool); assert_json!( expr, { - "name": "any", - "args": [ - { - "kind": "SimpleExpr", - "value": { - "op": "Not", - "arg": { - "lhs": [ - "http.request.headers.names", - { - "kind": "MapEach" - } - ], - "op": "OneOf", - "rhs": [ - "Cookie", - "Cookies" - ] - } + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "op": "Not", + "arg": { + "lhs": [ + "http.request.headers.names", + { + "kind": "MapEach" + } + ], + "op": "OneOf", + "rhs": [ + "Cookie", + "Cookies" + ] } } - ] + } } ); let expr = assert_ok!( FilterParser::new(&SCHEME) .lex_as("any(!(http.request.headers.names[*] in {\"Cookie\" \"Cookies\"}))"), - FunctionCallExpr { - function: SCHEME.get_function("any").unwrap().to_owned(), - args: vec![FunctionCallArgExpr::Logical(LogicalExpr::Unary { + LogicalExpr::Quantifier { + op: QuantifierOp::Any, + arg: Box::new(QuantifierArgExpr::Logical(LogicalExpr::Unary { op: UnaryOp::Not, arg: Box::new(LogicalExpr::Parenthesized(Box::new(ParenthesizedExpr { expr: LogicalExpr::Comparison(ComparisonExpr { @@ -1290,40 +1256,36 @@ mod tests { ])), }) },))) - })], - context: None, + })), }, "" ); - assert_eq!(expr.return_type(), Type::Bool); assert_eq!(expr.get_type(), Type::Bool); assert_json!( expr, { - "name": "any", - "args": [ - { - "kind": "SimpleExpr", - "value": { - "op": "Not", - "arg": { - "lhs": [ - "http.request.headers.names", - { - "kind": "MapEach" - } - ], - "op": "OneOf", - "rhs": [ - "Cookie", - "Cookies" - ] - } + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "op": "Not", + "arg": { + "lhs": [ + "http.request.headers.names", + { + "kind": "MapEach" + } + ], + "op": "OneOf", + "rhs": [ + "Cookie", + "Cookies" + ] } } - ] + } } ); } diff --git a/engine/src/ast/logical_expr.rs b/engine/src/ast/logical_expr.rs index bb3e60a8..7db2720d 100644 --- a/engine/src/ast/logical_expr.rs +++ b/engine/src/ast/logical_expr.rs @@ -1,11 +1,13 @@ use super::Expr; use super::field_expr::ComparisonExpr; +use super::function_expr::FunctionCallArgExpr; +use super::index_expr::IndexExpr; use super::parse::FilterParser; use super::visitor::{Visitor, VisitorMut}; use crate::compiler::Compiler; use crate::filter::{CompiledExpr, CompiledOneExpr, CompiledVecExpr}; -use crate::lex::{Lex, LexErrorKind, LexResult, LexWith, expect, skip_space}; -use crate::types::{GetType, Type, TypeMismatchError}; +use crate::lex::{Lex, LexErrorKind, LexResult, LexWith, expect, skip_space, span}; +use crate::types::{GetType, LhsValue, Type, TypeMismatchError}; use serde::Serialize; lex_enum!( @@ -29,6 +31,42 @@ lex_enum!( } ); +lex_enum!( + /// An operator that reduces an array of boolean values to a single boolean. + /// + /// Quantifier operators are parsed from `any(...)` and `all(...)` + /// expressions and can reduce mapped comparisons such as + /// `any(headers[*] == "x")`. + QuantifierOp { + /// Returns `true` when at least one input value is `true`. + "any" => Any, + /// Returns `true` when every input value is `true`. + "all" => All, + } +); + +impl QuantifierOp { + pub(crate) fn lex_call(input: &str) -> Option<(Self, &str)> { + let (op, rest) = Self::lex(input).ok()?; + if expect(skip_space(rest), "(").is_ok() { + Some((op, rest)) + } else { + None + } + } + + fn reduce_bool_iter(self, values: impl IntoIterator) -> bool { + match self { + Self::Any => values.into_iter().any(|value| value), + Self::All => values.into_iter().all(|value| value), + } + } + + fn reduce_lhs_array(self, array: crate::lhs_types::Array<'_>) -> bool { + self.reduce_bool_iter(array.into_iter().map(|lhs| bool::try_from(lhs).unwrap())) + } +} + /// A parenthesized expression. #[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)] #[serde(transparent)] @@ -37,6 +75,82 @@ pub struct ParenthesizedExpr { pub expr: LogicalExpr, } +/// The argument to an `any(...)` or `all(...)` quantifier expression. +/// +/// The argument must have type `Array(Bool)`. It can be either a direct +/// boolean array field or a logical expression that produces a boolean array +/// through map-each indexing, such as `headers[*] contains "cookie"`. +#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)] +#[serde(tag = "kind", content = "value")] +pub enum QuantifierArgExpr { + /// A value expression which evaluates to an `Array(Bool)`. + IndexExpr(IndexExpr), + /// A logical expression which evaluates to an `Array(Bool)`. + #[serde(rename = "SimpleExpr")] + Logical(LogicalExpr), +} + +fn bool_array_type() -> Type { + Type::Array(Type::Bool.into()) +} + +impl QuantifierArgExpr { + fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) { + match self { + Self::IndexExpr(index_expr) => visitor.visit_index_expr(index_expr), + Self::Logical(logical_expr) => visitor.visit_logical_expr(logical_expr), + } + } + + fn walk_mut<'a, V: VisitorMut<'a>>(&'a mut self, visitor: &mut V) { + match self { + Self::IndexExpr(index_expr) => visitor.visit_index_expr(index_expr), + Self::Logical(logical_expr) => visitor.visit_logical_expr(logical_expr), + } + } +} + +impl GetType for QuantifierArgExpr { + fn get_type(&self) -> Type { + match self { + Self::IndexExpr(index_expr) => index_expr.get_type(), + Self::Logical(logical_expr) => logical_expr.get_type(), + } + } +} + +impl<'i, 's> LexWith<'i, &FilterParser<'s>> for QuantifierArgExpr { + fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { + let (arg, rest) = FunctionCallArgExpr::lex_with(input, parser)?; + let arg = match arg { + FunctionCallArgExpr::IndexExpr(index_expr) => Self::IndexExpr(index_expr), + FunctionCallArgExpr::Logical(logical_expr) => Self::Logical(logical_expr), + FunctionCallArgExpr::Literal(literal) => { + return Err(( + LexErrorKind::TypeMismatch(TypeMismatchError { + expected: bool_array_type().into(), + actual: literal.get_type(), + }), + span(input, rest), + )); + } + }; + + let actual = arg.get_type(); + if actual == bool_array_type() { + Ok((arg, rest)) + } else { + Err(( + LexErrorKind::TypeMismatch(TypeMismatchError { + expected: bool_array_type().into(), + actual, + }), + span(input, rest), + )) + } + } +} + /// LogicalExpr is a either a generic sub-expression /// or a logical conjunction expression. #[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)] @@ -60,6 +174,16 @@ pub enum LogicalExpr { /// Sub-expression. arg: Box, }, + /// An `any(...)` or `all(...)` expression. + /// + /// Quantifier expressions reduce an `Array(Bool)` argument to a single + /// `Bool` value. + Quantifier { + /// The quantifier operator to apply. + op: QuantifierOp, + /// The boolean array expression to reduce. + arg: Box, + }, } impl GetType for LogicalExpr { @@ -69,6 +193,7 @@ impl GetType for LogicalExpr { LogicalExpr::Comparison(comparison) => comparison.get_type(), LogicalExpr::Parenthesized(parenthesized) => parenthesized.expr.get_type(), LogicalExpr::Unary { arg, .. } => arg.get_type(), + LogicalExpr::Quantifier { .. } => Type::Bool, } } } @@ -81,6 +206,32 @@ impl LogicalExpr { } } + fn lex_quantifier_expr<'i>( + input: &'i str, + parser: &FilterParser<'_>, + ) -> Option> { + let (op, rest) = QuantifierOp::lex_call(input)?; + let nested_parser = match parser.with_increased_nesting(skip_space(rest)) { + Ok(parser) => parser, + Err(err) => return Some(Err(err)), + }; + Some((|| { + let input = skip_space(rest); + let input = expect(input, "(")?; + let input = skip_space(input); + let (arg, input) = QuantifierArgExpr::lex_with(input, &nested_parser)?; + let input = skip_space(input); + let input = expect(input, ")")?; + Ok(( + LogicalExpr::Quantifier { + op, + arg: Box::new(arg), + }, + input, + )) + })()) + } + fn lex_simple_expr<'i>(input: &'i str, parser: &FilterParser<'_>) -> LexResult<'i, Self> { Ok(if let Ok(rest) = expect(input, "(") { let nested_parser = parser.with_increased_nesting(input)?; @@ -103,6 +254,8 @@ impl LogicalExpr { }, input, ) + } else if let Some(result) = Self::lex_quantifier_expr(input, parser) { + return result; } else { let (op, input) = ComparisonExpr::lex_with(input, parser)?; (LogicalExpr::Comparison(op), input) @@ -190,6 +343,7 @@ impl Expr for LogicalExpr { LogicalExpr::Comparison(node) => visitor.visit_comparison_expr(node), LogicalExpr::Parenthesized(node) => visitor.visit_logical_expr(&node.expr), LogicalExpr::Unary { arg, .. } => visitor.visit_logical_expr(arg), + LogicalExpr::Quantifier { arg, .. } => arg.walk(visitor), LogicalExpr::Combining { items, .. } => { items .iter() @@ -204,6 +358,7 @@ impl Expr for LogicalExpr { LogicalExpr::Comparison(node) => visitor.visit_comparison_expr(node), LogicalExpr::Parenthesized(node) => visitor.visit_logical_expr(&mut node.expr), LogicalExpr::Unary { arg, .. } => visitor.visit_logical_expr(arg), + LogicalExpr::Quantifier { arg, .. } => arg.walk_mut(visitor), LogicalExpr::Combining { items, .. } => { items .iter_mut() @@ -230,6 +385,27 @@ impl Expr for LogicalExpr { })), } } + LogicalExpr::Quantifier { op, arg } => match *arg { + QuantifierArgExpr::IndexExpr(index_expr) => { + let arg = compiler.compile_index_expr(index_expr); + CompiledExpr::One(CompiledOneExpr::new(move |ctx| match arg.execute(ctx) { + Ok(LhsValue::Array(array)) => op.reduce_lhs_array(array), + Err(_) => false, + Ok(_) => unreachable!(), + })) + } + QuantifierArgExpr::Logical(logical_expr) => { + let arg = compiler.compile_logical_expr(logical_expr); + match arg { + CompiledExpr::One(_) => unreachable!(), + CompiledExpr::Vec(vec) => { + CompiledExpr::One(CompiledOneExpr::new(move |ctx| { + op.reduce_bool_iter(vec.execute(ctx).iter().copied()) + })) + } + } + } + }, LogicalExpr::Combining { op, items } => { let items = items.into_iter(); let mut items = items.map(|item| compiler.compile_logical_expr(item)); @@ -328,7 +504,7 @@ fn test() { use crate::ast::index_expr::IndexExpr; use crate::execution_context::ExecutionContext; use crate::lex::complete; - use crate::lhs_types::Array; + use crate::lhs_types::{Array, Map}; use crate::scheme::FieldIndex; use crate::types::Type; @@ -338,6 +514,9 @@ fn test() { at: Array(Bool), af: Array(Bool), aat: Array(Array(Bool)), + empty_bool_array: Array(Bool), + map_bool_array: Map(Array(Bool)), + map_bytes_array: Map(Array(Bytes)), } .build(); @@ -377,6 +556,21 @@ fn test() { Array::from_iter([false, false, true]) }) .unwrap(); + ctx.set_field_value( + scheme.get_field("empty_bool_array").unwrap(), + Array::new(Type::Bool), + ) + .unwrap(); + ctx.set_field_value( + scheme.get_field("map_bool_array").unwrap(), + Map::new(Type::Array(Type::Bool.into())), + ) + .unwrap(); + ctx.set_field_value( + scheme.get_field("map_bytes_array").unwrap(), + Map::new(Type::Array(Type::Bytes.into())), + ) + .unwrap(); { let expr = assert_ok!( @@ -921,6 +1115,34 @@ fn test() { not_expr(parenthesized_expr(not_expr(not_expr(at_expr())))) ); + { + let execute = |input| scheme.parse(input).unwrap().compile().execute(ctx).unwrap(); + + assert_eq!(execute("any(at)"), true); + assert_eq!(execute("all(at)"), false); + assert_eq!(execute("any(empty_bool_array)"), false); + assert_eq!(execute("all(empty_bool_array)"), true); + assert_eq!(execute(r#"any(map_bool_array["missing"])"#), false); + assert_eq!(execute(r#"all(map_bool_array["missing"])"#), false); + assert_eq!( + execute(r#"any(map_bytes_array["missing"][*] matches "bar")"#), + false + ); + assert_eq!( + execute(r#"all(map_bytes_array["missing"][*] matches "bar")"#), + true + ); + + assert_err!( + FilterParser::new(scheme).lex_as::("any(t)"), + LexErrorKind::TypeMismatch(TypeMismatchError { + expected: Type::Array(Type::Bool.into()).into(), + actual: Type::Bool, + }), + "t" + ); + } + { let mut parser = FilterParser::new(scheme); parser.set_max_nesting_depth(1); diff --git a/engine/src/functions/all.rs b/engine/src/functions/all.rs deleted file mode 100644 index 152aed41..00000000 --- a/engine/src/functions/all.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, GetType, LhsValue, ParserSettings, Type, -}; -use std::iter::once; - -fn all_impl<'a>(args: FunctionArgs<'_, 'a>) -> Option> { - let arg = args.next().expect("expected 1 argument, got 0"); - if args.next().is_some() { - panic!("expected 1 argument, got {}", 2 + args.count()); - } - match arg { - Ok(LhsValue::Array(arr)) => Some(LhsValue::Bool( - arr.into_iter().all(|lhs| bool::try_from(lhs).unwrap()), - )), - Err(Type::Array(ref arr)) if arr.get_type() == Type::Bool => None, - _ => unreachable!(), - } -} - -/// A function which, given an array of bool, returns true if all of the -/// arguments are true, otherwise false. -/// -/// It expects one argument and will error if given an incorrect number of -/// arguments or an argument of invalid type. -#[derive(Debug, Default)] -pub struct AllFunction {} - -impl FunctionDefinition for AllFunction { - fn check_param( - &self, - _: &ParserSettings, - params: &mut dyn ExactSizeIterator>, - next_param: &FunctionParam<'_>, - _: Option<&mut FunctionDefinitionContext>, - ) -> Result<(), FunctionParamError> { - match params.len() { - 0 => { - next_param.arg_kind().expect(FunctionArgKind::Field)?; - next_param.expect_val_type(once(Type::Array(Type::Bool.into()).into()))?; - } - _ => unreachable!(), - } - - Ok(()) - } - - fn return_type( - &self, - _: &mut dyn ExactSizeIterator>, - _: Option<&FunctionDefinitionContext>, - ) -> Type { - Type::Bool - } - - fn arg_count(&self) -> (usize, Option) { - (1, Some(0)) - } - - fn compile( - &self, - _: &mut dyn ExactSizeIterator>, - _: Option, - ) -> CompiledFunction { - Box::new(all_impl) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Array; - - #[test] - fn test_all_fn() { - // assert that all([]) is true - let arr = LhsValue::Array(Array::new(Type::Bool)); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), all_impl(&mut args)); - - // assert that all([true]) is true - let arr = LhsValue::Array(Array::from_iter([true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), all_impl(&mut args)); - - // assert that all([false]) is false - let arr = LhsValue::Array(Array::from_iter([false])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(false)), all_impl(&mut args)); - - // assert that all([false, true]) is true - let arr = LhsValue::Array(Array::from_iter([false, true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(false)), all_impl(&mut args)); - - // assert that all([true, true]) is true - let arr = LhsValue::Array(Array::from_iter([true, true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), all_impl(&mut args)); - } - - #[test] - #[should_panic(expected = "expected 1 argument, got 0")] - fn test_all_fn_no_args() { - let mut args = vec![].into_iter(); - all_impl(&mut args); - } - - #[test] - #[should_panic(expected = "expected 1 argument, got 2")] - fn test_all_fn_too_many_args() { - let arr = LhsValue::Array(Array::new(Type::Bool)); - let mut args = vec![Ok(arr.clone()), Ok(arr.clone())].into_iter(); - all_impl(&mut args); - } - - #[test] - #[should_panic] - fn test_all_fn_bad_lhs_value() { - let mut args = vec![Ok(LhsValue::from(false))].into_iter(); - all_impl(&mut args); - } - - #[test] - #[should_panic] - fn test_all_fn_bad_lhs_arr_value() { - let arr = LhsValue::Array(Array::from_iter(["hello"])); - let mut args = vec![Ok(arr)].into_iter(); - all_impl(&mut args); - } -} diff --git a/engine/src/functions/any.rs b/engine/src/functions/any.rs deleted file mode 100644 index 66199d33..00000000 --- a/engine/src/functions/any.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - CompiledFunction, FunctionArgKind, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, - FunctionParam, FunctionParamError, GetType, LhsValue, ParserSettings, Type, -}; -use std::iter::once; - -fn any_impl<'a>(args: FunctionArgs<'_, 'a>) -> Option> { - let arg = args.next().expect("expected 1 argument, got 0"); - if args.next().is_some() { - panic!("expected 1 argument, got {}", 2 + args.count()); - } - match arg { - Ok(LhsValue::Array(arr)) => Some(LhsValue::Bool( - arr.into_iter().any(|lhs| bool::try_from(lhs).unwrap()), - )), - Err(Type::Array(ref arr)) if arr.get_type() == Type::Bool => None, - _ => unreachable!(), - } -} - -/// A function which, given an array of bool, returns true if any one of the -/// arguments is true, otherwise false. -/// -/// It expects one argument and will error if given an incorrect number of -/// arguments or an argument of invalid type. -#[derive(Debug, Default)] -pub struct AnyFunction {} - -impl FunctionDefinition for AnyFunction { - fn check_param( - &self, - _: &ParserSettings, - params: &mut dyn ExactSizeIterator>, - next_param: &FunctionParam<'_>, - _: Option<&mut FunctionDefinitionContext>, - ) -> Result<(), FunctionParamError> { - match params.len() { - 0 => { - next_param.arg_kind().expect(FunctionArgKind::Field)?; - next_param.expect_val_type(once(Type::Array(Type::Bool.into()).into()))?; - } - _ => unreachable!(), - } - - Ok(()) - } - - fn return_type( - &self, - _: &mut dyn ExactSizeIterator>, - _: Option<&FunctionDefinitionContext>, - ) -> Type { - Type::Bool - } - - fn arg_count(&self) -> (usize, Option) { - (1, Some(0)) - } - - fn compile( - &self, - _: &mut dyn ExactSizeIterator>, - _: Option, - ) -> CompiledFunction { - Box::new(any_impl) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Array; - - #[test] - fn test_any_fn() { - // assert that any([]) is false - let arr = LhsValue::Array(Array::new(Type::Bool)); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(false)), any_impl(&mut args)); - - // assert that any([true]) is true - let arr = LhsValue::Array(Array::from_iter([true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), any_impl(&mut args)); - - // assert that any([false]) is false - let arr = LhsValue::Array(Array::from_iter([false])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(false)), any_impl(&mut args)); - - // assert that any([false, true]) is true - let arr = LhsValue::Array(Array::from_iter([false, true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), any_impl(&mut args)); - - // assert that any([true, true]) is true - let arr = LhsValue::Array(Array::from_iter([true, true])); - let mut args = vec![Ok(arr)].into_iter(); - assert_eq!(Some(LhsValue::from(true)), any_impl(&mut args)); - } - - #[test] - #[should_panic(expected = "expected 1 argument, got 0")] - fn test_any_fn_no_args() { - let mut args = vec![].into_iter(); - any_impl(&mut args); - } - - #[test] - #[should_panic(expected = "expected 1 argument, got 2")] - fn test_any_fn_too_many_args() { - let arr = LhsValue::Array(Array::new(Type::Bool)); - let mut args = vec![Ok(arr.clone()), Ok(arr.clone())].into_iter(); - any_impl(&mut args); - } - - #[test] - #[should_panic] - fn test_any_fn_bad_lhs_value() { - let mut args = vec![Ok(LhsValue::from(false))].into_iter(); - any_impl(&mut args); - } - - #[test] - #[should_panic] - fn test_any_fn_bad_lhs_arr_value() { - let arr = LhsValue::Array(Array::from_iter(["hello"])); - let mut args = vec![Ok(arr)].into_iter(); - any_impl(&mut args); - } -} diff --git a/engine/src/functions/mod.rs b/engine/src/functions/mod.rs index 2af36b10..e236a0f9 100644 --- a/engine/src/functions/mod.rs +++ b/engine/src/functions/mod.rs @@ -1,9 +1,5 @@ -pub(crate) mod all; -pub(crate) mod any; pub(crate) mod concat; -pub use self::all::AllFunction; -pub use self::any::AnyFunction; pub use self::concat::ConcatFunction; use crate::ParserSettings; use crate::filter::CompiledValueResult; diff --git a/engine/src/lib.rs b/engine/src/lib.rs index e003eb9b..3944713b 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -84,7 +84,9 @@ pub use self::ast::field_expr::{ }; pub use self::ast::function_expr::{FunctionCallArgExpr, FunctionCallExpr}; pub use self::ast::index_expr::{Compare, IndexExpr}; -pub use self::ast::logical_expr::{LogicalExpr, LogicalOp, ParenthesizedExpr, UnaryOp}; +pub use self::ast::logical_expr::{ + LogicalExpr, LogicalOp, ParenthesizedExpr, QuantifierArgExpr, QuantifierOp, UnaryOp, +}; pub use self::ast::parse::{FilterParser, ParseError, ParserSettings}; pub use self::ast::visitor::{Visitor, VisitorMut}; pub use self::ast::{Expr, FilterAst, FilterValueAst, ValueExpr}; @@ -96,10 +98,10 @@ pub use self::filter::{ CompiledExpr, CompiledOneExpr, CompiledValueExpr, CompiledVecExpr, Filter, FilterValue, }; pub use self::functions::{ - AllFunction, AnyFunction, CompiledFunction, ConcatFunction, FunctionArgInvalidConstantError, - FunctionArgKind, FunctionArgKindMismatchError, FunctionArgs, FunctionDefinition, - FunctionDefinitionContext, FunctionParam, FunctionParamError, SimpleFunctionArgKind, - SimpleFunctionDefinition, SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, + CompiledFunction, ConcatFunction, FunctionArgInvalidConstantError, FunctionArgKind, + FunctionArgKindMismatchError, FunctionArgs, FunctionDefinition, FunctionDefinitionContext, + FunctionParam, FunctionParamError, SimpleFunctionArgKind, SimpleFunctionDefinition, + SimpleFunctionImpl, SimpleFunctionOptParam, SimpleFunctionParam, }; pub use self::lex::LexErrorKind; pub use self::lhs_types::{Array, Bytes, Map, MapIter, TypedArray, TypedMap};