add . and [] support.

This commit is contained in:
fengcen 2016-11-21 09:14:40 +08:00
parent 032e57df43
commit 3cb3197f6f
7 changed files with 318 additions and 84 deletions

View File

@ -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 <fengcen.love@gmail.com>"]

View File

@ -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`:
```

View File

@ -43,9 +43,25 @@ quick_error! {
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)
}
/// 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 {
@ -67,6 +83,10 @@ quick_error! {
InvalidRange(ident: String) {
display("Invalid range expression: {}", ident)
}
/// Can not add child node.
CanNotAddChild {
display("Can not add child node.")
}
/// Custom error.
Custom(detail: String) {
display("{}", detail)

View File

@ -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::<String>::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")));
}

View File

@ -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);
}

View File

@ -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),

View File

@ -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<Node>,
fn append_value_to_last_node(parsing_nodes: &mut Vec<Node>,
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<Node>) -> Result<Node, Error> {
while parsing_nodes.len() != 1 {
let last = parsing_nodes.pop().unwrap();
let mut prev = parsing_nodes.pop().unwrap();
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<Node>, 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<Node> {
fn find(contexts: &[Context], key: &str) -> Option<Value> {
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<Value> {
None
}
fn get(context: &Context, key: &str) -> Option<Value> {
let mut keys = key.split('.').collect::<Vec<_>>();
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("..")
}