From 2b9d50909e3f58f14882b2a29d2bb5287f69b769 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Tue, 19 Mar 2019 12:01:06 +0200 Subject: [PATCH] Add quickstart guide and detailed explanation of features --- LICENSE | 2 +- README.md | 205 ++++++++++++++++++--------------------- src/configuration/mod.rs | 8 +- src/lib.rs | 197 ++++++++++++++++++------------------- src/value/mod.rs | 36 +++++++ 5 files changed, 230 insertions(+), 218 deletions(-) diff --git a/LICENSE b/LICENSE index 2ca1de6..8702654 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 fengcen +Copyright (c) 2019 Sebastian Schmidt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7306427..f29ec72 100644 --- a/README.md +++ b/README.md @@ -9,151 +9,138 @@ Evalexpr is a powerful arithmetic and boolean expression evaluator. - ## Features +## Quickstart - ### Operators - - Supported binary operators: - - | Operator | Precedence | Description | | Operator | Precedence | Description | - |----------|------------|-------------|---|----------|------------|-------------| - | + | 95 | Sum | | < | 80 | Lower than | - | - | 95 | Difference | | \> | 80 | Greater than | - | * | 100 | Product | | <= | 80 | Lower than or equal | - | / | 100 | Division | | \>= | 80 | Greater than or equal | - | % | 100 | Modulo | | == | 80 | Equal | - | && | 75 | Logical and | | != | 80 | Not equal | - | || | 70 | Logical or | | | | - - Supported unary operators: - - | Operator | Precedence | Description | - |----------|------------|-------------| - | - | 110 | Negation | - | ! | 110 | Logical not | - - ### Values - - Operators take values as arguments and produce values as results. - Values can be boolean, integer or floating point numbers. - Strings are supported as well, but there are no operations defined for them yet. - - Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. - Operators that take numbers as arguments can either take integers or floating point numbers. - If one of the arguments is a floating point number, all others are converted to floating point numbers as well, and the resulting value is a floating point number as well. - Otherwise, the result is an integer. - - ### Variables - - - - - - -Supported binary operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` `==` -`+` unary/binary `-` `*` `/` `%` `&&` `||` `n..m`. - -Supported unary operators: `` - -Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`. -See the `builtin` module for a detailed description of each. - -Where can eval be used? ------------------------ - -* Template engine -* Scripting language -* ... - -Usage ------ - -Add dependency to Cargo.toml +Add `evalexpr` as dependency to your `Cargo.toml`: ```toml [dependencies] -evalexpr = "0.4" +evalexpr = "0.5" ``` -In your `main.rs` or `lib.rs`: +Add the `extern crate` definition to your `main.rs` or `lib.rs`: ```rust -extern crate evalexpr as eval; +extern crate evalexpr; ``` -Examples --------- - -You can do mathematical calculations with supported operators: +Then you can use `evalexpr` to evaluate expressions like this: ```rust -use eval::{eval, to_value}; +use evalexpr::*; -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))); +assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(6))); +assert_eq!(eval("1 - 2 * 3"), Ok(Value::from(-5))); +assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from(7.0))); +assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true))); ``` -You can eval with context: +And you can use variables and functions in expressions like this: ```rust -use eval::{Expr, to_value}; +use evalexpr::*; -assert_eq!(Expr::new("foo == bar") - .value("foo", true) - .value("bar", true) - .exec(), - Ok(to_value(true))); +let mut configuration = HashMapConfiguration::new(); +configuration.insert_variable("five", 5); +configuration.insert_variable("twelve", 12); +configuration.insert_function("f", Function::new(1 /* argument amount */, Box::new(|arguments| { + if let Value::Int(int) = arguments[0] { + Ok(Value::Int(int / 2)) + } else if let Value::Float(float) = arguments[0] { + Ok(Value::Float(float / 2.0)) + } else { + Err(Error::expected_number(arguments[0].clone())) + } +}))); + +assert_eq!(eval_with_configuration("five + 8 > f(twelve)", &configuration), Ok(Value::from(true))); ``` -You can access data like javascript by using `.` and `[]`. `[]` supports expression. +You can also precompile expressions like this: ```rust -use eval::{Expr, to_value}; -use std::collections::HashMap; +use evalexpr::*; -let mut object = HashMap::new(); -object.insert("foos", vec!["Hello", "world", "!"]); +let precompiled = build_operator_tree("a * b - c > 5").unwrap(); -assert_eq!(Expr::new("object.foos[1-1] == 'Hello'") - .value("object", object) - .exec(), - Ok(to_value(true))); +let mut configuration = HashMapConfiguration::new(); +configuration.insert_variable("a", 6); +configuration.insert_variable("b", 2); +configuration.insert_variable("c", 3); +assert_eq!(precompiled.eval(&configuration), Ok(Value::from(true))); + +configuration.insert_variable("c", 8); +assert_eq!(precompiled.eval(&configuration), Ok(Value::from(false))); ``` -You can eval with function: +## Features -```rust -use eval::{Expr, to_value}; +### Operators -assert_eq!(Expr::new("say_hello()") - .function("say_hello", |_| Ok(to_value("Hello world!"))) - .exec(), - Ok(to_value("Hello world!"))); -``` +Supported binary operators: -You can create an array with `array()`: +| Operator | Precedence | Description | | Operator | Precedence | Description | +|----------|------------|-------------|---|----------|------------|-------------| +| + | 95 | Sum | | < | 80 | Lower than | +| - | 95 | Difference | | \> | 80 | Greater than | +| * | 100 | Product | | <= | 80 | Lower than or equal | +| / | 100 | Division | | \>= | 80 | Greater than or equal | +| % | 100 | Modulo | | == | 80 | Equal | +| && | 75 | Logical and | | != | 80 | Not equal | +| || | 70 | Logical or | | | | -```rust -use eval::{eval, to_value}; +Supported unary operators: -assert_eq!(eval("array(1, 2, 3, 4, 5)"), Ok(to_value(vec![1, 2, 3, 4, 5]))); -``` +| Operator | Precedence | Description | +|----------|------------|-------------| +| - | 110 | Negation | +| ! | 110 | Logical not | -You can create an integer array with `n..m`: +### Values -```rust -use eval::{eval, to_value}; +Operators take values as arguments and produce values as results. +Values can be boolean, integer or floating point numbers. +Strings are supported as well, but there are no operations defined for them yet. +Values are denoted as displayed in the following table. -assert_eq!(eval("0..5"), Ok(to_value(vec![0, 1, 2, 3, 4]))); -``` +| Value type | Example | +|------------|---------| +| `Value::Boolean` | `true`, `false` | +| `Value::Int` | `3`, `-9`, `0`, `135412` | +| `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554` | -License -------- +Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. +Operators that take numbers as arguments can either take integers or floating point numbers. +If one of the arguments is a floating point number, all others are converted to floating point numbers as well, and the resulting value is a floating point number as well. +Otherwise, the result is an integer. -evalexpr is primarily distributed under the terms of the MIT license. -See [LICENSE](LICENSE) for details. +### Variables + +This crate allows to compile parameterizable formulas by using variables. +A variable is a literal in the formula, that does not contain whitespace or can be parsed as value. +The user needs to provide bindings to the variables for evaluation. +This is done with the `Configuration` trait. +Two structs implementing this trait are predefined. +There is `EmptyConfiguration`, that returns `None` for each request, and `HashMapConfiguration`, that stores mappings from literals to variables in a hash map. + +Variables do not have fixed types in the expression itself, but aer typed by the configuration. +The `Configuration` trait contains a function that takes a string literal and returns a `Value` enum. +The variant of this enum decides the type on evaluation. + +### Functions + +This crate also allows to define arbitrary functions to be used in parsed expressions. +A function is defined as a `Function` instance. +It contains two properties, the `argument_amount` and the `function`. +The `function` is a boxed `Fn(&[Value]) -> Result`. +The `argument_amount` is verified on execution by the crate and does not need to be verified by the `function`. +It determines the length of the slice that is passed to `function`. +See the examples section above for examples on how to construct a function instance. + +## License + +This crate is primarily distributed under the terms of the MIT license. +See [LICENSE](LICENSE) for details. diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs index 5ede6b2..b272de6 100644 --- a/src/configuration/mod.rs +++ b/src/configuration/mod.rs @@ -33,12 +33,12 @@ impl HashMapConfiguration { } } - pub fn insert_variable(&mut self, identifier: String, value: Value) { - self.variables.insert(identifier, value); + pub fn insert_variable, V: Into>(&mut self, identifier: S, value: V) { + self.variables.insert(identifier.into(), value.into()); } - pub fn insert_function(&mut self, identifier: String, function: Function) { - self.functions.insert(identifier, function); + pub fn insert_function>(&mut self, identifier: S, function: Function) { + self.functions.insert(identifier.into(), function); } } diff --git a/src/lib.rs b/src/lib.rs index dff4154..5364a6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,68 @@ //! +//! ## Quickstart +//! +//! Add `evalexpr` as dependency to your `Cargo.toml`: +//! +//! ```toml +//! [dependencies] +//! evalexpr = "0.5" +//! ``` +//! +//! Add the `extern crate` definition to your `main.rs` or `lib.rs`: +//! +//! ```rust +//! extern crate evalexpr; +//! ``` +//! +//! Then you can use `evalexpr` to evaluate expressions like this: +//! +//! ```rust +//! use evalexpr::*; +//! +//! assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(6))); +//! assert_eq!(eval("1 - 2 * 3"), Ok(Value::from(-5))); +//! assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from(7.0))); +//! assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true))); +//! ``` +//! +//! And you can use variables and functions in expressions like this: +//! +//! ```rust +//! use evalexpr::*; +//! +//! let mut configuration = HashMapConfiguration::new(); +//! configuration.insert_variable("five", 5); +//! configuration.insert_variable("twelve", 12); +//! configuration.insert_function("f", Function::new(1 /* argument amount */, Box::new(|arguments| { +//! if let Value::Int(int) = arguments[0] { +//! Ok(Value::Int(int / 2)) +//! } else if let Value::Float(float) = arguments[0] { +//! Ok(Value::Float(float / 2.0)) +//! } else { +//! Err(Error::expected_number(arguments[0].clone())) +//! } +//! }))); +//! +//! assert_eq!(eval_with_configuration("five + 8 > f(twelve)", &configuration), Ok(Value::from(true))); +//! ``` +//! +//! You can also precompile expressions like this: +//! +//! ```rust +//! use evalexpr::*; +//! +//! let precompiled = build_operator_tree("a * b - c > 5").unwrap(); +//! +//! let mut configuration = HashMapConfiguration::new(); +//! configuration.insert_variable("a", 6); +//! configuration.insert_variable("b", 2); +//! configuration.insert_variable("c", 3); +//! assert_eq!(precompiled.eval(&configuration), Ok(Value::from(true))); +//! +//! configuration.insert_variable("c", 8); +//! assert_eq!(precompiled.eval(&configuration), Ok(Value::from(false))); +//! ``` +//! //! ## Features //! //! ### Operators @@ -27,6 +91,13 @@ //! Operators take values as arguments and produce values as results. //! Values can be boolean, integer or floating point numbers. //! Strings are supported as well, but there are no operations defined for them yet. +//! Values are denoted as displayed in the following table. +//! +//! | Value type | Example | +//! |------------|---------| +//! | `Value::Boolean` | `true`, `false` | +//! | `Value::Int` | `3`, `-9`, `0`, `135412` | +//! | `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554` | //! //! Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. //! Operators that take numbers as arguments can either take integers or floating point numbers. @@ -35,115 +106,31 @@ //! //! ### Variables //! +//! This crate allows to compile parameterizable formulas by using variables. +//! A variable is a literal in the formula, that does not contain whitespace or can be parsed as value. +//! The user needs to provide bindings to the variables for evaluation. +//! This is done with the `Configuration` trait. +//! Two structs implementing this trait are predefined. +//! There is `EmptyConfiguration`, that returns `None` for each request, and `HashMapConfiguration`, that stores mappings from literals to variables in a hash map. //! +//! Variables do not have fixed types in the expression itself, but aer typed by the configuration. +//! The `Configuration` trait contains a function that takes a string literal and returns a `Value` enum. +//! The variant of this enum decides the type on evaluation. //! +//! ### Functions //! +//! This crate also allows to define arbitrary functions to be used in parsed expressions. +//! A function is defined as a `Function` instance. +//! It contains two properties, the `argument_amount` and the `function`. +//! The `function` is a boxed `Fn(&[Value]) -> Result`. +//! The `argument_amount` is verified on execution by the crate and does not need to be verified by the `function`. +//! It determines the length of the slice that is passed to `function`. +//! See the examples section above for examples on how to construct a function instance. //! -//! -//!Supported binary operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` `==` -//!`+` unary/binary `-` `*` `/` `%` `&&` `||` `n..m`. +//! ## License //! -//!Supported unary operators: `` -//! -//!Built-in functions: `min()` `max()` `len()` `is_empty()` `array()` `converge()`. -//!See the `builtin` module for a detailed description of each. -//! -//!Where can eval be used? -//!----------------------- -//! -//!* Template engine -//!* Scripting language -//!* ... -//! -//!Usage -//!----- -//! -//!Add dependency to Cargo.toml -//! -//!```toml -//![dependencies] -//!evalexpr = "0.4" -//!``` -//! -//!In your `main.rs` or `lib.rs`: -//! -//!```rust -//!extern crate evalexpr as eval; -//!``` -//! -//!Examples -//!-------- -//! -//!You can do mathematical calculations with supported operators: -//! -//!```rust -//!use eval::{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: -//! -//!```rust -//!use eval::{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. -//! -//!```rust -//!use eval::{Expr, to_value}; -//!use std::collections::HashMap; -//! -//!let mut object = HashMap::new(); -//!object.insert("foos", vec!["Hello", "world", "!"]); -//! -//!assert_eq!(Expr::new("object.foos[1-1] == 'Hello'") -//! .value("object", object) -//! .exec(), -//! Ok(to_value(true))); -//!``` -//! -//!You can eval with function: -//! -//!```rust -//!use eval::{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()`: -//! -//!```rust -//!use eval::{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`: -//! -//!```rust -//!use eval::{eval, to_value}; -//! -//!assert_eq!(eval("0..5"), Ok(to_value(vec![0, 1, 2, 3, 4]))); -//!``` -//! -//!License -//!------- -//! -//!evalexpr is primarily distributed under the terms of the MIT license. -//!See [LICENSE](LICENSE) for details. +//! This crate is primarily distributed under the terms of the MIT license. +//! See [LICENSE](LICENSE) for details. //! mod configuration; @@ -316,6 +303,8 @@ mod test { Box::new(|arguments| { if let Value::Int(int) = arguments[0] { Ok(Value::Int(int - 2)) + } else if let Value::Float(float) = arguments[0] { + Ok(Value::Float(float - 2.0)) } else { Err(Error::expected_number(arguments[0].clone())) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 170ab0e..9107c09 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -41,3 +41,39 @@ impl Value { } } } + +impl From for Value { + fn from(string: String) -> Self { + Value::String(string) + } +} + +impl From<&str> for Value { + fn from(string: &str) -> Self { + Value::String(string.to_string()) + } +} + +impl From for Value { + fn from(float: FloatType) -> Self { + Value::Float(float) + } +} + +impl From for Value { + fn from(int: IntType) -> Self { + Value::Int(int) + } +} + +impl From for Value { + fn from(boolean: bool) -> Self { + Value::Boolean(boolean) + } +} + +impl From for Result { + fn from(value: Value) -> Self { + Ok(value) + } +} \ No newline at end of file