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

Avoid creating a new ExpressionParser every time Parser::parse() is used #32

Open
wants to merge 6 commits 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
49 changes: 32 additions & 17 deletions jexl-eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
//! For example:
//! ```rust
//! use jexl_eval::Evaluator;
//! use jexl_parser::Parser;
//! use serde_json::json as value;
//! let parser = Parser::new();
//! let context = value!({"a": {"b": 2.0}});
//! let evaluator = Evaluator::new();
//! assert_eq!(evaluator.eval_in_context("a.b", context).unwrap(), value!(2.0));
//! assert_eq!(evaluator.eval_in_context(&parser, "a.b", context).unwrap(), value!(2.0));
//! ```
//!

Expand Down Expand Up @@ -138,15 +140,19 @@ impl<'a> Evaluator<'a> {

pub fn eval<'b>(&self, input: &'b str) -> Result<'b, Value> {
let context = value!({});
self.eval_in_context(input, &context)
// FIXME: we create the parser internally in eval() to minimize changes in function signatures, tests, etc.
// For our use case we don't use this function, but maybe makes sense to move this to function parameter
let parser = Parser::new();
self.eval_in_context(&parser, input, &context)
}

pub fn eval_in_context<'b, T: serde::Serialize>(
&self,
parser: &Parser,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make the parser be an argument to ::new() and add it as a field on the evaluator. This will be a breaking change, but its a better design.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for you feedback! We'll have a look and come back...

input: &'b str,
context: T,
) -> Result<'b, Value> {
let tree = Parser::parse(input)?;
let tree = parser.parse(input)?;
let context = serde_json::to_value(context)?;
if !context.is_object() {
return Err(EvaluationError::InvalidContext);
Expand Down Expand Up @@ -435,24 +441,27 @@ mod tests {

#[test]
fn test_identifier() {
let parser = Parser::new();
let context = value!({"a": 1.0});
assert_eq!(
Evaluator::new().eval_in_context("a", context).unwrap(),
Evaluator::new().eval_in_context(&parser, "a", context).unwrap(),
value!(1.0)
);
}

#[test]
fn test_identifier_chain() {
let parser = Parser::new();
let context = value!({"a": {"b": 2.0}});
assert_eq!(
Evaluator::new().eval_in_context("a.b", context).unwrap(),
Evaluator::new().eval_in_context(&parser, "a.b", context).unwrap(),
value!(2.0)
);
}

#[test]
fn test_context_filter_arrays() {
let parser = Parser::new();
let context = value!({
"foo": {
"bar": [
Expand All @@ -464,14 +473,15 @@ mod tests {
});
assert_eq!(
Evaluator::new()
.eval_in_context("foo.bar[.tek == 'baz']", &context)
.eval_in_context(&parser, "foo.bar[.tek == 'baz']", &context)
.unwrap(),
value!([{"tek": "baz"}])
);
}

#[test]
fn test_context_array_index() {
let parser = Parser::new();
let context = value!({
"foo": {
"bar": [
Expand All @@ -483,18 +493,19 @@ mod tests {
});
assert_eq!(
Evaluator::new()
.eval_in_context("foo.bar[1].tek", context)
.eval_in_context(&parser, "foo.bar[1].tek", context)
.unwrap(),
value!("baz")
);
}

#[test]
fn test_object_expression_properties() {
let parser = Parser::new();
let context = value!({"foo": {"baz": {"bar": "tek"}}});
assert_eq!(
Evaluator::new()
.eval_in_context("foo['ba' + 'z'].bar", &context)
.eval_in_context(&parser, "foo['ba' + 'z'].bar", &context)
.unwrap(),
value!("tek")
);
Expand Down Expand Up @@ -684,7 +695,8 @@ mod tests {
});

let test = |expr: &str, is_ok: bool, exp: Value| {
let obs = evaluator.eval_in_context(&expr, context.clone());
let parser = Parser::new();
let obs = evaluator.eval_in_context(&parser, &expr, context.clone());
if !is_ok {
assert!(obs.is_err());
assert!(matches!(
Expand Down Expand Up @@ -789,6 +801,7 @@ mod tests {
#[test]
fn test_filter_collections_many_returned() {
let evaluator = Evaluator::new();
let parser = Parser::new();
let context = value!({
"foo": [
{"bobo": 50, "fofo": 100},
Expand All @@ -799,14 +812,15 @@ mod tests {
});
let exp = "foo[.bobo >= 50]";
assert_eq!(
evaluator.eval_in_context(exp, context).unwrap(),
evaluator.eval_in_context(&parser, exp, context).unwrap(),
value!([{"bobo": 50, "fofo": 100}, {"bobo": 60, "baz": 90}])
);
}

#[test]
fn test_binary_op_eq_ne() {
let evaluator = Evaluator::new();
let parser = Parser::new();
let context = value!({
"NULL": null,
"STRING": "string",
Expand All @@ -819,13 +833,13 @@ mod tests {
let test = |l: &str, r: &str, exp: bool| {
let expr = format!("{} == {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(exp)
);

let expr = format!("{} != {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(!exp)
);
};
Expand Down Expand Up @@ -885,6 +899,7 @@ mod tests {
#[test]
fn test_binary_op_string_gt_lt_gte_lte() {
let evaluator = Evaluator::new();
let parser = Parser::new();
let context = value!({
"A": "A string",
"B": "B string",
Expand All @@ -893,34 +908,34 @@ mod tests {
let test = |l: &str, r: &str, is_gt: bool| {
let expr = format!("{} > {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(is_gt)
);

let expr = format!("{} <= {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(!is_gt)
);

// we test equality in another test
let expr = format!("{} == {}", l, r);
let is_eq = evaluator
.eval_in_context(&expr, context.clone())
.eval_in_context(&parser, &expr, context.clone())
.unwrap()
.as_bool()
.unwrap();

if is_eq {
let expr = format!("{} >= {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(true)
);
} else {
let expr = format!("{} < {}", l, r);
assert_eq!(
evaluator.eval_in_context(&expr, context.clone()).unwrap(),
evaluator.eval_in_context(&parser, &expr, context.clone()).unwrap(),
value!(!is_gt)
);
}
Expand Down
51 changes: 36 additions & 15 deletions jexl-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ pub use lalrpop_util::ParseError;

pub use crate::parser::Token;

pub struct Parser {}
pub struct Parser {
parser: parser::ExpressionParser
}

impl Parser {
pub fn parse(input: &str) -> Result<ast::Expression, ParseError<usize, Token, &str>> {
Ok(*parser::ExpressionParser::new().parse(input)?)

pub fn new() -> Self {
Parser {
parser: parser::ExpressionParser::new()
}
}

pub fn parse<'a>(& self, input: &'a str) -> Result<ast::Expression, ParseError<usize, Token<'a>, &'static str>> {
Ok(*self.parser.parse(input)?)
}
}

Expand All @@ -25,13 +34,15 @@ mod tests {

#[test]
fn literal() {
assert_eq!(Parser::parse("1"), Ok(Expression::Number(1.0)));
let parser = Parser::new();
assert_eq!(parser.parse("1"), Ok(Expression::Number(1.0)));
}

#[test]
fn binary_expression() {
let parser = Parser::new();
assert_eq!(
Parser::parse("1+2"),
parser.parse("1+2"),
Ok(Expression::BinaryOperation {
operation: OpCode::Add,
left: Box::new(Expression::Number(1.0)),
Expand All @@ -42,13 +53,15 @@ mod tests {

#[test]
fn binary_expression_whitespace() {
assert_eq!(Parser::parse("1 + 2 "), Parser::parse("1+2"),);
let parser = Parser::new();
assert_eq!(parser.parse("1 + 2 "), parser.parse("1+2"),);
}

#[test]
fn transform_simple_no_args() {
let parser = Parser::new();
let exp = "'T_T'|lower";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::Transform {
Expand All @@ -61,8 +74,9 @@ mod tests {

#[test]
fn transform_multiple_args() {
let parser = Parser::new();
let exp = "'John Doe'|split(' ')";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::Transform {
Expand All @@ -75,8 +89,9 @@ mod tests {

#[test]
fn trasform_way_too_many_args() {
let parser = Parser::new();
let exp = "123456|math(12, 35, 100, 31, 90)";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::Transform {
Expand All @@ -95,8 +110,9 @@ mod tests {

#[test]
fn test_index_op_ident() {
let parser = Parser::new();
let exp = "foo[0]";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::IndexOperation {
Expand All @@ -108,8 +124,9 @@ mod tests {

#[test]
fn test_index_op_array_literal() {
let parser = Parser::new();
let exp = "[1, 2, 3][0]";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::IndexOperation {
Expand All @@ -125,8 +142,9 @@ mod tests {

#[test]
fn test_dot_op_ident() {
let parser = Parser::new();
let exp = "foo.bar";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::DotOperation {
Expand All @@ -138,8 +156,9 @@ mod tests {

#[test]
fn test_dot_op_equality_with_null() {
let parser = Parser::new();
let exp = "foo.bar == null";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::BinaryOperation {
Expand All @@ -155,8 +174,9 @@ mod tests {

#[test]
fn test_dot_op_object_literal() {
let parser = Parser::new();
let exp = "{'foo': 1}.foo";
let parsed = Parser::parse(exp).unwrap();
let parsed = parser.parse(exp).unwrap();
assert_eq!(
parsed,
Expression::DotOperation {
Expand All @@ -171,6 +191,7 @@ mod tests {

#[test]
fn test_parsing_null() {
assert_eq!(Parser::parse("null"), Ok(Expression::Null));
let parser = Parser::new();
assert_eq!(parser.parse("null"), Ok(Expression::Null));
}
}