parent
13420ed745
commit
83269068a2
17
README.md
17
README.md
@ -120,7 +120,7 @@ Supported binary operators:
|
|||||||
| % | 100 | Modulo | | == | 80 | Equal |
|
| % | 100 | Modulo | | == | 80 | Equal |
|
||||||
| ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
| ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
||||||
| && | 75 | Logical and | | , | 40 | Aggregation |
|
| && | 75 | Logical and | | , | 40 | Aggregation |
|
||||||
| || | 70 | Logical or | | | | |
|
| || | 70 | Logical or | | = | 50 | Assignment |
|
||||||
|
|
||||||
Supported unary operators:
|
Supported unary operators:
|
||||||
|
|
||||||
@ -251,6 +251,21 @@ Functions have a precedence of 190.
|
|||||||
| `true` | no | Expression is interpreted as `Value::Bool` |
|
| `true` | no | Expression is interpreted as `Value::Bool` |
|
||||||
| `.34` | no | Expression is interpreted as `Value::Float` |
|
| `.34` | no | Expression is interpreted as `Value::Float` |
|
||||||
|
|
||||||
|
### Assignments
|
||||||
|
|
||||||
|
This crate features the assignment operator, that allows expressions to store their result in a variable in the expression context.
|
||||||
|
If an expression uses the assignment operator, it must be evaluated with a mutable context.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use evalexpr::*;
|
||||||
|
|
||||||
|
let mut context = HashMapContext::new();
|
||||||
|
assert_eq!(eval_with_context("a = 5", &context), Err(EvalexprError::ContextNotManipulable));
|
||||||
|
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
|
||||||
|
assert_eq!(eval_int_with_context("a", &context), Ok(5));
|
||||||
|
assert_eq!(context.get_value("a"), Some(5.into()).as_ref());
|
||||||
|
```
|
||||||
|
|
||||||
### [Serde](https://serde.rs)
|
### [Serde](https://serde.rs)
|
||||||
|
|
||||||
To use this crate with serde, the serde feature flag has to be set.
|
To use this crate with serde, the serde feature flag has to be set.
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
//! They are meant as shortcuts to not write the same error checking code everywhere.
|
//! They are meant as shortcuts to not write the same error checking code everywhere.
|
||||||
|
|
||||||
use token::PartialToken;
|
use token::PartialToken;
|
||||||
use value::{value_type::ValueType, TupleType};
|
use value::{TupleType, value_type::ValueType};
|
||||||
|
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ pub enum EvalexprError {
|
|||||||
divisor: Value,
|
divisor: Value,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A `set_`-function was called on a `Context` that does not allow modifications.
|
/// A modification was attempted on a `Context` that does not allow modifications.
|
||||||
ContextNotManipulable,
|
ContextNotManipulable,
|
||||||
|
|
||||||
/// A custom error explained by its message.
|
/// A custom error explained by its message.
|
||||||
@ -294,6 +294,14 @@ pub(crate) fn expect_function_argument_amount(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok(&str)` if the given value is a `Value::String`, or `Err(Error::ExpectedString)` otherwise.
|
||||||
|
pub fn expect_string(actual: &Value) -> EvalexprResult<&str> {
|
||||||
|
match actual {
|
||||||
|
Value::String(string) => Ok(string),
|
||||||
|
_ => Err(EvalexprError::expected_string(actual.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `Ok(())` if the given value is numeric.
|
/// Returns `Ok(())` if the given value is numeric.
|
||||||
/// Numeric types are `Value::Int` and `Value::Float`.
|
/// Numeric types are `Value::Int` and `Value::Float`.
|
||||||
/// Otherwise, `Err(Error::ExpectedNumber)` is returned.
|
/// Otherwise, `Err(Error::ExpectedNumber)` is returned.
|
||||||
@ -304,7 +312,7 @@ pub fn expect_number(actual: &Value) -> EvalexprResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Ok(())` if the given value is a `Value::Boolean`, or `Err(Error::ExpectedBoolean)` otherwise.
|
/// Returns `Ok(bool)` if the given value is a `Value::Boolean`, or `Err(Error::ExpectedBoolean)` otherwise.
|
||||||
pub fn expect_boolean(actual: &Value) -> EvalexprResult<bool> {
|
pub fn expect_boolean(actual: &Value) -> EvalexprResult<bool> {
|
||||||
match actual {
|
match actual {
|
||||||
Value::Boolean(boolean) => Ok(*boolean),
|
Value::Boolean(boolean) => Ok(*boolean),
|
||||||
|
17
src/lib.rs
17
src/lib.rs
@ -107,7 +107,7 @@
|
|||||||
//! | % | 100 | Modulo | | == | 80 | Equal |
|
//! | % | 100 | Modulo | | == | 80 | Equal |
|
||||||
//! | ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
//! | ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
||||||
//! | && | 75 | Logical and | | , | 40 | Aggregation |
|
//! | && | 75 | Logical and | | , | 40 | Aggregation |
|
||||||
//! | || | 70 | Logical or | | | | |
|
//! | || | 70 | Logical or | | = | 50 | Assignment |
|
||||||
//!
|
//!
|
||||||
//! Supported unary operators:
|
//! Supported unary operators:
|
||||||
//!
|
//!
|
||||||
@ -238,6 +238,21 @@
|
|||||||
//! | `true` | no | Expression is interpreted as `Value::Bool` |
|
//! | `true` | no | Expression is interpreted as `Value::Bool` |
|
||||||
//! | `.34` | no | Expression is interpreted as `Value::Float` |
|
//! | `.34` | no | Expression is interpreted as `Value::Float` |
|
||||||
//!
|
//!
|
||||||
|
//! ### Assignments
|
||||||
|
//!
|
||||||
|
//! This crate features the assignment operator, that allows expressions to store their result in a variable in the expression context.
|
||||||
|
//! If an expression uses the assignment operator, it must be evaluated with a mutable context.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use evalexpr::*;
|
||||||
|
//!
|
||||||
|
//! let mut context = HashMapContext::new();
|
||||||
|
//! assert_eq!(eval_with_context("a = 5", &context), Err(EvalexprError::ContextNotManipulable));
|
||||||
|
//! assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
|
||||||
|
//! assert_eq!(eval_int_with_context("a", &context), Ok(5));
|
||||||
|
//! assert_eq!(context.get_value("a"), Some(5.into()).as_ref());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ### [Serde](https://serde.rs)
|
//! ### [Serde](https://serde.rs)
|
||||||
//!
|
//!
|
||||||
//! To use this crate with serde, the serde feature flag has to be set.
|
//! To use this crate with serde, the serde feature flag has to be set.
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use operator::*;
|
|
||||||
use std::fmt::{Display, Error, Formatter};
|
use std::fmt::{Display, Error, Formatter};
|
||||||
|
|
||||||
|
use operator::*;
|
||||||
|
|
||||||
impl Display for RootNode {
|
impl Display for RootNode {
|
||||||
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
|
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -109,6 +110,12 @@ impl Display for Tuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Assign {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
|
write!(f, " = ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Const {
|
impl Display for Const {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||||
write!(f, "{}", self.value)
|
write!(f, "{}", self.value)
|
||||||
|
@ -77,6 +77,9 @@ pub struct Not;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tuple;
|
pub struct Tuple;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Assign;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Const {
|
pub struct Const {
|
||||||
value: Value,
|
value: Value,
|
||||||
@ -644,6 +647,7 @@ impl Operator for Tuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
|
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
|
||||||
|
expect_operator_argument_amount(arguments.len(), 2)?;
|
||||||
if let Value::Tuple(tuple) = &arguments[0] {
|
if let Value::Tuple(tuple) = &arguments[0] {
|
||||||
let mut tuple = tuple.clone();
|
let mut tuple = tuple.clone();
|
||||||
if let Value::Tuple(tuple2) = &arguments[1] {
|
if let Value::Tuple(tuple2) = &arguments[1] {
|
||||||
@ -667,6 +671,32 @@ impl Operator for Tuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Operator for Assign {
|
||||||
|
fn precedence(&self) -> i32 {
|
||||||
|
50
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_left_to_right(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_argument_amount(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval(&self, _arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
|
||||||
|
Err(EvalexprError::ContextNotManipulable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_mut(&self, arguments: &[Value], context: &mut Context) -> EvalexprResult<Value> {
|
||||||
|
expect_operator_argument_amount(arguments.len(), 2)?;
|
||||||
|
let target = expect_string(&arguments[0])?;
|
||||||
|
context.set_value(target.into(), arguments[1].clone())?;
|
||||||
|
|
||||||
|
Ok(Value::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Operator for Const {
|
impl Operator for Const {
|
||||||
fn precedence(&self) -> i32 {
|
fn precedence(&self) -> i32 {
|
||||||
200
|
200
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use token::{PartialToken, Token};
|
use token::{PartialToken, Token};
|
||||||
|
|
||||||
impl fmt::Display for Token {
|
impl fmt::Display for Token {
|
||||||
@ -30,6 +31,9 @@ impl fmt::Display for Token {
|
|||||||
// Aggregation
|
// Aggregation
|
||||||
Comma => write!(f, ","),
|
Comma => write!(f, ","),
|
||||||
|
|
||||||
|
// Assignment
|
||||||
|
Assign => write!(f, "="),
|
||||||
|
|
||||||
// Values => write!(f, ""), Variables and Functions
|
// Values => write!(f, ""), Variables and Functions
|
||||||
Identifier(identifier) => identifier.fmt(f),
|
Identifier(identifier) => identifier.fmt(f),
|
||||||
Float(float) => float.fmt(f),
|
Float(float) => float.fmt(f),
|
||||||
|
@ -31,6 +31,9 @@ pub enum Token {
|
|||||||
// Aggregation
|
// Aggregation
|
||||||
Comma,
|
Comma,
|
||||||
|
|
||||||
|
// Assignment
|
||||||
|
Assign,
|
||||||
|
|
||||||
// Values, Variables and Functions
|
// Values, Variables and Functions
|
||||||
Identifier(String),
|
Identifier(String),
|
||||||
Float(FloatType),
|
Float(FloatType),
|
||||||
@ -109,6 +112,8 @@ impl Token {
|
|||||||
|
|
||||||
Token::Comma => false,
|
Token::Comma => false,
|
||||||
|
|
||||||
|
Token::Assign => false,
|
||||||
|
|
||||||
Token::Identifier(_) => true,
|
Token::Identifier(_) => true,
|
||||||
Token::Float(_) => true,
|
Token::Float(_) => true,
|
||||||
Token::Int(_) => true,
|
Token::Int(_) => true,
|
||||||
@ -141,6 +146,8 @@ impl Token {
|
|||||||
|
|
||||||
Token::Comma => false,
|
Token::Comma => false,
|
||||||
|
|
||||||
|
Token::Assign => false,
|
||||||
|
|
||||||
Token::Identifier(_) => true,
|
Token::Identifier(_) => true,
|
||||||
Token::Float(_) => true,
|
Token::Float(_) => true,
|
||||||
Token::Int(_) => true,
|
Token::Int(_) => true,
|
||||||
@ -173,7 +180,7 @@ fn str_to_tokens(string: &str) -> Vec<PartialToken> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves all partial tokens by converting them to complex tokens.
|
/// Resolves all partial tokens by converting them to complex tokens.
|
||||||
fn resolve_literals(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<Token>> {
|
fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<Token>> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
while tokens.len() > 0 {
|
while tokens.len() > 0 {
|
||||||
let first = tokens[0].clone();
|
let first = tokens[0].clone();
|
||||||
@ -204,7 +211,10 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<Token>> {
|
|||||||
},
|
},
|
||||||
PartialToken::Eq => match second {
|
PartialToken::Eq => match second {
|
||||||
Some(PartialToken::Eq) => Some(Token::Eq),
|
Some(PartialToken::Eq) => Some(Token::Eq),
|
||||||
_ => return Err(EvalexprError::unmatched_partial_token(first, second)),
|
_ => {
|
||||||
|
cutoff = 1;
|
||||||
|
Some(Token::Assign)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
PartialToken::ExclamationMark => match second {
|
PartialToken::ExclamationMark => match second {
|
||||||
Some(PartialToken::Eq) => Some(Token::Eq),
|
Some(PartialToken::Eq) => Some(Token::Eq),
|
||||||
@ -245,5 +255,5 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<Token>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tokenize(string: &str) -> EvalexprResult<Vec<Token>> {
|
pub(crate) fn tokenize(string: &str) -> EvalexprResult<Vec<Token>> {
|
||||||
resolve_literals(&str_to_tokens(string))
|
partial_tokens_to_tokens(&str_to_tokens(string))
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use token::Token;
|
|
||||||
use value::{TupleType, EMPTY_VALUE};
|
|
||||||
use EmptyContext;
|
use EmptyContext;
|
||||||
use EmptyType;
|
use EmptyType;
|
||||||
use FloatType;
|
use FloatType;
|
||||||
use IntType;
|
use IntType;
|
||||||
|
use token::Token;
|
||||||
|
use value::{EMPTY_VALUE, TupleType};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::Context,
|
context::Context,
|
||||||
@ -68,7 +68,7 @@ impl Node {
|
|||||||
for child in self.children() {
|
for child in self.children() {
|
||||||
arguments.push(child.eval_with_context_mut(context)?);
|
arguments.push(child.eval_with_context_mut(context)?);
|
||||||
}
|
}
|
||||||
self.operator().eval(&arguments, context)
|
self.operator().eval_mut(&arguments, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluates the operator tree rooted at this node with an empty context.
|
/// Evaluates the operator tree rooted at this node with an empty context.
|
||||||
@ -384,10 +384,14 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
|
|||||||
|
|
||||||
Token::Comma => Some(Node::new(Tuple)),
|
Token::Comma => Some(Node::new(Tuple)),
|
||||||
|
|
||||||
|
Token::Assign => Some(Node::new(Assign)),
|
||||||
|
|
||||||
Token::Identifier(identifier) => {
|
Token::Identifier(identifier) => {
|
||||||
let mut result = Some(Node::new(VariableIdentifier::new(identifier.clone())));
|
let mut result = Some(Node::new(VariableIdentifier::new(identifier.clone())));
|
||||||
if let Some(next) = next {
|
if let Some(next) = next {
|
||||||
if next.is_leftsided_value() {
|
if next == &Token::Assign {
|
||||||
|
result = Some(Node::new(Const::new(identifier.clone().into())));
|
||||||
|
} else if next.is_leftsided_value() {
|
||||||
result = Some(Node::new(FunctionIdentifier::new(identifier)));
|
result = Some(Node::new(FunctionIdentifier::new(identifier)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
extern crate evalexpr;
|
extern crate evalexpr;
|
||||||
|
|
||||||
use evalexpr::{error::*, *};
|
use evalexpr::{*, error::*};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unary_examples() {
|
fn test_unary_examples() {
|
||||||
@ -490,6 +490,40 @@ fn test_whitespace() {
|
|||||||
assert!(eval_boolean("2 < = 3").is_err());
|
assert!(eval_boolean("2 < = 3").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assignment() {
|
||||||
|
let mut context = HashMapContext::new();
|
||||||
|
assert_eq!(
|
||||||
|
eval_empty_with_context_mut("int = 3", &mut context),
|
||||||
|
Ok(EMPTY_VALUE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_empty_with_context_mut("float = 2.0", &mut context),
|
||||||
|
Ok(EMPTY_VALUE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_empty_with_context_mut("tuple = (1,1)", &mut context),
|
||||||
|
Ok(EMPTY_VALUE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_empty_with_context_mut("empty = ()", &mut context),
|
||||||
|
Ok(EMPTY_VALUE)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_empty_with_context_mut("boolean = false", &mut context),
|
||||||
|
Ok(EMPTY_VALUE)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(eval_int_with_context("int", &context), Ok(3));
|
||||||
|
assert_eq!(eval_float_with_context("float", &context), Ok(2.0));
|
||||||
|
assert_eq!(
|
||||||
|
eval_tuple_with_context("tuple", &context),
|
||||||
|
Ok(vec![1.into(), 1.into()])
|
||||||
|
);
|
||||||
|
assert_eq!(eval_empty_with_context("empty", &context), Ok(EMPTY_VALUE));
|
||||||
|
assert_eq!(eval_boolean_with_context("boolean", &context), Ok(false));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde() {
|
fn test_serde() {
|
||||||
|
Loading…
Reference in New Issue
Block a user