Skip to content

Commit c7ee4e2

Browse files
Support LogicalExpr for op in FilterValueAst
1 parent af1a1e9 commit c7ee4e2

4 files changed

Lines changed: 148 additions & 12 deletions

File tree

engine/src/ast/logical_expr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ impl LogicalExpr {
206206
}
207207
}
208208

209-
fn lex_quantifier_expr<'i>(
209+
pub(crate) fn lex_quantifier_expr<'i>(
210210
input: &'i str,
211211
parser: &FilterParser<'_>,
212212
) -> Option<LexResult<'i, Self>> {

engine/src/ast/mod.rs

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::compiler::{Compiler, DefaultCompiler};
1313
use crate::filter::{CompiledExpr, CompiledValueExpr, Filter, FilterValue};
1414
use crate::lex::{LexErrorKind, LexResult, LexWith};
1515
use crate::scheme::{Scheme, UnknownFieldError};
16-
use crate::types::{GetType, Type, TypeMismatchError};
16+
use crate::types::{GetType, LhsValue, Type, TypeMismatchError};
1717
use serde::Serialize;
1818
use std::fmt::{self, Debug};
1919

@@ -164,6 +164,75 @@ impl FilterAst {
164164
}
165165
}
166166

167+
/// The root expression of a parsed value AST.
168+
#[derive(PartialEq, Eq, Serialize, Clone, Hash)]
169+
#[serde(untagged)]
170+
pub enum FilterValueExpr {
171+
/// An indexed field or function-call expression.
172+
Index(IndexExpr),
173+
/// A logical expression that evaluates to a scalar boolean value.
174+
Logical(LogicalExpr),
175+
}
176+
177+
impl Debug for FilterValueExpr {
178+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179+
match self {
180+
Self::Index(expr) => expr.fmt(f),
181+
Self::Logical(expr) => expr.fmt(f),
182+
}
183+
}
184+
}
185+
186+
impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueExpr {
187+
fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> {
188+
if let Some(result) = LogicalExpr::lex_quantifier_expr(input, parser) {
189+
result.map(|(expr, rest)| (FilterValueExpr::Logical(expr), rest))
190+
} else {
191+
IndexExpr::lex_with(input, parser)
192+
.map(|(expr, rest)| (FilterValueExpr::Index(expr), rest))
193+
}
194+
}
195+
}
196+
197+
impl ValueExpr for FilterValueExpr {
198+
fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) {
199+
match self {
200+
Self::Index(expr) => visitor.visit_index_expr(expr),
201+
Self::Logical(expr) => visitor.visit_logical_expr(expr),
202+
}
203+
}
204+
205+
fn walk_mut<'a, V: VisitorMut<'a>>(&'a mut self, visitor: &mut V) {
206+
match self {
207+
Self::Index(expr) => visitor.visit_index_expr(expr),
208+
Self::Logical(expr) => visitor.visit_logical_expr(expr),
209+
}
210+
}
211+
212+
fn compile_with_compiler<C: Compiler>(self, compiler: &mut C) -> CompiledValueExpr<C::U> {
213+
match self {
214+
Self::Index(expr) => compiler.compile_index_expr(expr),
215+
Self::Logical(expr) => match compiler.compile_logical_expr(expr) {
216+
CompiledExpr::One(expr) => {
217+
CompiledValueExpr::new(move |ctx| LhsValue::from(expr.execute(ctx)).into())
218+
}
219+
CompiledExpr::Vec(expr) => CompiledValueExpr::new(move |ctx| {
220+
LhsValue::Array(expr.execute(ctx).into()).into()
221+
}),
222+
},
223+
}
224+
}
225+
}
226+
227+
impl GetType for FilterValueExpr {
228+
fn get_type(&self) -> Type {
229+
match self {
230+
Self::Index(expr) => expr.get_type(),
231+
Self::Logical(expr) => expr.get_type(),
232+
}
233+
}
234+
}
235+
167236
/// A parsed value AST.
168237
///
169238
/// It's attached to its corresponding [`Scheme`](struct@Scheme) because all
@@ -175,7 +244,7 @@ pub struct FilterValueAst {
175244
#[serde(skip)]
176245
scheme: Scheme,
177246

178-
op: IndexExpr,
247+
op: FilterValueExpr,
179248
}
180249

181250
impl Debug for FilterValueAst {
@@ -186,12 +255,14 @@ impl Debug for FilterValueAst {
186255

187256
impl<'i, 's> LexWith<'i, &FilterParser<'s>> for FilterValueAst {
188257
fn lex_with(input: &'i str, parser: &FilterParser<'s>) -> LexResult<'i, Self> {
189-
let (op, rest) = IndexExpr::lex_with(input.trim(), parser)?;
190-
if op.map_each_count() > 0 {
258+
let (op, rest) = FilterValueExpr::lex_with(input.trim(), parser)?;
259+
if let FilterValueExpr::Index(expr) = &op
260+
&& expr.map_each_count() > 0
261+
{
191262
Err((
192263
LexErrorKind::TypeMismatch(TypeMismatchError {
193-
expected: op.get_type().into(),
194-
actual: Type::Array(op.get_type().into()),
264+
expected: expr.get_type().into(),
265+
actual: Type::Array(expr.get_type().into()),
195266
}),
196267
input,
197268
))
@@ -216,20 +287,20 @@ impl FilterValueAst {
216287

217288
/// Returns the associated expression.
218289
#[inline]
219-
pub fn expression(&self) -> &IndexExpr {
290+
pub fn expression(&self) -> &FilterValueExpr {
220291
&self.op
221292
}
222293

223294
/// Recursively visit all nodes in the AST using a [`Visitor`].
224295
#[inline]
225296
pub fn walk<'a, V: Visitor<'a>>(&'a self, visitor: &mut V) {
226-
visitor.visit_index_expr(&self.op)
297+
visitor.visit_value_expr(&self.op)
227298
}
228299

229300
/// Recursively visit all nodes in the AST using a [`VisitorMut`].
230301
#[inline]
231302
pub fn walk_mut<'a, V: VisitorMut<'a>>(&'a mut self, visitor: &mut V) {
232-
visitor.visit_index_expr(&mut self.op)
303+
visitor.visit_value_expr(&mut self.op)
233304
}
234305

235306
/// Recursively checks whether a [`FilterAst`] uses a given field name.
@@ -254,7 +325,7 @@ impl FilterValueAst {
254325

255326
/// Compiles a [`FilterValueAst`] into a [`FilterValue`] using a specific [`Compiler`].
256327
pub fn compile_with_compiler<C: Compiler>(self, compiler: &mut C) -> FilterValue<C::U> {
257-
FilterValue::new(compiler.compile_index_expr(self.op), self.scheme)
328+
FilterValue::new(compiler.compile_value_expr(self.op), self.scheme)
258329
}
259330

260331
/// Compiles a [`FilterValueAst`] into a [`FilterValue`] using the [`DefaultCompiler`].

engine/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub use self::ast::logical_expr::{
8989
};
9090
pub use self::ast::parse::{FilterParser, ParseError, ParserSettings};
9191
pub use self::ast::visitor::{Visitor, VisitorMut};
92-
pub use self::ast::{Expr, FilterAst, FilterValueAst, ValueExpr};
92+
pub use self::ast::{Expr, FilterAst, FilterValueAst, FilterValueExpr, ValueExpr};
9393
pub use self::compiler::{Compiler, DefaultCompiler};
9494
pub use self::execution_context::{
9595
ExecutionContext, ExecutionContextGuard, InvalidListMatcherError, SetFieldValueError,

engine/src/scheme.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,71 @@ fn test_parse_error() {
12601260
}
12611261
}
12621262

1263+
#[test]
1264+
fn test_parse_quantifier_as_value() {
1265+
use crate::{Array, ExecutionContext, LhsValue};
1266+
1267+
let scheme = Scheme! {
1268+
values: Array(Bytes),
1269+
}
1270+
.build();
1271+
let ast = scheme
1272+
.parse_value(r#"any(values[*] in {"HIT" "UPDATE"})"#)
1273+
.unwrap();
1274+
1275+
assert_json!(scheme.parse_value("values").unwrap(), "values");
1276+
assert!(
1277+
scheme
1278+
.parse_value(r#"all(values[*] in {"HIT" "UPDATE"})"#)
1279+
.is_ok()
1280+
);
1281+
1282+
assert_eq!(ast.get_type(), Type::Bool);
1283+
assert_json!(
1284+
ast,
1285+
{
1286+
"op": "Any",
1287+
"arg": {
1288+
"kind": "SimpleExpr",
1289+
"value": {
1290+
"lhs": ["values", { "kind": "MapEach" }],
1291+
"op": "OneOf",
1292+
"rhs": ["HIT", "UPDATE"]
1293+
}
1294+
}
1295+
}
1296+
);
1297+
1298+
let mut ctx = ExecutionContext::new(&scheme);
1299+
ctx.set_field_value(
1300+
scheme.get_field("values").unwrap(),
1301+
Array::from_iter(["MISS", "HIT"]),
1302+
)
1303+
.unwrap();
1304+
assert_eq!(ast.compile().execute(&ctx), Ok(Ok(LhsValue::Bool(true))));
1305+
}
1306+
1307+
#[test]
1308+
fn test_parse_value_rejects_non_quantifier_logical_expressions() {
1309+
let scheme = Scheme! {
1310+
values: Array(Bytes),
1311+
}
1312+
.build();
1313+
1314+
for input in [
1315+
// LogicalExpr::Comparison
1316+
r#"values[0] == "HIT""#,
1317+
// LogicalExpr::Parenthesized
1318+
r#"(any(values[*] == "HIT"))"#,
1319+
// LogicalExpr::Unary
1320+
r#"not any(values[*] == "HIT")"#,
1321+
// LogicalExpr::Combining
1322+
r#"any(values[*] == "HIT") and any(values[*] == "UPDATE")"#,
1323+
] {
1324+
assert!(scheme.parse_value(input).is_err(), "parsed {input:?}");
1325+
}
1326+
}
1327+
12631328
#[test]
12641329
fn test_parse_error_in_op() {
12651330
use cidr::errors::NetworkParseError;

0 commit comments

Comments
 (0)