Skip to content

Commit

Permalink
Added negate operator '!' (#54)
Browse files Browse the repository at this point in the history
* Added negate operator '!'

* Fix chaining '!' by moving the logic_not before parsing logic_atom

* rustfmt fail fix
  • Loading branch information
catalinstochita committed Nov 23, 2023
1 parent c7b5524 commit 89a0630
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 10 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ following elements:

| Expression sign | Description | Where to use |
|-----------------|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| `!` | Not | To negate the expression |
| `==` | Equal | To compare numbers or string literals |
| `!=` | Unequal | To compare numbers or string literals in opposite way to equals |
| `<` | Less | To compare numbers |
Expand All @@ -99,7 +100,7 @@ following elements:
| `noneOf` | The left size has no intersection with right | |
| `anyOf` | The left size has at least one intersection with right | |
| `subsetOf` | The left is a subset of the right side | |
| | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` |
| `?` | Exists operator. | The operator checks the existence of the field depicted on the left side like that `[?(@.key.isActive)]` |

Filter expressions can be chained using `||` and `&&` (logical or and logical and correspondingly) in the following way:

Expand Down
60 changes: 60 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,66 @@ mod tests {
);
}

#[test]
fn logical_not_exp_test() {
let json: Box<Value> = Box::new(json!({"first":{"second":{"active":1}}}));
let path: Box<JsonPathInst> = Box::from(
JsonPathInst::from_str("$.first[?(!@.does_not_exist >= 1.0)]")
.expect("the path is correct"),
);
let finder = JsonPathFinder::new(json.clone(), path);
let v = finder.find_slice();
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);

let path: Box<JsonPathInst> = Box::from(
JsonPathInst::from_str("$.first[?(!(@.does_not_exist >= 1.0))]")
.expect("the path is correct"),
);
let finder = JsonPathFinder::new(json.clone(), path);
let v = finder.find_slice();
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);

let path: Box<JsonPathInst> = Box::from(
JsonPathInst::from_str("$.first[?(!(@.second.active == 1) || @.second.active == 1)]")
.expect("the path is correct"),
);
let finder = JsonPathFinder::new(json.clone(), path);
let v = finder.find_slice();
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);

let path: Box<JsonPathInst> = Box::from(
JsonPathInst::from_str("$.first[?(!@.second.active == 1 && !@.second.active == 1 || !@.second.active == 2)]")
.expect("the path is correct"),
);
let finder = JsonPathFinder::new(json, path);
let v = finder.find_slice();
assert_eq!(
v,
vec![Slice(
&json!({"second":{"active": 1}}),
"$.['first']".to_string()
)]
);
}

// #[test]
// fn no_value_len_field_test() {
// let json: Box<Value> =
Expand Down
10 changes: 6 additions & 4 deletions src/parser/grammar/json_path.pest
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ char = _{
}
root = {"$"}
sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "size" | "noneOf" | "anyOf" | "subsetOf"}
not = {"!"}
key_lim = {!"length()" ~ (word | ASCII_DIGIT | specs)+}
key_unlim = {"[" ~ string_qt ~ "]"}
key = ${key_lim | key_unlim}
Expand All @@ -38,11 +39,12 @@ slice = {start_slice? ~ col ~ end_slice? ~ step_slice? }

unit_keys = { string_qt ~ ("," ~ string_qt)+ }
unit_indexes = { number ~ ("," ~ number)+ }
filter = {"?"~ "(" ~ logic ~ ")"}
filter = {"?"~ "(" ~ logic_or ~ ")"}

logic = {logic_and ~ ("||" ~ logic_and)*}
logic_and = {logic_atom ~ ("&&" ~ logic_atom)*}
logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic ~ ")"}
logic_or = {logic_and ~ ("||" ~ logic_and)*}
logic_and = {logic_not ~ ("&&" ~ logic_not)*}
logic_not = {not? ~ logic_atom}
logic_atom = {atom ~ (sign ~ atom)? | "(" ~ logic_or ~ ")"}

atom = {chain | string_qt | number | boolean | null}

Expand Down
2 changes: 2 additions & 0 deletions src/parser/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub enum FilterExpression {
And(Box<FilterExpression>, Box<FilterExpression>),
/// or with ||
Or(Box<FilterExpression>, Box<FilterExpression>),
/// not with !
Not(Box<FilterExpression>),
}

impl FilterExpression {
Expand Down
26 changes: 21 additions & 5 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::parser::errors::JsonPathParserError::ParserError;
use crate::parser::errors::{parser_err, JsonPathParserError};
use crate::parser::model::FilterExpression::{And, Or};
use crate::parser::model::FilterExpression::{And, Not, Or};
use crate::parser::model::{
FilterExpression, FilterSign, Function, JsonPath, JsonPathIndex, Operand,
};
Expand Down Expand Up @@ -145,10 +145,10 @@ fn parse_chain_in_operand(rule: Pair<Rule>) -> Result<Operand, JsonPathParserErr
}

fn parse_filter_index(pair: Pair<Rule>) -> Result<JsonPathIndex, JsonPathParserError> {
Ok(JsonPathIndex::Filter(parse_logic(pair.into_inner())?))
Ok(JsonPathIndex::Filter(parse_logic_or(pair.into_inner())?))
}

fn parse_logic(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
fn parse_logic_or(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
let mut expr: Option<FilterExpression> = None;
let error_message = format!("Failed to parse logical expression: {:?}", pairs);
for pair in pairs {
Expand All @@ -168,7 +168,7 @@ fn parse_logic_and(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParse
let mut expr: Option<FilterExpression> = None;
let error_message = format!("Failed to parse logical `and` expression: {:?}", pairs,);
for pair in pairs {
let next_expr = parse_logic_atom(pair.into_inner())?;
let next_expr = parse_logic_not(pair.into_inner())?;
match expr {
None => expr = Some(next_expr),
Some(e) => expr = Some(And(Box::new(e), Box::new(next_expr))),
Expand All @@ -180,10 +180,26 @@ fn parse_logic_and(pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParse
}
}

fn parse_logic_not(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
match rule {
Rule::not => {
pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)");
parse_logic_not(pairs)
.map(|expr|Not(Box::new(expr)))
},
Rule::logic_atom => parse_logic_atom(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
x => Err(JsonPathParserError::UnexpectedRuleLogicError(x, pairs)),
}
} else {
Err(JsonPathParserError::UnexpectedNoneLogicError(pairs))
}
}

fn parse_logic_atom(mut pairs: Pairs<Rule>) -> Result<FilterExpression, JsonPathParserError> {
if let Some(rule) = pairs.peek().map(|x| x.as_rule()) {
match rule {
Rule::logic => parse_logic(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
Rule::logic_or => parse_logic_or(pairs.next().expect("unreachable in arithmetic: should have a value as pairs.peek() was Some(_)").into_inner()),
Rule::atom => {
let left: Operand = parse_atom(pairs.next().unwrap())?;
if pairs.peek().is_none() {
Expand Down
9 changes: 9 additions & 0 deletions src/path/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ pub enum FilterPath<'a> {
left: PathInstance<'a>,
right: PathInstance<'a>,
},
Not {
exp: PathInstance<'a>,
},
}

impl<'a> FilterPath<'a> {
Expand All @@ -223,6 +226,9 @@ impl<'a> FilterPath<'a> {
left: Box::new(FilterPath::new(l, root)),
right: Box::new(FilterPath::new(r, root)),
},
FilterExpression::Not(exp) => FilterPath::Not {
exp: Box::new(FilterPath::new(exp, root)),
},
}
}
fn compound(
Expand Down Expand Up @@ -307,6 +313,9 @@ impl<'a> FilterPath<'a> {
!JsonPathValue::vec_as_data(right.find(Slice(curr_el, pref))).is_empty()
}
}
FilterPath::Not { exp } => {
JsonPathValue::vec_as_data(exp.find(Slice(curr_el, pref))).is_empty()
}
}
}
}
Expand Down

0 comments on commit 89a0630

Please sign in to comment.