From 3cb3197f6fdd9dda9ab72ac1ab1297284a508a80 Mon Sep 17 00:00:00 2001 From: fengcen Date: Mon, 21 Nov 2016 09:14:40 +0800 Subject: [PATCH] add `.` and `[]` support. --- Cargo.toml | 2 +- README.md | 21 +++++- src/error/mod.rs | 58 +++++++++----- src/lib.rs | 90 ++++++++++++++++++---- src/node/mod.rs | 21 ++++++ src/operator/mod.rs | 31 ++++++-- src/tree/mod.rs | 179 +++++++++++++++++++++++++++++++++----------- 7 files changed, 318 insertions(+), 84 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4764161..1df9bb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eval" -version = "0.2.0" +version = "0.2.1" description = "Expression evaluator" keywords = ["expression", "evaluate", "evaluator", "expr", "template"] authors = ["fengcen "] diff --git a/README.md b/README.md index b6ee0fe..cb277e8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ eval === -[![docs](https://docs.rs/eval/badge.svg?version=0.2.0 "docs")](https://docs.rs/eval) +[![docs](https://docs.rs/eval/badge.svg?version=0.2.1 "docs")](https://docs.rs/eval) Eval is a powerful expression evaluator. @@ -56,6 +56,23 @@ assert_eq!(Expr::new("foo == bar") Ok(to_value(true))); ``` + +You can access data like javascript by using `.` and `[]`. `[]` supports expression. + +``` +use eval::{Expr, to_value}; +use std::collections::HashMap; + +let mut object = HashMap::new(); +object.insert("foos", vec!["Hello", "world", "!"]); + +assert_eq!(Expr::new("object.foos[1-1] == 'Hello'") + .value("object", object) + .exec(), + Ok(to_value(true))); +``` + + You can eval with function: ``` @@ -67,6 +84,7 @@ assert_eq!(Expr::new("say_hello()") Ok(to_value("Hello world!"))); ``` + You can create an array with `[]`: ``` @@ -75,6 +93,7 @@ use eval::{eval, to_value}; assert_eq!(eval("[1, 2, 3, 4, 5]"), Ok(to_value(vec![1, 2, 3, 4, 5]))); ``` + You can create an integer array with `n..m`: ``` diff --git a/src/error/mod.rs b/src/error/mod.rs index 86d0f8f..f53895b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -4,70 +4,90 @@ use operator::Operator; quick_error! { - /// Expression parsing error +/// Expression parsing error #[derive(Debug, PartialEq)] pub enum Error { - /// Unsupported operator yet. +/// Unsupported operator yet. UnsupportedOperator(operator: String) { display("Unsupported operator: {:?}", operator) } - /// This operator does not support execution. +/// This operator does not support execution. CanNotExec(operator: Operator) { display("This operator does not support execution: {:?}", operator) } - /// Your expression may start with non-value operator like ( + * ) +/// Your expression may start with non-value operator like ( + * ) StartWithNonValueOperator { display("Your expression may start with non-value operator like ( + * ).") } - /// Unpaired brackets, left brackets count does not equal right brackets count +/// Unpaired brackets, left brackets count does not equal right brackets count UnpairedBrackets { display("Unpaired brackets, left brackets count does not equal right brackets count.") } - /// Duplicate values node, you may have (2 3) but there is no operators between them +/// Duplicate values node, you may have (2 3) but there is no operators between them DuplicateValueNode { display("Duplicate values node, you may have (2 3) but there is no operators between them.") } - /// Duplicate operators node, you may have (+ +) but there is no values between them +/// Duplicate operators node, you may have (+ +) but there is no values between them DuplicateOperatorNode { display("Duplicate operators node, you may have (+ +) but there is no values between them.") } - /// You have a comma(,) , but there is no function in front of it. +/// You have a comma(,) , but there is no function in front of it. CommaNotWithFunction { display("You have a comma(,) , but there is no function in front of it.") } - /// You have empty brackets () , but there is no function in front of it. +/// You have empty brackets () , but there is no function in front of it. BracketNotWithFunction { display("You have empty brackets () , but there is no function in front of it.") } - /// Function not exists. +/// Function not exists. FunctionNotExists(ident: String) { display("Function not exists: {}", ident) } - /// Expected boolean type but the given value isn't. - NotBoolean(value: Value) { - display("Expected boolean type, found: {}", value) +/// Expected a boolean but the given value isn't. + ExpectedBoolean(value: Value) { + display("Expected a boolean, found: {}", value) } - /// Failed to parse, no final expression. +/// Expected ident. + ExpectedIdentifier { + display("Expected ident.") + } +/// Expected array. + ExpectedArray { + display("Expected array.") + } +/// Expected object. + ExpectedObject { + display("Expected object.") + } +/// Expect number. + ExpectedNumber { + display("Expected number.") + } +/// Failed to parse, no final expression. NoFinalNode { display("Failed to parse, no final expression.") } - /// The number of arguments is greater than the maximum limit. +/// The number of arguments is greater than the maximum limit. ArgumentsGreater(max: usize) { display("The number of arguments is greater than the maximum limit: {}", max) } - /// The number of arguments is less than the minimum limit. +/// The number of arguments is less than the minimum limit. ArgumentsLess(min: usize) { display("The number of arguments is less than the minimum limit: {}", min) } - /// This two value types are different or do not support mathematical calculations. +/// This two value types are different or do not support mathematical calculations. UnsupportedTypes(a: String, b: String) { display("This two value types are different or do not support mathematical calculations: {}, {}", a, b) } - /// Invalid range expression like `1..2..3` +/// Invalid range expression like `1..2..3` InvalidRange(ident: String) { display("Invalid range expression: {}", ident) } - /// Custom error. +/// Can not add child node. + CanNotAddChild { + display("Can not add child node.") + } +/// Custom error. Custom(detail: String) { display("{}", detail) } diff --git a/src/lib.rs b/src/lib.rs index e00dbc7..f2b680b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ //! Eval is a powerful expression evaluator. //! -//! Supported operators: `!` `!=` `""` `''` `()` `[]` `,` `>` `<` `>=` `<=` +//! Supported operators: `!` `!=` `""` `''` `()` `[]` `.` `,` `>` `<` `>=` `<=` //! `==` `+` `-` `*` `/` `%` `&&` `||` `n..m`. //! -//! Built-in functions: `min()` `max()` `len()` `is_empty()`. +//! Built-in functions: `min()` `max()` `len()` `is_empty()` `array()`. //! //! ## Examples //! @@ -30,6 +30,21 @@ //! Ok(to_value(true))); //! ``` //! +//! You can access data like javascript by using `.` and `[]`. `[]` supports expression. +//! +//! ``` +//! use eval::{Expr, to_value}; +//! use std::collections::HashMap; +//! +//! let mut object = HashMap::new(); +//! object.insert("foos", vec!["Hello", "world", "!"]); +//! +//! assert_eq!(Expr::new("object.foos[2-1] == 'world'") // Access field `foos` and index `2-1` +//! .value("object", object) +//! .exec(), +//! Ok(to_value(true))); +//! ``` +//! //! You can eval with function: //! //! ``` @@ -41,12 +56,12 @@ //! Ok(to_value("Hello world!"))); //! ``` //! -//! You can create an array with `[]`: +//! You can create an array with `array()`: //! //! ``` //! use eval::{eval, to_value}; //! -//! assert_eq!(eval("[1, 2, 3, 4, 5]"), Ok(to_value(vec![1, 2, 3, 4, 5]))); +//! assert_eq!(eval("array(1, 2, 3, 4, 5)"), Ok(to_value(vec![1, 2, 3, 4, 5]))); //! ``` //! //! You can create an integer array with `n..m`: @@ -69,7 +84,10 @@ //! Accept single arguments and return the length of value. Only accept String, Array, Object and Null. //! //! ### is_empty() -//! Accept single arguments and return the a boolean. Check whether the value is empty or not. +//! Accept single arguments and return a boolean. Check whether the value is empty or not. +//! +//! ### array() +//! Accept multiple arguments and return an array. //! //! #![recursion_limit="100"] @@ -122,6 +140,7 @@ mod tests { use error::Error; use Expr; use tree::Tree; + use Value; use eval; use std::collections::HashMap; @@ -204,7 +223,7 @@ mod tests { #[test] fn test_len_array() { - assert_eq!(eval("len([2, 3, 4, 5, 6])"), Ok(to_value(5))); + assert_eq!(eval("len(array(2, 3, 4, 5, 6))"), Ok(to_value(5))); } #[test] @@ -320,17 +339,16 @@ mod tests { #[test] fn test_array() { - assert_eq!(eval("[1, 2, 3, 4]"), Ok(to_value(vec![1, 2, 3, 4]))); assert_eq!(eval("array(1, 2, 3, 4)"), Ok(to_value(vec![1, 2, 3, 4]))); } #[test] - fn test_array_ident() { + fn test_range() { assert_eq!(eval("0..5"), Ok(to_value(vec![0, 1, 2, 3, 4]))); } #[test] - fn test_array_ident_and_min() { + fn test_range_and_min() { assert_eq!(eval("min(0..5)"), Ok(to_value(0))); } @@ -403,12 +421,58 @@ mod tests { assert_eq!(eval("(!(1 == 2)) == true"), Ok(to_value(true))); } + #[test] + fn test_object_access() { + let mut object = HashMap::new(); + object.insert("foo", "Foo, hello world!"); + object.insert("bar", "Bar, hello world!"); + assert_eq!(Expr::new("object.foo == 'Foo, hello world!'") + .value("object", object) + .exec(), + Ok(to_value(true))); + } + + #[test] + fn test_object_dynamic_access() { + let mut object = HashMap::new(); + object.insert("foo", "Foo, hello world!"); + object.insert("bar", "Bar, hello world!"); + assert_eq!(Expr::new("object['foo'] == 'Foo, hello world!'") + .value("object", object) + .exec(), + Ok(to_value(true))); + } + + #[test] + fn test_object_dynamic_access_2() { + let mut object = HashMap::new(); + object.insert("foo", "Foo, hello world!"); + object.insert("bar", "Bar, hello world!"); + assert_eq!(Expr::new("object[foo] == 'Foo, hello world!'") + .value("object", object) + .value("foo", "foo") + .exec(), + Ok(to_value(true))); + } + + #[test] + fn test_path() { + assert_eq!(Expr::new("array[2-2].foo[2-2]").exec(), Ok(Value::Null)); + } + + #[test] + fn test_array_access() { + let array = vec!["hello", "world", "!"]; + assert_eq!(Expr::new("array[1-1] == 'hello' && array[1] == 'world' && array[2] == '!'") + .value("array", array) + .exec(), + Ok(to_value(true))); + } + #[test] fn test_builtin_is_empty() { assert_eq!(Expr::new("is_empty(array)") .value("array", Vec::::new()) - .compile() - .unwrap() .exec(), Ok(to_value(true))); } @@ -417,8 +481,6 @@ mod tests { fn test_builtin_min() { assert_eq!(Expr::new("min(array)") .value("array", vec![23, 34, 45, 2]) - .compile() - .unwrap() .exec(), Ok(to_value(2))); } @@ -428,8 +490,6 @@ mod tests { assert_eq!(Expr::new("output()") .function("output", |_| Ok(to_value("This is custom function's output"))) - .compile() - .unwrap() .exec(), Ok(to_value("This is custom function's output"))); } diff --git a/src/node/mod.rs b/src/node/mod.rs index da49037..c5c4eb9 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -64,6 +64,27 @@ impl Node { } } + pub fn is_unclosed_square_bracket(&self) -> bool { + match self.operator { + Operator::LeftSquareBracket(_) => !self.closed, + _ => false, + } + } + + pub fn is_left_square_bracket(&self) -> bool { + match self.operator { + Operator::LeftSquareBracket(_) => true, + _ => false, + } + } + + pub fn is_dot(&self) -> bool { + match self.operator { + Operator::Dot(_) => true, + _ => false, + } + } + pub fn add_child(&mut self, node: Node) { self.children.push(node); } diff --git a/src/operator/mod.rs b/src/operator/mod.rs index c686711..acb8bf5 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -21,9 +21,10 @@ pub enum Operator { Le(u8), And(u8), Or(u8), + Dot(u8), LeftParenthesis, RightParenthesis, - LeftSquareBracket, + LeftSquareBracket(u8), RightSquareBracket, DoubleQuotes, SingleQuote, @@ -107,6 +108,20 @@ impl Operator { } } + pub fn is_left_square_bracket(&self) -> bool { + match *self { + Operator::LeftSquareBracket(_) => true, + _ => false, + } + } + + pub fn is_dot(&self) -> bool { + match *self { + Operator::Dot(_) => true, + _ => false, + } + } + pub fn is_value_or_ident(&self) -> bool { match *self { Operator::Value(_) | @@ -130,6 +145,9 @@ impl Operator { Operator::And(_) | Operator::Or(_) | Operator::Ge(_) | + Operator::Not(_) | + Operator::Dot(_) | + Operator::LeftSquareBracket(_) | Operator::Le(_) => true, _ => false, } @@ -138,7 +156,7 @@ impl Operator { pub fn is_left(&self) -> bool { match *self { Operator::LeftParenthesis | - Operator::LeftSquareBracket => true, + Operator::LeftSquareBracket(_) => true, _ => false, } } @@ -146,7 +164,7 @@ impl Operator { pub fn get_left(&self) -> Operator { match *self { Operator::RightParenthesis => Operator::LeftParenthesis, - Operator::RightSquareBracket => Operator::LeftSquareBracket, + Operator::RightSquareBracket => Operator::LeftSquareBracket(100), _ => panic!("not bracket"), } } @@ -161,9 +179,9 @@ impl Operator { node } - pub fn get_identifier(&self) -> String { + pub fn get_identifier(&self) -> &str { match *self { - Operator::Identifier(ref ident) => ident.to_owned(), + Operator::Identifier(ref ident) => ident, _ => panic!("not identifier"), } } @@ -181,8 +199,9 @@ impl FromStr for Operator { "%" => Ok(Operator::Rem(10)), "(" => Ok(Operator::LeftParenthesis), ")" => Ok(Operator::RightParenthesis), - "[" => Ok(Operator::LeftSquareBracket), + "[" => Ok(Operator::LeftSquareBracket(100)), "]" => Ok(Operator::RightSquareBracket), + "." => Ok(Operator::Dot(100)), "\"" => Ok(Operator::DoubleQuotes), "'" => Ok(Operator::SingleQuote), " " => Ok(Operator::WhiteSpace), diff --git a/src/tree/mod.rs b/src/tree/mod.rs index fda6c54..2724b9d 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -31,7 +31,7 @@ impl Tree { for (index, cur) in self.raw.chars().enumerate() { match cur { '(' | ')' | '+' | '-' | '*' | '/' | ',' | ' ' | '!' | '=' | '>' | '<' | '\'' | - '[' | ']' | '%' | '&' | '|' => { + '[' | ']' | '.' | '%' | '&' | '|' => { if !found_quote { pos.push(index); pos.push(index + 1); @@ -57,9 +57,9 @@ impl Tree { let mut start; let mut end = 0; let mut parenthesis = 0; - let mut square_brackets = 0; let mut quote = None; let mut prev = String::new(); + let mut number = String::new(); for pos_ref in &self.pos { let pos = *pos_ref; @@ -72,6 +72,10 @@ impl Tree { let raw = self.raw[start..end].to_owned(); + if raw.is_empty() { + continue; + } + let operator = Operator::from_str(&raw).unwrap(); match operator { Operator::DoubleQuotes | Operator::SingleQuote => { @@ -96,8 +100,12 @@ impl Tree { continue; } - if raw.is_empty() { + if parse_number(&raw).is_some() || operator.is_dot() { + number += &raw; continue; + } else if !number.is_empty() { + operators.push(Operator::from_str(&number).unwrap()); + number.clear(); } if raw == "=" { @@ -137,19 +145,14 @@ impl Tree { } match operator { - Operator::LeftSquareBracket => { - square_brackets += 1; - operators.push(Operator::Function("array".to_owned())); - operators.push(operator); - continue; - } Operator::LeftParenthesis => { parenthesis += 1; if !operators.is_empty() { let prev_operator = operators.pop().unwrap(); if prev_operator.is_identifier() { - operators.push(Operator::Function(prev_operator.get_identifier())); + operators.push(Operator::Function(prev_operator.get_identifier() + .to_owned())); operators.push(operator); continue; } else { @@ -158,7 +161,6 @@ impl Tree { } } Operator::RightParenthesis => parenthesis -= 1, - Operator::RightSquareBracket => square_brackets -= 1, Operator::WhiteSpace => continue, _ => (), } @@ -167,7 +169,11 @@ impl Tree { operators.push(operator); } - if parenthesis != 0 || square_brackets != 0 { + if !number.is_empty() { + operators.push(Operator::from_str(&number).unwrap()); + } + + if parenthesis != 0 { Err(Error::UnpairedBrackets) } else { self.operators = operators; @@ -193,6 +199,8 @@ impl Tree { Operator::And(priority) | Operator::Or(priority) | Operator::Le(priority) | + Operator::Dot(priority) | + Operator::LeftSquareBracket(priority) | Operator::Rem(priority) => { if !parsing_nodes.is_empty() { let prev = parsing_nodes.pop().unwrap(); @@ -215,15 +223,14 @@ impl Tree { } } Operator::Function(_) | - Operator::LeftParenthesis | - Operator::LeftSquareBracket => parsing_nodes.push(operator.to_node()), + Operator::LeftParenthesis => parsing_nodes.push(operator.to_node()), Operator::Comma => close_comma(&mut parsing_nodes)?, Operator::RightParenthesis | Operator::RightSquareBracket => { close_bracket(&mut parsing_nodes, operator.get_left())? } Operator::Value(_) | - Operator::Identifier(_) => append_child_to_last_node(&mut parsing_nodes, operator)?, + Operator::Identifier(_) => append_value_to_last_node(&mut parsing_nodes, operator)?, _ => (), } } @@ -337,7 +344,93 @@ impl Tree { match value { Value::Bool(boolean) => Ok(Value::Bool(!boolean)), Value::Null => Ok(Value::Bool(true)), - _ => Err(Error::NotBoolean(value)), + _ => Err(Error::ExpectedBoolean(value)), + } + } + Operator::Dot(_) => { + let mut value = None; + for child in &node.children { + if value.is_none() { + let name = exec_node(child, builtin, contexts, functions)?; + if name.is_string() { + value = find(contexts, name.as_str().unwrap()); + if value.is_none() { + return Ok(Value::Null); + } + } else if name.is_object() { + value = Some(name); + } else if name.is_null() { + return Ok(Value::Null); + } else { + return Err(Error::ExpectedObject); + } + } else { + if child.operator.is_identifier() { + value = value.as_ref() + .unwrap() + .find(child.operator.get_identifier()) + .cloned(); + } else { + return Err(Error::ExpectedIdentifier); + } + } + } + + if value.is_some() { + return Ok(value.unwrap()); + } else { + return Ok(Value::Null); + } + } + Operator::LeftSquareBracket(_) => { + let mut value = None; + for child in &node.children { + let name = exec_node(child, builtin, contexts, functions)?; + if value.is_none() { + if name.is_string() { + value = find(contexts, name.as_str().unwrap()); + if value.is_none() { + return Ok(Value::Null); + } + } else if name.is_array() { + value = Some(name); + } else if name.is_object() { + value = Some(name); + } else if name.is_null() { + return Ok(Value::Null); + } else { + return Err(Error::ExpectedArray); + } + } else if value.as_ref().unwrap().is_object() { + if name.is_string() { + value = value.as_ref() + .unwrap() + .find(name.as_str().unwrap()) + .cloned(); + } else { + return Err(Error::ExpectedIdentifier); + } + } else { + if name.is_u64() { + if value.as_ref().unwrap().is_array() { + value = value.as_ref() + .unwrap() + .as_array() + .unwrap() + .get(name.as_u64().unwrap() as usize) + .cloned(); + } else { + return Err(Error::ExpectedArray); + } + } else { + return Err(Error::ExpectedNumber); + } + } + } + if value.is_some() { + return Ok(value.unwrap()); + } else { + return Ok(Value::Null); } } Operator::Identifier(ref ident) => { @@ -360,21 +453,30 @@ impl Tree { } } -fn append_child_to_last_node(parsing_nodes: &mut Vec, +fn append_value_to_last_node(parsing_nodes: &mut Vec, operator: &Operator) -> Result<(), Error> { let mut node = operator.to_node(); node.closed = true; if let Some(mut prev) = parsing_nodes.pop() { - if prev.is_value_or_enough() { + if prev.is_dot() { + prev.add_child(node); + prev.closed = true; + parsing_nodes.push(prev); + } else if prev.is_left_square_bracket() { + parsing_nodes.push(prev); + parsing_nodes.push(node); + } else if prev.is_value_or_enough() { return Err(Error::DuplicateValueNode); } else if prev.is_enough() { parsing_nodes.push(prev); parsing_nodes.push(node); - } else { + } else if prev.operator.can_have_child() { prev.add_child(node); parsing_nodes.push(prev); + } else { + return Err(Error::CanNotAddChild); } } else { parsing_nodes.push(node); @@ -391,8 +493,12 @@ fn get_final_node(mut parsing_nodes: Vec) -> Result { while parsing_nodes.len() != 1 { let last = parsing_nodes.pop().unwrap(); let mut prev = parsing_nodes.pop().unwrap(); - prev.add_child(last); - parsing_nodes.push(prev); + if prev.operator.can_have_child() { + prev.add_child(last); + parsing_nodes.push(prev); + } else { + return Err(Error::CanNotAddChild); + } } Ok(parsing_nodes.pop().unwrap()) @@ -403,7 +509,14 @@ fn close_bracket(parsing_nodes: &mut Vec, bracket: Operator) -> Result<(), let mut current = parsing_nodes.pop().unwrap(); let mut prev = parsing_nodes.pop().unwrap(); - if current.operator == bracket { + if current.operator.is_left_square_bracket() { + return Err(Error::BracketNotWithFunction); + } else if prev.operator.is_left_square_bracket() { + prev.add_child(current); + prev.closed = true; + parsing_nodes.push(prev); + break; + } else if current.operator == bracket { if prev.is_unclosed_function() { prev.closed = true; parsing_nodes.push(prev); @@ -503,9 +616,8 @@ fn rob_to(mut was_robed: Node, mut rober: Node) -> Vec { fn find(contexts: &[Context], key: &str) -> Option { for context in contexts.iter().rev() { - let value = get(context, key); - match value { - Some(_) => return value, + match context.get(key) { + Some(value) => return Some(value.clone()), None => continue, } } @@ -513,23 +625,6 @@ fn find(contexts: &[Context], key: &str) -> Option { None } -fn get(context: &Context, key: &str) -> Option { - let mut keys = key.split('.').collect::>(); - let context_key = keys.remove(0); - let context_value_option = context.get(context_key); - - if context_value_option.is_none() { - None - } else if !keys.is_empty() { - match context_value_option.unwrap().search(&keys.join(".")) { - Some(value) => Some(value.clone()), - None => None, - } - } else { - Some(context_value_option.unwrap().clone()) - } -} - fn is_range(ident: &str) -> bool { ident.contains("..") }