diff --git a/engine/src/ast/logical_expr.rs b/engine/src/ast/logical_expr.rs index 7db2720d..7c535174 100644 --- a/engine/src/ast/logical_expr.rs +++ b/engine/src/ast/logical_expr.rs @@ -95,14 +95,14 @@ fn bool_array_type() -> Type { } impl QuantifierArgExpr { - fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) { + pub(crate) 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) { + pub(crate) 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), @@ -206,10 +206,10 @@ impl LogicalExpr { } } - fn lex_quantifier_expr<'i>( + pub(crate) fn lex_quantifier<'i>( input: &'i str, parser: &FilterParser<'_>, - ) -> Option> { + ) -> Option)>> { let (op, rest) = QuantifierOp::lex_call(input)?; let nested_parser = match parser.with_increased_nesting(skip_space(rest)) { Ok(parser) => parser, @@ -222,13 +222,7 @@ impl LogicalExpr { 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, - )) + Ok(((op, Box::new(arg)), input)) })()) } @@ -254,8 +248,9 @@ impl LogicalExpr { }, input, ) - } else if let Some(result) = Self::lex_quantifier_expr(input, parser) { - return result; + } else if let Some(result) = Self::lex_quantifier(input, parser) { + let ((op, arg), input) = result?; + (LogicalExpr::Quantifier { op, arg }, input) } else { let (op, input) = ComparisonExpr::lex_with(input, parser)?; (LogicalExpr::Comparison(op), input) diff --git a/engine/src/ast/mod.rs b/engine/src/ast/mod.rs index b158ea9a..84236d11 100644 --- a/engine/src/ast/mod.rs +++ b/engine/src/ast/mod.rs @@ -6,14 +6,14 @@ pub mod parse; pub mod visitor; use self::index_expr::IndexExpr; -use self::logical_expr::LogicalExpr; +use self::logical_expr::{LogicalExpr, QuantifierArgExpr, QuantifierOp}; use self::parse::FilterParser; use self::visitor::{UsesListVisitor, UsesVisitor, Visitor, VisitorMut}; use crate::compiler::{Compiler, DefaultCompiler}; use crate::filter::{CompiledExpr, CompiledValueExpr, Filter, FilterValue}; use crate::lex::{LexErrorKind, LexResult, LexWith}; use crate::scheme::{Scheme, UnknownFieldError}; -use crate::types::{GetType, Type, TypeMismatchError}; +use crate::types::{GetType, LhsValue, Type, TypeMismatchError}; use serde::Serialize; use std::fmt::{self, Debug}; @@ -164,6 +164,87 @@ impl FilterAst { } } +/// The root expression of a parsed value AST. +#[derive(PartialEq, Eq, Serialize, Clone, Hash)] +#[serde(untagged)] +pub enum FilterValueExpr { + /// An indexed field or function-call expression. + Index(IndexExpr), + /// An `any(...)` or `all(...)` quantifier expression. + Quantifier { + /// The quantifier operator to apply. + op: QuantifierOp, + /// The boolean array expression to reduce. + arg: Box, + }, +} + +impl Debug for FilterValueExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Index(expr) => expr.fmt(f), + Self::Quantifier { op, arg } => f + .debug_struct("Quantifier") + .field("op", op) + .field("arg", arg) + .finish(), + } + } +} + +impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueExpr { + fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { + match IndexExpr::lex_with(input, parser) { + Ok((expr, rest)) => Ok((FilterValueExpr::Index(expr), rest)), + Err(index_err) => match LogicalExpr::lex_quantifier(input, parser) { + Some(result) => { + result.map(|((op, arg), rest)| (FilterValueExpr::Quantifier { op, arg }, rest)) + } + None => Err(index_err), + }, + } + } +} + +impl ValueExpr for FilterValueExpr { + fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) { + match self { + Self::Index(expr) => visitor.visit_index_expr(expr), + Self::Quantifier { arg, .. } => arg.walk(visitor), + } + } + + fn walk_mut<'a, V: VisitorMut<'a>>(&'a mut self, visitor: &mut V) { + match self { + Self::Index(expr) => visitor.visit_index_expr(expr), + Self::Quantifier { arg, .. } => arg.walk_mut(visitor), + } + } + + fn compile_with_compiler(self, compiler: &mut C) -> CompiledValueExpr { + match self { + Self::Index(expr) => compiler.compile_index_expr(expr), + Self::Quantifier { op, arg } => { + match compiler.compile_logical_expr(LogicalExpr::Quantifier { op, arg }) { + CompiledExpr::One(expr) => { + CompiledValueExpr::new(move |ctx| LhsValue::from(expr.execute(ctx)).into()) + } + CompiledExpr::Vec(_) => unreachable!(), + } + } + } + } +} + +impl GetType for FilterValueExpr { + fn get_type(&self) -> Type { + match self { + Self::Index(expr) => expr.get_type(), + Self::Quantifier { .. } => Type::Bool, + } + } +} + /// A parsed value AST. /// /// It's attached to its corresponding [`Scheme`](struct@Scheme) because all @@ -175,7 +256,7 @@ pub struct FilterValueAst { #[serde(skip)] scheme: Scheme, - op: IndexExpr, + op: FilterValueExpr, } impl Debug for FilterValueAst { @@ -186,12 +267,14 @@ impl Debug for FilterValueAst { impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueAst { fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> { - let (op, rest) = IndexExpr::lex_with(input.trim(), parser)?; - if op.map_each_count() > 0 { + let (op, rest) = FilterValueExpr::lex_with(input.trim(), parser)?; + if let FilterValueExpr::Index(expr) = &op + && expr.map_each_count() > 0 + { Err(( LexErrorKind::TypeMismatch(TypeMismatchError { - expected: op.get_type().into(), - actual: Type::Array(op.get_type().into()), + expected: expr.get_type().into(), + actual: Type::Array(expr.get_type().into()), }), input, )) @@ -216,20 +299,20 @@ impl FilterValueAst { /// Returns the associated expression. #[inline] - pub fn expression(&self) -> &IndexExpr { + pub fn expression(&self) -> &FilterValueExpr { &self.op } /// Recursively visit all nodes in the AST using a [`Visitor`]. #[inline] pub fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) { - visitor.visit_index_expr(&self.op) + visitor.visit_value_expr(&self.op) } /// Recursively visit all nodes in the AST using a [`VisitorMut`]. #[inline] pub fn walk_mut<'a, V: VisitorMut<'a>>(&'a mut self, visitor: &mut V) { - visitor.visit_index_expr(&mut self.op) + visitor.visit_value_expr(&mut self.op) } /// Recursively checks whether a [`FilterAst`] uses a given field name. @@ -254,7 +337,7 @@ impl FilterValueAst { /// Compiles a [`FilterValueAst`] into a [`FilterValue`] using a specific [`Compiler`]. pub fn compile_with_compiler(self, compiler: &mut C) -> FilterValue { - FilterValue::new(compiler.compile_index_expr(self.op), self.scheme) + FilterValue::new(compiler.compile_value_expr(self.op), self.scheme) } /// Compiles a [`FilterValueAst`] into a [`FilterValue`] using the [`DefaultCompiler`]. diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 3944713b..3f1b5c60 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -89,7 +89,7 @@ pub use self::ast::logical_expr::{ }; pub use self::ast::parse::{FilterParser, ParseError, ParserSettings}; pub use self::ast::visitor::{Visitor, VisitorMut}; -pub use self::ast::{Expr, FilterAst, FilterValueAst, ValueExpr}; +pub use self::ast::{Expr, FilterAst, FilterValueAst, FilterValueExpr, ValueExpr}; pub use self::compiler::{Compiler, DefaultCompiler}; pub use self::execution_context::{ ExecutionContext, ExecutionContextGuard, InvalidListMatcherError, SetFieldValueError, diff --git a/engine/src/scheme.rs b/engine/src/scheme.rs index db21743f..303fc29f 100644 --- a/engine/src/scheme.rs +++ b/engine/src/scheme.rs @@ -1260,6 +1260,71 @@ fn test_parse_error() { } } +#[test] +fn test_parse_quantifier_as_value() { + use crate::{Array, ExecutionContext, LhsValue}; + + let scheme = Scheme! { + values: Array(Bytes), + } + .build(); + let ast = scheme + .parse_value(r#"any(values[*] in {"HIT" "UPDATE"})"#) + .unwrap(); + + assert_json!(scheme.parse_value("values").unwrap(), "values"); + assert!( + scheme + .parse_value(r#"all(values[*] in {"HIT" "UPDATE"})"#) + .is_ok() + ); + + assert_eq!(ast.get_type(), Type::Bool); + assert_json!( + ast, + { + "op": "Any", + "arg": { + "kind": "SimpleExpr", + "value": { + "lhs": ["values", { "kind": "MapEach" }], + "op": "OneOf", + "rhs": ["HIT", "UPDATE"] + } + } + } + ); + + let mut ctx = ExecutionContext::new(&scheme); + ctx.set_field_value( + scheme.get_field("values").unwrap(), + Array::from_iter(["MISS", "HIT"]), + ) + .unwrap(); + assert_eq!(ast.compile().execute(&ctx), Ok(Ok(LhsValue::Bool(true)))); +} + +#[test] +fn test_parse_value_rejects_non_quantifier_logical_expressions() { + let scheme = Scheme! { + values: Array(Bytes), + } + .build(); + + for input in [ + // LogicalExpr::Comparison + r#"values[0] == "HIT""#, + // LogicalExpr::Parenthesized + r#"(any(values[*] == "HIT"))"#, + // LogicalExpr::Unary + r#"not any(values[*] == "HIT")"#, + // LogicalExpr::Combining + r#"any(values[*] == "HIT") and any(values[*] == "UPDATE")"#, + ] { + assert!(scheme.parse_value(input).is_err(), "parsed {input:?}"); + } +} + #[test] fn test_parse_error_in_op() { use cidr::errors::NetworkParseError;