diff --git a/README.md b/README.md index bffc67a..34aca65 100644 --- a/README.md +++ b/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. diff --git a/src/error/mod.rs b/src/error/mod.rs index 675ce75..20ea316 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -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 { match actual { Value::Boolean(boolean) => Ok(*boolean), diff --git a/src/lib.rs b/src/lib.rs index 43facf2..9db038b 100644 --- a/src/lib.rs +++ b/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. diff --git a/src/operator/display.rs b/src/operator/display.rs index 7c3f613..7de8d29 100644 --- a/src/operator/display.rs +++ b/src/operator/display.rs @@ -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) diff --git a/src/operator/mod.rs b/src/operator/mod.rs index b6d9c07..330c728 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -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 { + 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 { + Err(EvalexprError::ContextNotManipulable) + } + + fn eval_mut(&self, arguments: &[Value], context: &mut Context) -> EvalexprResult { + 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 diff --git a/src/token/display.rs b/src/token/display.rs index 345fe8e..224c6ea 100644 --- a/src/token/display.rs +++ b/src/token/display.rs @@ -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), diff --git a/src/token/mod.rs b/src/token/mod.rs index 379c229..7a39665 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -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 { } /// Resolves all partial tokens by converting them to complex tokens. -fn resolve_literals(mut tokens: &[PartialToken]) -> EvalexprResult> { +fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult> { 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> { }, 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> { } pub(crate) fn tokenize(string: &str) -> EvalexprResult> { - resolve_literals(&str_to_tokens(string)) + partial_tokens_to_tokens(&str_to_tokens(string)) } diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 27e780a..d355afe 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -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) -> EvalexprResult 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))); } } diff --git a/tests/integration.rs b/tests/integration.rs index 3a27ab9..f0e2d94 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -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() {