evalx 0.5.0

Expression evaluator
Documentation
//! Evalx is a powerful expression evaluator.
//!
//! This crate is a maintained fork and further development of the original
//! [`eval` by fengcen](https://github.com/fengcen/eval). It includes bugfixes
//! and updates such as migrating the Rust edition to 2021 while keeping the
//! public API compatible.
//!
//! Supported operators: `!` `!=` `""` `''` `()` `[]` `.` `,` `>` `<` `>=` `<=`
//! `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`.
//!
//! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`.
//!
//! ## Examples
//!
//! You can do mathematical calculations with supported operators:
//!
//! ```
//! use evalx::{eval, to_value};
//!
//! assert_eq!(eval("1 + 2 + 3"), Ok(to_value(6)));
//! assert_eq!(eval("2 * 2 + 3"), Ok(to_value(7)));
//! assert_eq!(eval("2 / 2 + 3"), Ok(to_value(4.0)));
//! assert_eq!(eval("2 / 2 + 3 / 3"), Ok(to_value(2.0)));
//! ```
//!
//! You can eval with context:
//!
//! ```
//! use evalx::{Expr, to_value};
//!
//! assert_eq!(Expr::new("foo == bar")
//!                .value("foo", true)
//!                .value("bar", true)
//!                .exec(),
//!            Ok(to_value(true)));
//! ```
//!
//! You can access data like javascript by using `.` and `[]`. `[]` supports expression.
//!
//! ```
//! use evalx::{Expr, to_value};
//! use std::collections::HashMap;
//!
//! let mut object = HashMap::new();
//! object.insert("foos", vec!["Hello", "world", "!"]);
//!
//! assert_eq!(Expr::new("object.foos[2-1] == 'world'") // Access field `foos` and index `2-1`
//!                .value("object", object)
//!                .exec(),
//!            Ok(to_value(true)));
//! ```
//!
//! You can eval with function:
//!
//! ```
//! use evalx::{Expr, to_value};
//!
//! assert_eq!(Expr::new("say_hello()")
//!                .function("say_hello", |_| Ok(to_value("Hello world!")))
//!                .exec(),
//!            Ok(to_value("Hello world!")));
//! ```
//!
//! You can create an array with `array()`:
//!
//! ```
//! use evalx::{eval, to_value};
//!
//! assert_eq!(eval("array(1, 2, 3, 4, 5)"), Ok(to_value(vec![1, 2, 3, 4, 5])));
//! ```
//!
//! You can create an integer array with `n..m`:
//!
//! ```
//! use evalx::{eval, to_value};
//!
//! assert_eq!(eval("0..5"), Ok(to_value(vec![0, 1, 2, 3, 4])));
//! ```
//!
//! ## Built-in functions
//!
//! ### `min()`
//! Accept multiple arguments and return the minimum value.
//!
//! ### `max()`
//! Accept multiple arguments and return the maximum value.
//!
//! ### `len()`
//! Accept single arguments and return the length of value. Only accept String, Array, Object and Null.
//!
//! ### `is_empty()`
//! Accept single arguments and return a boolean. Check whether the value is empty or not.
//!
//! ### `array()`
//! Accept multiple arguments and return an array.
//!
//!
#![recursion_limit="100"]
#![deny(missing_docs)]
#![cfg_attr(all(feature = "unstable", test), feature(test))]

mod math;
mod function;
mod operator;
mod node;
mod tree;
mod error;
mod builtin;
mod expr;

pub use expr::ExecOptions;
pub use serde_json::Value;
pub use error::Error;
pub use function::Function;
pub use expr::Expr;

use std::collections::HashMap;
use serde_json::to_value as json_to_value;
use serde::Serialize;

/// Convert variable to `serde_json::Value`
pub fn to_value<S: Serialize>(v: S) -> Value {
    json_to_value(v).unwrap()
}

/// Custom context.
pub type Context = HashMap<String, Value>;
/// Custom contexts. The value of the last context is searched first.
pub type Contexts = Vec<Context>;
/// Custom functions.
pub type Functions = HashMap<String, Function>;

/// Evaluates the value of an expression.
pub fn eval(expr: &str) -> Result<Value, Error> {
    Expr::new(expr).compile()?.exec()
}


type Compiled = Box<dyn Fn(&[Context], &Functions) -> Result<Value, Error>>;



#[cfg(all(feature = "unstable", test))]
mod benches {
    extern crate test;
    use crate::eval;
    use crate::tree::Tree;
    use crate::Expr;

    #[bench]
    fn bench_deep_brackets(b: &mut test::Bencher) {
        b.iter(|| eval("(2 + (3 + 4) + (6 + (6 + 7)) + 5)"));
    }

    #[bench]
    fn bench_parse_pos(b: &mut test::Bencher) {
        let mut tree = Tree {
            raw: "(2 + (3 + 4) + (6 + (6 + 7)) + 5)".to_owned(),
            ..Default::default()
        };

        b.iter(|| tree.parse_pos().unwrap());
    }

    #[bench]
    fn bench_parse_operators(b: &mut test::Bencher) {
        let mut tree = Tree {
            raw: "(2 + (3 + 4) + (6 + (6 + 7)) + 5)".to_owned(),
            ..Default::default()
        };

        tree.parse_pos().unwrap();
        b.iter(|| tree.parse_operators().unwrap());
    }

    #[bench]
    fn bench_parse_nodes(b: &mut test::Bencher) {
        let mut tree = Tree {
            raw: "(2 + (3 + 4) + (6 + (6 + 7)) + 5)".to_owned(),
            ..Default::default()
        };

        tree.parse_pos().unwrap();
        tree.parse_operators().unwrap();
        b.iter(|| tree.parse_node().unwrap());
    }

    #[bench]
    fn bench_compile(b: &mut test::Bencher) {
        b.iter(|| {
            let mut tree = Tree {
                raw: "(2 + (3 + 4) + (6 + (6 + 7)) + 5)".to_owned(),
                ..Default::default()
            };
            tree.parse_pos().unwrap();
            tree.parse_operators().unwrap();
            tree.parse_node().unwrap();
            tree.compile().unwrap();
        });
    }

    #[bench]
    fn bench_exec(b: &mut test::Bencher) {
        let expr = Expr::new("(2 + (3 + 4) + (6 + (6 + 7)) + 5)")
            .compile()
            .unwrap();
        b.iter(|| expr.exec().unwrap())
    }

    #[bench]
    fn bench_eval(b: &mut test::Bencher) {
        b.iter(|| eval("(2 + (3 + 4) + (6 + (6 + 7)) + 5)"));
    }
}