Merge ContextMut with Context and add eval_<type>_with_context_mut methods

Trait objects of `ContextMut` cannot be converted into `Context`, even though `ContextMut` requires `Context`.

Relates to #30
This commit is contained in:
Sebastian Schmidt 2019-03-28 09:14:37 +01:00
parent 6bd68e6491
commit e266f4fc0d
8 changed files with 154 additions and 100 deletions

View File

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

View File

@ -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\"";

View File

@ -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<S: Into<String>, V: Into<Value>>(
&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<S: Into<String>>(
&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<S: Into<String>, V: Into<Value>>(
&mut self,
_identifier: S,
_value: V,
) -> EvalexprResult<()> {
Err(EvalexprError::ContextNotManipulable)
}
fn set_function<S: Into<String>>(
&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<S: Into<String>, V: Into<Value>>(
&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<S: Into<String>>(
&mut self,
identifier: S,
function: Function,
) -> EvalexprResult<()> {
fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
self.functions.insert(identifier.into(), function);
Ok(())
}

View File

@ -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<Value> {
/// 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<Valu
/// let precomputed = build_operator_tree("one + two + three").unwrap(); // Do proper error handling here
///
/// 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!(precomputed.eval_with_context(&context), Ok(Value::from(6)));
///
/// context.set_value("three", 5).unwrap(); // Do proper error handling here
/// context.set_value("three".into(), 5.into()).unwrap(); // Do proper error handling here
/// assert_eq!(precomputed.eval_with_context(&context), Ok(Value::from(8)));
/// ```
///

View File

@ -36,9 +36,9 @@
//! 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] {
@ -47,7 +47,7 @@
//! 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])?;
//!
@ -74,12 +74,12 @@
//! 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.
@ -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;

View File

@ -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<Value>;
fn eval(&self, arguments: &[Value], context: &dyn Context) -> EvalexprResult<Value>;
/// Evaluates the operator with the given arguments and mutable context.
fn eval_mut(&self, arguments: &[Value], context: &mut ContextMut) -> EvalexprResult<Value> {
fn eval_mut(&self, arguments: &[Value], context: &mut dyn Context) -> EvalexprResult<Value> {
self.eval(arguments, context)
}
}

View File

@ -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<Value> {
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<String> {
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<FloatType> {
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<IntType> {
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<FloatType> {
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<bool> {
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<TupleType> {
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.

View File

@ -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("???"));