Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement negation #30

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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