From a7ca4c717d3faa2bc1e5a95df90049b5419332ba Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Sat, 23 Mar 2019 15:20:43 +0200 Subject: [PATCH] Implement, test and document serde `Deserialize` for expressions Relates to #18 --- CHANGELOG.md | 2 ++ Cargo.toml | 5 ++++- src/error/display.rs | 23 +--------------------- src/error/mod.rs | 3 +++ src/feature_serde/mod.rs | 42 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 31 +++++++++++++++++++++++++++-- tests/integration.rs | 12 ++++++++++++ 7 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 src/feature_serde/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b5f34b..7bdb61e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Added * Add `serde` feature + * Implement `serde::de::Deserialize` for `Node` + * Document `serde` usage ### Removed diff --git a/Cargo.toml b/Cargo.toml index 9a16143..e491f3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,7 @@ path = "src/lib.rs" [dependencies] serde = { version = "1", optional = true} -[features] \ No newline at end of file +[features] + +[dev-dependencies] +ron = "0.4" \ No newline at end of file diff --git a/src/error/display.rs b/src/error/display.rs index ea0cd9a..12ca80d 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -10,64 +10,48 @@ impl fmt::Display for Error { "An operator expected {} arguments, but got {}.", expected, actual ), - WrongFunctionArgumentAmount { expected, actual } => write!( f, "A function expected {} arguments, but got {}.", expected, actual ), - ExpectedString { actual } => { write!(f, "Expected a Value::String, but got {:?}.", actual) }, - ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual), - ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual), - ExpectedNumber { actual } => { write!(f, "Expected a Value::Number, but got {:?}.", actual) }, - ExpectedBoolean { actual } => { write!(f, "Expected a Value::Boolean, but got {:?}.", actual) }, - ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual), - EmptyExpression => write!( f, "Got an empty expression that cannot be parsed into a node tree, because it \ returns nothing." ), - AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), - PrecedenceViolation => write!( f, "Tried to append a node to another node with higher precedence." ), - VariableIdentifierNotFound(identifier) => write!( f, "Variable identifier is not bound to anything by configuration: {:?}.", identifier ), - FunctionIdentifierNotFound(identifier) => write!( f, "Function identifier is not bound to anything by configuration: {:?}.", identifier ), - TypeError { expected, actual } => { write!(f, "Expected one of {:?}, but got {:?}.", expected, actual) }, - UnmatchedLBrace => write!(f, "Found an unmatched opening parenthesis '('."), - UnmatchedRBrace => write!(f, "Found an unmatched closing parenthesis ')'."), - UnmatchedPartialToken { first, second } => { if let Some(second) = second { write!( @@ -84,28 +68,23 @@ impl fmt::Display for Error { ) } }, - AdditionError { augend, addend } => write!(f, "Error adding {} + {}", augend, addend), - SubtractionError { minuend, subtrahend, } => write!(f, "Error subtracting {} - {}", minuend, subtrahend), - NegationError { argument } => write!(f, "Error negating -{}", argument), - MultiplicationError { multiplicand, multiplier, } => write!(f, "Error multiplying {} * {}", multiplicand, multiplier), - DivisionError { dividend, divisor } => { write!(f, "Error dividing {} / {}", dividend, divisor) }, - ModulationError { dividend, divisor } => { write!(f, "Error modulating {} % {}", dividend, divisor) }, + Custom(message) => write!(f, "Error: {}", message), } } } diff --git a/src/error/mod.rs b/src/error/mod.rs index 1a205f2..18d076f 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -153,6 +153,9 @@ pub enum Error { /// The second argument of the modulation. divisor: Value, }, + + /// A custom error explained by its message. + Custom(String), } impl Error { diff --git a/src/feature_serde/mod.rs b/src/feature_serde/mod.rs new file mode 100644 index 0000000..d2d0706 --- /dev/null +++ b/src/feature_serde/mod.rs @@ -0,0 +1,42 @@ +use interface::build_operator_tree; +use serde::{de, Deserialize, Deserializer}; +use std::fmt; +use ::{Error, Node}; + +impl<'de> Deserialize<'de> for Node { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(NodeVisitor) + } +} + +struct NodeVisitor; + +impl<'de> de::Visitor<'de> for NodeVisitor { + type Value = Node; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "a string in the expression format of the `evalexpr` crate" + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + match build_operator_tree(v) { + Ok(node) => Ok(node), + Err(error) => Err(E::custom(error)), + } + } +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Custom(msg.to_string()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 690ceb2..3be9a84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -231,6 +231,31 @@ //! | `true` | no | Expression is interpreted as `Value::Bool` | //! | `.34` | no | Expression is interpreted as `Value::Float` | //! +//! ### [serde](https://serde.rs) +//! +//! This crate implements `serde::de::Deserialize` for its type `Node` that represents a parsed expression tree. +//! The implementation expects a `string` as input. +//! Example parsing with [ron format](docs.rs/ron): +//! +//! ```rust +//! extern crate ron; +//! use evalexpr::*; +//! +//! let mut configuration = HashMapConfiguration::new(); +//! configuration.insert_variable("five", 5); +//! +//! // In ron format, strings are surrounded by " +//! let serialized_free = "\"five * five\""; +//! match ron::de::from_str::(serialized_free) { +//! Ok(free) => assert_eq!(free.eval_with_configuration(&configuration), Ok(Value::from(25))), +//! Err(error) => { +//! // Handle error +//! }, +//! } +//! ``` +//! +//! With `serde`, expressions can be integrated into arbitrarily complex data. +//! //! ## License //! //! This crate is primarily distributed under the terms of the MIT license. @@ -239,19 +264,21 @@ #![warn(missing_docs)] +#[cfg(test)] +extern crate ron; #[cfg(feature = "serde")] extern crate serde; mod configuration; pub mod error; +#[cfg(feature = "serde")] +mod feature_serde; mod function; mod interface; mod operator; mod token; mod tree; mod value; -#[cfg(feature = "serde")] -mod feature_serde; // Exports diff --git a/tests/integration.rs b/tests/integration.rs index f72f853..2c8c846 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -407,3 +407,15 @@ fn test_shortcut_functions() { Ok(vec![Value::Int(3), Value::Int(3)]) ); } + +#[cfg(feature = "serde")] +#[test] +fn test_serde() { + let strings = ["3", "4+4", "21^(2*2)--3>5||!true"]; + + for string in &strings { + let manual_tree = build_operator_tree(string).unwrap(); + let serde_tree: Node = ron::de::from_str(&format!("\"{}\"", string)).unwrap(); + assert_eq!(manual_tree.eval(), serde_tree.eval()); + } +}