Skip to content

Commit

Permalink
Expression is now Sync.
Browse files Browse the repository at this point in the history
This commit updates Expression to use a serde_json::Value as the literal
value instad of an RcVar. This has the benefit that Expression is now
Sync and therefore can be used in things like lazy_static!, but it does
require that a value is converted to a Variable each time a literal
value is referenced while evaluating an expression. Most literals tend
to be rather small and typically scalars, so this should not cause a
noticeable performance issue in practice.
  • Loading branch information
mtdowling committed May 20, 2016
1 parent d38601d commit 8a170e0
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 24 deletions.
14 changes: 8 additions & 6 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! JMESPath AST

extern crate serde_json;

use std::fmt;

use super::RcVar;
use super::Coordinates;
use serde_json::Value;
use Coordinates;
use lexer::Token;

/// Abstract syntax tree of a JMESPath expression.
#[derive(Clone, PartialEq, PartialOrd, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub enum Ast {
/// Compares two nodes using a comparator, returning true/false.
Comparison {
Expand Down Expand Up @@ -77,7 +79,7 @@ pub enum Ast {
/// Approximate absolute position in the parsed expression.
offset: usize,
/// Literal value
value: RcVar,
value: Value,
},
/// Evaluates to a list of evaluated expressions.
MultiList {
Expand Down Expand Up @@ -190,7 +192,7 @@ impl fmt::Display for Ast {
}

/// Represents a key value pair in a multi-hash
#[derive(Clone, PartialEq, PartialOrd, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub struct KeyValuePair {
/// Key name.
pub key: String,
Expand All @@ -199,7 +201,7 @@ pub struct KeyValuePair {
}

/// Comparators used in Comparison nodes
#[derive(Clone, PartialEq, PartialOrd, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub enum Comparator {
Eq(EqComparator),
Ord(OrdComparator),
Expand Down
2 changes: 1 addition & 1 deletion src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ macro_rules! arg {
* ------------------------------------------ */

/// Stores and evaluates JMESPath functions.
pub trait FnRegistry {
pub trait FnRegistry : Sync {
/// Gets a function signature by name from the registry.
///
/// Returns the signature if found, or None if not found.
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn interpret(data: &RcVar, node: &Ast, ctx: &mut Context) -> SearchResult {
},
Ast::Field { ref name, .. } => Ok(get_field(data, name)),
Ast::Identity { .. } => Ok(data.clone()),
Ast::Literal { ref value, .. } => Ok(value.clone()),
Ast::Literal { ref value, .. } => Ok(Rc::new(Variable::from_serde_value(value))),
Ast::Index { idx, .. } => {
if idx >= 0 {
Ok(get_index(data, idx as usize))
Expand Down
31 changes: 17 additions & 14 deletions src/lexer.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Module for tokenizing JMESPath expression.

use std::rc::Rc;
extern crate serde_json;

use std::iter::Peekable;
use std::str::CharIndices;
use std::collections::VecDeque;
use serde_json::Value;

use super::{Error, ErrorReason, RcVar};
use super::{Error, ErrorReason};
use self::Token::*;
use super::variable::Variable;

Expand All @@ -15,7 +17,7 @@ pub enum Token {
Identifier(String),
QuotedIdentifier(String),
Number(i32),
Literal(RcVar),
Literal(Box<Value>),
Dot,
Star,
Flatten,
Expand Down Expand Up @@ -272,16 +274,16 @@ impl<'a> Lexer<'a> {
fn consume_raw_string(&mut self, pos: usize) -> Result<Token, Error> {
// Note: we need to unescape here because the backslashes are passed through.
self.consume_inside(pos, '\'',
|s| Ok(Literal(Rc::new(Variable::String(s.replace("\\'", "'"))))))
|s| Ok(Literal(Box::new(Value::String(s.replace("\\'", "'"))))))
}

// Consume and parse a literal JSON token.
#[inline]
fn consume_literal(&mut self, pos: usize) -> Result<Token, Error> {
self.consume_inside(pos, '`', |s| {
let unescaped = s.replace("\\`", "`");
match Variable::from_json(unescaped.as_ref()) {
Ok(j) => Ok(Literal(Rc::new(j))),
match serde_json::from_str(unescaped.as_ref()) {
Ok(j) => Ok(Literal(Box::new(j))),
Err(err) => Err(format!("Unable to parse literal JSON {}: {}", s, err))
}
})
Expand All @@ -302,10 +304,11 @@ impl<'a> Lexer<'a> {

#[cfg(test)]
mod tests {
use std::rc::Rc;
extern crate serde_json;

use super::*;
use super::Token::*;
use variable::Variable;
use serde_json::Value;

fn tokenize_queue(expr: &str) -> Vec<TokenTuple> {
let mut result = tokenize(expr).unwrap();
Expand Down Expand Up @@ -430,15 +433,15 @@ mod tests {
#[test]
fn tokenize_raw_string_test() {
assert_eq!(tokenize_queue("'foo'"), vec![
(0, Literal(Rc::new(Variable::String("foo".to_string())))),
(0, Literal(Box::new(Value::String("foo".to_string())))),
(5, Eof)
]);
assert_eq!(tokenize_queue("''"), vec![
(0, Literal(Rc::new(Variable::String("".to_string())))),
(0, Literal(Box::new(Value::String("".to_string())))),
(2, Eof)
]);
assert_eq!(tokenize_queue("'a\\nb'"), vec![
(0, Literal(Rc::new(Variable::String("a\\nb".to_string())))),
(0, Literal(Box::new(Value::String("a\\nb".to_string())))),
(6, Eof)
]);
}
Expand All @@ -448,11 +451,11 @@ mod tests {
// Must enclose in quotes. See JEP 12.
assert!(tokenize("`a`").unwrap_err().to_string().contains("Unable to parse"));
assert_eq!(tokenize_queue("`\"a\"`"), vec![
(0, Literal(Rc::new(Variable::String("a".to_string())))),
(0, Literal(Box::new(Value::String("a".to_string())))),
(5, Eof)
]);
assert_eq!(tokenize_queue("`\"a b\"`"), vec![
(0, Literal(Rc::new(Variable::String("a b".to_string())))),
(0, Literal(Box::new(Value::String("a b".to_string())))),
(7, Eof)
]);
}
Expand Down Expand Up @@ -482,7 +485,7 @@ mod tests {
assert_eq!(tokens[1], (3, Dot));
assert_eq!(tokens[2], (4, Identifier("bar".to_string())));
assert_eq!(tokens[3], (8, Or));
assert_eq!(tokens[4], (11, Literal(Rc::new(Variable::String("a".to_string())))));
assert_eq!(tokens[4], (11, Literal(Box::new(Value::String("a".to_string())))));
assert_eq!(tokens[5], (17, Pipe));
assert_eq!(tokens[6], (19, Number(10)));
assert_eq!(tokens[7], (21, Eof));
Expand Down
2 changes: 1 addition & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ impl<'a> Parser<'a> {
},
Token::Literal(value) => {
Ok(Trampoline::Value(Ast::Literal {
value: value,
value: *value,
offset: offset
}))
},
Expand Down
53 changes: 52 additions & 1 deletion src/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::string::ToString;
use self::serde::de;
use self::serde::ser;
use self::serde::Serialize;
use self::serde_json::Value;
use self::serde_json::error::Error;

use super::RcVar;
Expand All @@ -22,7 +23,7 @@ use super::ast::{Ast, Comparator, EqComparator, OrdComparator};
///
/// Note: this enum and implementation is based on rustc-serialize:
/// https://github.com/rust-lang-nursery/rustc-serialize
#[derive(Clone, PartialEq, PartialOrd, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub enum Variable {
Null,
String(String),
Expand All @@ -37,6 +38,31 @@ pub enum Variable {

impl Eq for Variable {}

/// Implement PartialOrd so that Ast can be in the PartialOrd of Variable.
impl PartialOrd<Variable> for Variable {
fn partial_cmp(&self, other: &Variable) -> Option<Ordering> {
Some(self.cmp(other))
}

fn lt(&self, other: &Variable) -> bool {
self.cmp(other) == Ordering::Less
}

fn le(&self, other: &Variable) -> bool {
let ordering = self.cmp(other);
ordering == Ordering::Equal || ordering == Ordering::Less
}

fn gt(&self, other: &Variable) -> bool {
self.cmp(other) == Ordering::Greater
}

fn ge(&self, other: &Variable) -> bool {
let ordering = self.cmp(other);
ordering == Ordering::Equal || ordering == Ordering::Greater
}
}

impl Ord for Variable {
fn cmp(&self, other: &Self) -> Ordering {
let var_type = self.get_type();
Expand Down Expand Up @@ -69,6 +95,31 @@ impl Variable {
serde_json::from_str::<Variable>(s).map_err(|e| e.to_string())
}

/// Create a JMESPath `Variable` from a `serde_json::Value` type.
/// TODO: Can trait specialization remove the need for this?
pub fn from_serde_value(value: &Value) -> Variable {
match *value {
Value::String(ref s) => Variable::String(s.to_owned()),
Value::Null => Variable::Null,
Value::Bool(b) => Variable::Bool(b),
Value::F64(f) => Variable::F64(f),
Value::I64(i) => Variable::I64(i),
Value::U64(u) => Variable::U64(u),
Value::Array(ref values) => {
Variable::Array(
values.iter().map(|v| Rc::new(Variable::from_serde_value(v))).collect()
)
},
Value::Object(ref values) => {
let mut map: BTreeMap<String, RcVar> = BTreeMap::new();
for kvp in values.iter() {
map.insert(kvp.0.to_owned(), Rc::new(Variable::from_serde_value(kvp.1)));
}
Variable::Object(map)
},
}
}

/// Create a JMESPath `Variable` from a `serde::se::Serialize` type.
pub fn from_serialize<T: ser::Serialize>(value: &T) -> Variable {
let mut ser = Serializer::new();
Expand Down

0 comments on commit 8a170e0

Please sign in to comment.