Skip to content

Commit

Permalink
Implement negation
Browse files Browse the repository at this point in the history
Signed-off-by: Igor Pashev <[email protected]>
  • Loading branch information
ip1981 committed May 18, 2024
1 parent 2dff4e7 commit ff521e3
Show file tree
Hide file tree
Showing 5 changed files with 778 additions and 620 deletions.
57 changes: 56 additions & 1 deletion jexl-eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
//!
use jexl_parser::{
ast::{Expression, OpCode},
ast::{Expression, OpCode, UnOpCode},
Parser,
};
use serde_json::{json as value, Value};
Expand Down Expand Up @@ -215,6 +215,9 @@ impl<'a> Evaluator<'a> {
}
}

Expression::UnaryOperation { operation, right } => {
self.apply_unary_op(operation, right, context)
}
Expression::BinaryOperation {
left,
right,
Expand Down Expand Up @@ -264,6 +267,19 @@ impl<'a> Evaluator<'a> {
}
}

fn apply_unary_op<'b>(
&self,
operation: UnOpCode,
right: Box<Expression>,
context: &Context,
) -> Result<'b, Value> {
let right = self.eval_ast(*right, context)?;

match operation {
UnOpCode::Negate => Ok(value!(!right.is_truthy())),
}
}

fn eval_op<'b>(
&self,
operation: OpCode,
Expand Down Expand Up @@ -968,4 +984,43 @@ mod tests {
assert!(res.is_ok());
assert_eq!(res.unwrap(), value!(42.0));
}

#[test]
fn test_negation() {
let evaluator = Evaluator::new();

let res = evaluator.eval("!true");
assert_eq!(res.unwrap(), value!(false));

let res = evaluator.eval("!true == !(!null)");
assert_eq!(res.unwrap(), value!(true));

let res = evaluator.eval(r#"!(!"") != true"#);
assert_eq!(res.unwrap(), value!(true));

let res = evaluator.eval("!0.0 in [true]");
assert_eq!(res.unwrap(), value!(true));

let res = evaluator.eval("! (2 in [3, 2, 1])");
assert_eq!(res.unwrap(), value!(false));

let context = value!({
"foo": [false]
});
let res = evaluator.eval_in_context("foo[0] == false", &context);
assert_eq!(res.unwrap(), value!(true));

let res = evaluator.eval_in_context("!foo[0] == true", &context);
assert_eq!(res.unwrap(), value!(true));

let context = value!({
"foo": value!({"bar": false})
});

let res = evaluator.eval_in_context("foo.bar == false", &context);
assert_eq!(res.unwrap(), value!(true));

let res = evaluator.eval_in_context("!foo.bar == true", &context);
assert_eq!(res.unwrap(), value!(true));
}
}
10 changes: 10 additions & 0 deletions jexl-parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub enum Expression {
Identifier(String),
Null,

UnaryOperation {
operation: UnOpCode,
right: Box<Expression>,
},

BinaryOperation {
operation: OpCode,
left: Box<Expression>,
Expand Down Expand Up @@ -44,6 +49,11 @@ pub enum Expression {
},
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum UnOpCode {
Negate,
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum OpCode {
Add,
Expand Down
32 changes: 31 additions & 1 deletion jexl-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl Parser {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expression, OpCode};
use crate::ast::{Expression, OpCode, UnOpCode};

#[test]
fn literal() {
Expand Down Expand Up @@ -169,6 +169,36 @@ mod tests {
);
}

#[test]
fn test_simple_negation() {
let exp = "!false";
let parsed = Parser::parse(exp).unwrap();
assert_eq!(
parsed,
Expression::UnaryOperation {
operation: UnOpCode::Negate,
right: Box::new(Expression::Boolean(false)),
}
);
}

#[test]
fn test_negation() {
let exp = "! a && b";
let parsed = Parser::parse(exp).unwrap();
assert_eq!(
parsed,
Expression::BinaryOperation {
operation: OpCode::And,
left: Box::new(Expression::UnaryOperation {
operation: UnOpCode::Negate,
right: Box::new(Expression::Identifier("a".to_string())),
}),
right: Box::new(Expression::Identifier("b".to_string())),
}
);
}

#[test]
fn test_parsing_null() {
assert_eq!(Parser::parse("null"), Ok(Expression::Null));
Expand Down
27 changes: 18 additions & 9 deletions jexl-parser/src/parser.lalrpop
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::str::FromStr;

use crate::ast::{Expression, OpCode};
use crate::ast::{Expression, OpCode, UnOpCode};

grammar;

Expand Down Expand Up @@ -41,6 +41,11 @@ Expr60: Box<Expression> = {
Expr70
};

Expr70: Box<Expression> = {
<operation: UnOp80> <right: Expr80> => Box::new(Expression::UnaryOperation { operation, right }),
Expr80
}


/// Expression for dereferencing.
/// Used for dereferencing object literals, array literals, and the context
Expand All @@ -49,7 +54,7 @@ Expr60: Box<Expression> = {
/// - Or an `index` operation, taking an expression on the left hand side, and another expression inside square ("[]") brackets.
///
/// # Examples:
///
///
/// Assume our context is the following
/// ```
///{
Expand All @@ -64,13 +69,13 @@ Expr60: Box<Expression> = {
/// `foo.bar[0] == {"baz": 1}`
/// `foo.bar[1].bobo[0] == 13`
/// `[1, 2, 3][1] == 2`
Expr70: Box<Expression> = {
<subject: Expr70> <index: Index> => Box::new(Expression::IndexOperation{subject, index}),
<subject: Expr70> "." <ident: Identifier> => Box::new(Expression::DotOperation{subject, ident}),
Expr80
Expr80: Box<Expression> = {
<subject: Expr80> <index: Index> => Box::new(Expression::IndexOperation{subject, index}),
<subject: Expr80> "." <ident: Identifier> => Box::new(Expression::DotOperation{subject, ident}),
Expr90
};

Expr80: Box<Expression> = {
Expr90: Box<Expression> = {
Number => Box::new(Expression::Number(<>)),
Boolean => Box::new(Expression::Boolean(<>)),
String => Box::new(Expression::String(<>)),
Expand Down Expand Up @@ -116,6 +121,10 @@ Op50: OpCode = {
"^" => OpCode::Exponent,
};

UnOp80: UnOpCode = {
"!" => UnOpCode::Negate,
};

Number: f64 = {
r"[0-9]+" => f64::from_str(<>).unwrap(),
r"[0-9]+\.[0-9]*" => f64::from_str(<>).unwrap(),
Expand All @@ -136,7 +145,7 @@ Identifier: String = {
}

Index: Box<Expression> = {
"[" "." <ident: Identifier> <op: Op20> <right: Expr80> "]" => Box::new(Expression::Filter {ident, op, right}),
"[" "." <ident: Identifier> <op: Op20> <right: Expr90> "]" => Box::new(Expression::Filter {ident, op, right}),
"[" <Expression> "]",
}

Expand Down Expand Up @@ -167,6 +176,6 @@ Object: Vec<(String, Box<Expression>)> = {
}

ObjectIdentifier: String = {
String,
String,
Identifier
}
Loading

0 comments on commit ff521e3

Please sign in to comment.