From e266f4fc0d27c4b574912cb1a768a17a2b65322c Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 28 Mar 2019 09:14:37 +0100 Subject: [PATCH] Merge `ContextMut` with `Context` and add `eval__with_context_mut` methods Trait objects of `ContextMut` cannot be converted into `Context`, even though `ContextMut` requires `Context`. Relates to #30 --- CHANGELOG.md | 11 ++++++ README.md | 18 +++++----- src/context/mod.rs | 65 +++++++-------------------------- src/interface/mod.rs | 20 +++++------ src/lib.rs | 22 ++++++------ src/operator/mod.rs | 5 ++- src/tree/mod.rs | 85 ++++++++++++++++++++++++++++++++++++++++++-- tests/integration.rs | 28 ++++++++------- 8 files changed, 154 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b82f1db..3c271e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,23 @@ ## Unreleased +### Notes + +The 3.0.0 update includes further breaking changes that are necessary to allow assignments and chaining of expressions. +Rust does not allow trait objects to be converted into one another, even if one requires the other, so `ContextMut` had to be merged into `Context` for a more generic implementation of the (internal) `Operator` trait. + ### Added + * Methods `Node::eval__with_context_mut` + ### Removed + * Generic arguments from `Context` traits are now static to allow using trait objects of `Context` + ### Changed + * Merge `ContextMut` trait into `Context` trait + ### Fixed ### Deprecated diff --git a/README.md b/README.md index b582f3f..7df46b3 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ use evalexpr::*; use evalexpr::error::expect_number; let mut context = HashMapContext::new(); -context.set_value("five", 5).unwrap(); // Do proper error handling here -context.set_value("twelve", 12).unwrap(); // Do proper error handling here -context.set_function("f", Function::new(Some(1) /* argument amount */, Box::new(|arguments| { +context.set_value("five".into(), 5.into()).unwrap(); // Do proper error handling here +context.set_value("twelve".into(), 12.into()).unwrap(); // Do proper error handling here +context.set_function("f".into(), Function::new(Some(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] { @@ -60,7 +60,7 @@ context.set_function("f", Function::new(Some(1) /* argument amount */, Box::new( Err(EvalexprError::expected_number(arguments[0].clone())) } }))).unwrap(); // Do proper error handling here -context.set_function("avg", Function::new(Some(2) /* argument amount */, Box::new(|arguments| { +context.set_function("avg".into(), Function::new(Some(2) /* argument amount */, Box::new(|arguments| { expect_number(&arguments[0])?; expect_number(&arguments[1])?; @@ -87,12 +87,12 @@ use evalexpr::*; let precompiled = build_operator_tree("a * b - c > 5").unwrap(); // Do proper error handling here let mut context = HashMapContext::new(); -context.set_value("a", 6).unwrap(); // Do proper error handling here -context.set_value("b", 2).unwrap(); // Do proper error handling here -context.set_value("c", 3).unwrap(); // Do proper error handling here +context.set_value("a".into(), 6.into()).unwrap(); // Do proper error handling here +context.set_value("b".into(), 2.into()).unwrap(); // Do proper error handling here +context.set_value("c".into(), 3.into()).unwrap(); // Do proper error handling here assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true))); -context.set_value("c", 8).unwrap(); // Do proper error handling here +context.set_value("c".into(), 8.into()).unwrap(); // Do proper error handling here assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(false))); // `Node::eval_with_context` returns a variant of the `Value` enum, // while `Node::eval_[type]_with_context` returns the respective type directly. @@ -263,7 +263,7 @@ extern crate ron; use evalexpr::*; let mut context = HashMapContext::new(); -context.set_value("five", 5).unwrap(); // Do proper error handling here +context.set_value("five".into(), 5.into()).unwrap(); // Do proper error handling here // In ron format, strings are surrounded by " let serialized_free = "\"five * five\""; diff --git a/src/context/mod.rs b/src/context/mod.rs index 71cf64e..860c51c 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -1,43 +1,34 @@ use std::collections::HashMap; -use function::Function; -use value::value_type::ValueType; use EvalexprError; use EvalexprResult; +use function::Function; +use value::value_type::ValueType; use crate::value::Value; -/// A context for an expression tree. +/// A mutable context for an expression tree. /// /// A context defines methods to retrieve values and functions for literals in an expression tree. +/// In addition, it also allows the manipulation of values and functions. /// This crate implements two basic variants, the `EmptyContext`, that returns `None` for each identifier and cannot be manipulated, and the `HashMapContext`, that stores its mappings in hash maps. +/// The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier. pub trait Context { /// Returns the value that is linked to the given identifier. fn get_value(&self, identifier: &str) -> Option<&Value>; /// Returns the function that is linked to the given identifier. fn get_function(&self, identifier: &str) -> Option<&Function>; -} -/// A mutable context for an expression tree. -/// -/// In addition to all functionality of a `Context`, a mutable context also allows the manipulation of values and functions. -/// This crate implements two basic variants, the `EmptyContext`, that returns an error for each manipulation, and the `HashMapContext`, that stores its mappings in hash maps. -/// The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier. -pub trait ContextMut: Context { /// Links the given value to the given identifier. - fn set_value, V: Into>( - &mut self, - identifier: S, - value: V, - ) -> EvalexprResult<()>; + fn set_value(&mut self, _identifier: String, _value: Value) -> EvalexprResult<()> { + Err(EvalexprError::ContextNotManipulable) + } /// Links the given function to the given identifier. - fn set_function>( - &mut self, - identifier: S, - function: Function, - ) -> EvalexprResult<()>; + fn set_function(&mut self, _identifier: String, _function: Function) -> EvalexprResult<()> { + Err(EvalexprError::ContextNotManipulable) + } } /// A context that returns `None` for each identifier. @@ -53,24 +44,6 @@ impl Context for EmptyContext { } } -impl ContextMut for EmptyContext { - fn set_value, V: Into>( - &mut self, - _identifier: S, - _value: V, - ) -> EvalexprResult<()> { - Err(EvalexprError::ContextNotManipulable) - } - - fn set_function>( - &mut self, - _identifier: S, - _function: Function, - ) -> EvalexprResult<()> { - Err(EvalexprError::ContextNotManipulable) - } -} - /// A context that stores its mappings in hash maps. /// /// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.* @@ -99,16 +72,8 @@ impl Context for HashMapContext { fn get_function(&self, identifier: &str) -> Option<&Function> { self.functions.get(identifier) } -} -impl ContextMut for HashMapContext { - fn set_value, V: Into>( - &mut self, - identifier: S, - value: V, - ) -> EvalexprResult<()> { - let identifier = identifier.into(); - let value = value.into(); + fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> { if let Some(existing_value) = self.variables.get_mut(&identifier) { if ValueType::from(&existing_value) == ValueType::from(&value) { *existing_value = value; @@ -123,11 +88,7 @@ impl ContextMut for HashMapContext { Ok(()) } - fn set_function>( - &mut self, - identifier: S, - function: Function, - ) -> EvalexprResult<()> { + fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> { self.functions.insert(identifier.into(), function); Ok(()) } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 0410223..c5acef2 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -1,6 +1,3 @@ -use token; -use tree; -use value::TupleType; use Context; use EmptyContext; use EvalexprError; @@ -8,7 +5,10 @@ use EvalexprResult; use FloatType; use IntType; use Node; +use token; +use tree; use Value; +use value::TupleType; /// Evaluate the given expression string. /// @@ -33,9 +33,9 @@ pub fn eval(string: &str) -> EvalexprResult { /// use evalexpr::*; /// /// let mut context = HashMapContext::new(); -/// context.set_value("one", 1).unwrap(); // Do proper error handling here -/// context.set_value("two", 2).unwrap(); // Do proper error handling here -/// context.set_value("three", 3).unwrap(); // Do proper error handling here +/// context.set_value("one".into(), 1.into()).unwrap(); // Do proper error handling here +/// context.set_value("two".into(), 2.into()).unwrap(); // Do proper error handling here +/// context.set_value("three".into(), 3.into()).unwrap(); // Do proper error handling here /// assert_eq!(eval_with_context("one + two + three", &context), Ok(Value::from(6))); /// ``` /// @@ -57,13 +57,13 @@ pub fn eval_with_context(string: &str, context: &Context) -> EvalexprResult 5").unwrap(); // Do proper error handling here //! //! let mut context = HashMapContext::new(); -//! context.set_value("a", 6).unwrap(); // Do proper error handling here -//! context.set_value("b", 2).unwrap(); // Do proper error handling here -//! context.set_value("c", 3).unwrap(); // Do proper error handling here +//! context.set_value("a".into(), 6.into()).unwrap(); // Do proper error handling here +//! context.set_value("b".into(), 2.into()).unwrap(); // Do proper error handling here +//! context.set_value("c".into(), 3.into()).unwrap(); // Do proper error handling here //! assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true))); //! -//! context.set_value("c", 8).unwrap(); // Do proper error handling here +//! context.set_value("c".into(), 8.into()).unwrap(); // Do proper error handling here //! assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(false))); //! // `Node::eval_with_context` returns a variant of the `Value` enum, //! // while `Node::eval_[type]_with_context` returns the respective type directly. @@ -250,7 +250,7 @@ //! use evalexpr::*; //! //! let mut context = HashMapContext::new(); -//! context.set_value("five", 5).unwrap(); // Do proper error handling here +//! context.set_value("five".into(), 5.into()).unwrap(); // Do proper error handling here //! //! // In ron format, strings are surrounded by " //! let serialized_free = "\"five * five\""; @@ -277,12 +277,12 @@ extern crate ron; #[cfg(feature = "serde")] extern crate serde; -pub use context::{Context, ContextMut, EmptyContext, HashMapContext}; +pub use context::{Context, EmptyContext, HashMapContext}; pub use error::{EvalexprError, EvalexprResult}; pub use function::Function; pub use interface::*; pub use tree::Node; -pub use value::{value_type::ValueType, FloatType, IntType, TupleType, Value}; +pub use value::{FloatType, IntType, TupleType, Value, value_type::ValueType}; mod context; pub mod error; diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 1f71c08..171cf84 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -1,6 +1,5 @@ use std::fmt::{Debug, Display}; -use ::{ContextMut, ValueType}; use function::builtin::builtin_function; use crate::{context::Context, error::*, value::Value}; @@ -30,10 +29,10 @@ pub trait Operator: Debug + Display { fn argument_amount(&self) -> usize; /// Evaluates the operator with the given arguments and context. - fn eval(&self, arguments: &[Value], context: &Context) -> EvalexprResult; + fn eval(&self, arguments: &[Value], context: &dyn Context) -> EvalexprResult; /// Evaluates the operator with the given arguments and mutable context. - fn eval_mut(&self, arguments: &[Value], context: &mut ContextMut) -> EvalexprResult { + fn eval_mut(&self, arguments: &[Value], context: &mut dyn Context) -> EvalexprResult { self.eval(arguments, context) } } diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 196bf4a..ef15ce3 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,8 +1,8 @@ -use token::Token; -use value::TupleType; use EmptyContext; use FloatType; use IntType; +use token::Token; +use value::TupleType; use crate::{ context::Context, @@ -25,7 +25,7 @@ mod display; /// use evalexpr::*; /// /// let mut context = HashMapContext::new(); -/// context.set_value("alpha", 2).unwrap(); // Do proper error handling here +/// context.set_value("alpha".into(), 2.into()).unwrap(); // Do proper error handling here /// let node = build_operator_tree("1 + alpha").unwrap(); // Do proper error handling here /// assert_eq!(node.eval_with_context(&context), Ok(Value::from(3))); /// ``` @@ -59,6 +59,17 @@ impl Node { self.operator().eval(&arguments, context) } + /// Evaluates the operator tree rooted at this node with the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + let mut arguments = Vec::new(); + for child in self.children() { + arguments.push(child.eval_with_context_mut(context)?); + } + self.operator().eval(&arguments, context) + } + /// Evaluates the operator tree rooted at this node with an empty context. /// /// Fails, if one of the operators in the expression tree fails. @@ -134,6 +145,74 @@ impl Node { } } + /// Evaluates the operator tree rooted at this node into a string with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_string_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::String(string)) => Ok(string), + Ok(value) => Err(EvalexprError::expected_string(value)), + Err(error) => Err(error), + } + } + + /// Evaluates the operator tree rooted at this node into a float with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_float_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Float(float)) => Ok(float), + Ok(value) => Err(EvalexprError::expected_float(value)), + Err(error) => Err(error), + } + } + + /// Evaluates the operator tree rooted at this node into an integer with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_int_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Int(int)) => Ok(int), + Ok(value) => Err(EvalexprError::expected_int(value)), + Err(error) => Err(error), + } + } + + /// Evaluates the operator tree rooted at this node into a float with an the given mutable context. + /// If the result of the expression is an integer, it is silently converted into a float. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_number_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Int(int)) => Ok(int as FloatType), + Ok(Value::Float(float)) => Ok(float), + Ok(value) => Err(EvalexprError::expected_int(value)), + Err(error) => Err(error), + } + } + + /// Evaluates the operator tree rooted at this node into a boolean with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_boolean_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Boolean(boolean)) => Ok(boolean), + Ok(value) => Err(EvalexprError::expected_boolean(value)), + Err(error) => Err(error), + } + } + + /// Evaluates the operator tree rooted at this node into a tuple with an the given mutable context. + /// + /// Fails, if one of the operators in the expression tree fails. + pub fn eval_tuple_with_context_mut(&self, context: &mut Context) -> EvalexprResult { + match self.eval_with_context_mut(context) { + Ok(Value::Tuple(tuple)) => Ok(tuple), + Ok(value) => Err(EvalexprError::expected_tuple(value)), + Err(error) => Err(error), + } + } + /// Evaluates the operator tree rooted at this node into a string with an empty context. /// /// Fails, if one of the operators in the expression tree fails. diff --git a/tests/integration.rs b/tests/integration.rs index ab2e2ee..a979188 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() { @@ -100,12 +100,16 @@ fn test_boolean_examples() { #[test] fn test_with_context() { let mut context = HashMapContext::new(); - context.set_value("tr", Value::Boolean(true)).unwrap(); - context.set_value("fa", Value::Boolean(false)).unwrap(); - context.set_value("five", Value::Int(5)).unwrap(); - context.set_value("six", Value::Int(6)).unwrap(); - context.set_value("half", Value::Float(0.5)).unwrap(); - context.set_value("zero", Value::Int(0)).unwrap(); + context + .set_value("tr".into(), Value::Boolean(true)) + .unwrap(); + context + .set_value("fa".into(), Value::Boolean(false)) + .unwrap(); + context.set_value("five".into(), Value::Int(5)).unwrap(); + context.set_value("six".into(), Value::Int(6)).unwrap(); + context.set_value("half".into(), Value::Float(0.5)).unwrap(); + context.set_value("zero".into(), Value::Int(0)).unwrap(); assert_eq!(eval_with_context("tr", &context), Ok(Value::Boolean(true))); assert_eq!(eval_with_context("fa", &context), Ok(Value::Boolean(false))); @@ -166,7 +170,7 @@ fn test_n_ary_functions() { let mut context = HashMapContext::new(); context .set_function( - "sub2", + "sub2".into(), Function::new( Some(1), Box::new(|arguments| { @@ -183,7 +187,7 @@ fn test_n_ary_functions() { .unwrap(); context .set_function( - "avg", + "avg".into(), Function::new( Some(2), Box::new(|arguments| { @@ -203,7 +207,7 @@ fn test_n_ary_functions() { .unwrap(); context .set_function( - "muladd", + "muladd".into(), Function::new( Some(3), Box::new(|arguments| { @@ -227,7 +231,7 @@ fn test_n_ary_functions() { .unwrap(); context .set_function( - "count", + "count".into(), Function::new( None, Box::new(|arguments| Ok(Value::Int(arguments.len() as IntType))), @@ -333,7 +337,7 @@ fn test_no_panic() { fn test_shortcut_functions() { let mut context = HashMapContext::new(); context - .set_value("string", Value::from("a string")) + .set_value("string".into(), Value::from("a string")) .unwrap(); // assert_eq!(eval_string("???"));