parent
13420ed745
commit
83269068a2
17
README.md
17
README.md
@ -120,7 +120,7 @@ Supported binary operators:
|
||||
| % | 100 | Modulo | | == | 80 | Equal |
|
||||
| ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
||||
| && | 75 | Logical and | | , | 40 | Aggregation |
|
||||
| || | 70 | Logical or | | | | |
|
||||
| || | 70 | Logical or | | = | 50 | Assignment |
|
||||
|
||||
Supported unary operators:
|
||||
|
||||
@ -251,6 +251,21 @@ Functions have a precedence of 190.
|
||||
| `true` | no | Expression is interpreted as `Value::Bool` |
|
||||
| `.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)
|
||||
|
||||
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.
|
||||
|
||||
use token::PartialToken;
|
||||
use value::{value_type::ValueType, TupleType};
|
||||
use value::{TupleType, value_type::ValueType};
|
||||
|
||||
use crate::value::Value;
|
||||
|
||||
@ -160,7 +160,7 @@ pub enum EvalexprError {
|
||||
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,
|
||||
|
||||
/// 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.
|
||||
/// Numeric types are `Value::Int` and `Value::Float`.
|
||||
/// 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> {
|
||||
match actual {
|
||||
Value::Boolean(boolean) => Ok(*boolean),
|
||||
|
17
src/lib.rs
17
src/lib.rs
@ -107,7 +107,7 @@
|
||||
//! | % | 100 | Modulo | | == | 80 | Equal |
|
||||
//! | ^ | 120 | Exponentiation | | != | 80 | Not equal |
|
||||
//! | && | 75 | Logical and | | , | 40 | Aggregation |
|
||||
//! | || | 70 | Logical or | | | | |
|
||||
//! | || | 70 | Logical or | | = | 50 | Assignment |
|
||||
//!
|
||||
//! Supported unary operators:
|
||||
//!
|
||||
@ -238,6 +238,21 @@
|
||||
//! | `true` | no | Expression is interpreted as `Value::Bool` |
|
||||
//! | `.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)
|
||||
//!
|
||||
//! 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 operator::*;
|
||||
|
||||
impl Display for RootNode {
|
||||
fn fmt(&self, _f: &mut Formatter) -> Result<(), Error> {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "{}", self.value)
|
||||
|
@ -77,6 +77,9 @@ pub struct Not;
|
||||
#[derive(Debug)]
|
||||
pub struct Tuple;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Assign;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Const {
|
||||
value: Value,
|
||||
@ -644,6 +647,7 @@ impl Operator for Tuple {
|
||||
}
|
||||
|
||||
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
|
||||
expect_operator_argument_amount(arguments.len(), 2)?;
|
||||
if let Value::Tuple(tuple) = &arguments[0] {
|
||||
let mut tuple = tuple.clone();
|
||||
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 {
|
||||
fn precedence(&self) -> i32 {
|
||||
200
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
use token::{PartialToken, Token};
|
||||
|
||||
impl fmt::Display for Token {
|
||||
@ -30,6 +31,9 @@ impl fmt::Display for Token {
|
||||
// Aggregation
|
||||
Comma => write!(f, ","),
|
||||
|
||||
// Assignment
|
||||
Assign => write!(f, "="),
|
||||
|
||||
// Values => write!(f, ""), Variables and Functions
|
||||
Identifier(identifier) => identifier.fmt(f),
|
||||
Float(float) => float.fmt(f),
|
||||
|
@ -31,6 +31,9 @@ pub enum Token {
|
||||
// Aggregation
|
||||
Comma,
|
||||
|
||||
// Assignment
|
||||
Assign,
|
||||
|
||||
// Values, Variables and Functions
|
||||
Identifier(String),
|
||||
Float(FloatType),
|
||||
@ -109,6 +112,8 @@ impl Token {
|
||||
|
||||
Token::Comma => false,
|
||||
|
||||
Token::Assign => false,
|
||||
|
||||
Token::Identifier(_) => true,
|
||||
Token::Float(_) => true,
|
||||
Token::Int(_) => true,
|
||||
@ -141,6 +146,8 @@ impl Token {
|
||||
|
||||
Token::Comma => false,
|
||||
|
||||
Token::Assign => false,
|
||||
|
||||
Token::Identifier(_) => true,
|
||||
Token::Float(_) => 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.
|
||||
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();
|
||||
while tokens.len() > 0 {
|
||||
let first = tokens[0].clone();
|
||||
@ -204,7 +211,10 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<Token>> {
|
||||
},
|
||||
PartialToken::Eq => match second {
|
||||
Some(PartialToken::Eq) => Some(Token::Eq),
|
||||
_ => return Err(EvalexprError::unmatched_partial_token(first, second)),
|
||||
_ => {
|
||||
cutoff = 1;
|
||||
Some(Token::Assign)
|
||||
},
|
||||
},
|
||||
PartialToken::ExclamationMark => match second {
|
||||
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>> {
|
||||
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 EmptyType;
|
||||
use FloatType;
|
||||
use IntType;
|
||||
use token::Token;
|
||||
use value::{EMPTY_VALUE, TupleType};
|
||||
|
||||
use crate::{
|
||||
context::Context,
|
||||
@ -68,7 +68,7 @@ impl Node {
|
||||
for child in self.children() {
|
||||
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.
|
||||
@ -384,10 +384,14 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
|
||||
|
||||
Token::Comma => Some(Node::new(Tuple)),
|
||||
|
||||
Token::Assign => Some(Node::new(Assign)),
|
||||
|
||||
Token::Identifier(identifier) => {
|
||||
let mut result = Some(Node::new(VariableIdentifier::new(identifier.clone())));
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
extern crate evalexpr;
|
||||
|
||||
use evalexpr::{error::*, *};
|
||||
use evalexpr::{*, error::*};
|
||||
|
||||
#[test]
|
||||
fn test_unary_examples() {
|
||||
@ -490,6 +490,40 @@ fn test_whitespace() {
|
||||
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")]
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
|
Loading…
Reference in New Issue
Block a user