Add the assignment operator

Relates to #25
This commit is contained in:
Sebastian Schmidt 2019-03-28 11:12:47 +01:00
parent 13420ed745
commit 83269068a2
9 changed files with 141 additions and 14 deletions

View File

@ -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.

View File

@ -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),

View File

@ -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 |
//! | &#124;&#124; | 70 | Logical or | | | | | //! | &#124;&#124; | 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.

View File

@ -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)

View File

@ -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

View File

@ -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),

View File

@ -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))
} }

View File

@ -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)));
} }
} }

View File

@ -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() {