Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions engine/src/ast/logical_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -206,10 +206,10 @@ impl LogicalExpr {
}
}

fn lex_quantifier_expr<'i>(
pub(crate) fn lex_quantifier<'i>(
input: &'i str,
parser: &FilterParser<'_>,
) -> Option<LexResult<'i, Self>> {
) -> Option<LexResult<'i, (QuantifierOp, Box<QuantifierArgExpr>)>> {
let (op, rest) = QuantifierOp::lex_call(input)?;
let nested_parser = match parser.with_increased_nesting(skip_space(rest)) {
Ok(parser) => parser,
Expand All @@ -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))
})())
}

Expand All @@ -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)
Expand Down
105 changes: 94 additions & 11 deletions engine/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<QuantifierArgExpr>,
},
}

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<C: Compiler>(self, compiler: &mut C) -> CompiledValueExpr<C::U> {
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
Expand All @@ -175,7 +256,7 @@ pub struct FilterValueAst {
#[serde(skip)]
scheme: Scheme,

op: IndexExpr,
op: FilterValueExpr,
}

impl Debug for FilterValueAst {
Expand All @@ -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,
))
Expand All @@ -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.
Expand All @@ -254,7 +337,7 @@ impl FilterValueAst {

/// Compiles a [`FilterValueAst`] into a [`FilterValue`] using a specific [`Compiler`].
pub fn compile_with_compiler<C: Compiler>(self, compiler: &mut C) -> FilterValue<C::U> {
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`].
Expand Down
2 changes: 1 addition & 1 deletion engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
65 changes: 65 additions & 0 deletions engine/src/scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,71 @@ fn test_parse_error() {
}
}

#[test]
fn test_parse_quantifier_as_value() {
Comment thread
utkarshgupta137 marked this conversation as resolved.
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;
Expand Down
Loading