use crate::operators::Operator;
use crate::Data;
use serde_json::Value;
use std::collections::HashSet;
#[derive(Debug, PartialEq)]
pub enum Expression<'a> {
Constant(&'a Value),
Computed(Operator, Vec<Expression<'a>>),
}
impl<'a> Expression<'a> {
pub fn from_json(json: &Value) -> Result<Expression, String> {
if !json.is_object() {
return Ok(Expression::Constant(&json));
}
let object = json.as_object().unwrap();
if object.len() != 1 {
return Ok(Expression::Constant(json));
}
let entry: Vec<(&String, &serde_json::Value)> = object.iter().collect();
let &(operator_key, value) = entry.get(0).unwrap();
let operator = Operator::from_str(operator_key)
.ok_or_else(|| format!("Unrecognized operation {}", operator_key))?;
let arguments: Vec<_> = match value {
Value::Array(arr) => arr.iter().map(|expr| Expression::from_json(expr)).collect(),
Value::Null => Ok(vec![]),
_ => Expression::from_json(value).and_then(|expr| Ok(vec![expr])),
}?;
Ok(Expression::Computed(operator, arguments))
}
pub fn compute(&self, data: &Data) -> Value {
match self {
Expression::Constant(value) => (*value).clone(),
Expression::Computed(operator, args) => operator.compute(args, data),
}
}
pub fn get_variable_names(&self) -> Result<HashSet<String>, String> {
let mut variable_names: HashSet<String> = HashSet::new();
self.insert_var_names(&mut variable_names)?;
Ok(variable_names)
}
fn insert_var_names(&self, names: &mut HashSet<String>) -> Result<(), String> {
match self {
Expression::Constant(_) => Ok(()),
Expression::Computed(operator, args) => {
if let Operator::Variable = operator {
let first_expr = args
.get(0)
.ok_or("found Variable operator without arguments")?;
if let Expression::Constant(name_value) = first_expr {
let name = name_value
.as_str()
.ok_or("found Variable operator with non string argument")?;
names.insert(name.to_owned());
return Ok(());
} else {
return Err(String::from(
"found Variable operator with non static argument",
));
}
}
args.iter()
.map(|expr| expr.insert_var_names(names))
.collect()
}
}
}
}
#[cfg(test)]
mod tests {
use super::Expression::*;
use super::*;
use serde_json::json;
#[test]
fn parse_to_ast() {
assert_eq!(
Expression::from_json(&json!({ "==": null })).unwrap(),
Expression::Computed(Operator::Equal, vec![])
);
assert_eq!(
Expression::from_json(&json!({ "==": [] })).unwrap(),
Expression::Computed(Operator::Equal, vec![])
);
assert_eq!(
Expression::from_json(&json!({ "==": [1] })).unwrap(),
Expression::Computed(Operator::Equal, vec![Constant(&json!(1))])
);
assert_eq!(
Expression::from_json(&json!({ "==": [1, 2] })).unwrap(),
Expression::Computed(
Operator::Equal,
vec![Constant(&json!(1)), Constant(&json!(2))]
)
);
assert_eq!(
Expression::from_json(&json!({"!=": [5, 2]})).unwrap(),
Expression::Computed(
Operator::NotEqual,
vec![Constant(&json!(5)), Constant(&json!(2))]
)
);
assert_eq!(
Expression::from_json(&json!({"var": ["foo"]})).unwrap(),
Expression::Computed(Operator::Variable, vec![Constant(&json!("foo"))])
);
assert_eq!(
Expression::from_json(&json!({"==": [{"var": ["foo"]}, "foo"]})).unwrap(),
Expression::Computed(
Operator::Equal,
vec![
Expression::Computed(Operator::Variable, vec![Constant(&json!("foo"))]),
Expression::Constant(&json!("foo"))
]
)
);
}
#[test]
fn get_variable_names_error() {
assert_eq!(
Expression::Computed(
Operator::Variable,
vec![Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
)]
)
.get_variable_names(),
Err(String::from(
"found Variable operator with non static argument"
))
);
assert_eq!(
Expression::Computed(Operator::Variable, vec![Expression::Constant(&json!(1))])
.get_variable_names(),
Err(String::from(
"found Variable operator with non string argument"
))
);
assert_eq!(
Expression::Computed(Operator::Variable, vec![]).get_variable_names(),
Err(String::from("found Variable operator without arguments"))
);
}
#[test]
fn get_variable_names() {
assert_eq!(
Expression::Constant(&json!("foo")).get_variable_names(),
Ok(HashSet::new())
);
assert_eq!(
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
)
.get_variable_names(),
Ok(["foo".to_owned()].iter().cloned().collect::<HashSet<_>>())
);
assert_eq!(
Expression::Computed(
Operator::Equal,
vec![
Expression::Constant(&json!("a value")),
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
)
]
)
.get_variable_names(),
Ok(["foo".to_owned()].iter().cloned().collect::<HashSet<_>>())
);
assert_eq!(
Expression::Computed(
Operator::Equal,
vec![
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
),
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
)
]
)
.get_variable_names(),
Ok(["foo".to_owned()].iter().cloned().collect::<HashSet<_>>())
);
assert_eq!(
Expression::Computed(
Operator::Equal,
vec![
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("bar"))]
),
Expression::Computed(
Operator::Variable,
vec![Expression::Constant(&json!("foo"))]
)
]
)
.get_variable_names(),
Ok(["foo".to_owned(), "bar".to_owned()]
.iter()
.cloned()
.collect::<HashSet<_>>())
);
}
mod compute {
use super::*;
#[test]
fn constant_expression() {
assert_eq!(Constant(&json!(1)).compute(&Data::empty()), json!(1));
}
#[test]
fn equal() {
assert_eq!(
Computed(Operator::Equal, vec![]).compute(&Data::empty()),
json!(true)
);
assert_eq!(
Computed(Operator::Equal, vec![Constant(&json!(null))]).compute(&Data::empty()),
json!(true)
);
assert_eq!(
Computed(
Operator::Equal,
vec![Constant(&json!(1)), Constant(&json!(1))]
)
.compute(&Data::empty()),
json!(true)
);
assert_eq!(
Computed(
Operator::Equal,
vec![Constant(&json!(1)), Constant(&json!(2))]
)
.compute(&Data::empty()),
json!(false)
);
}
}
}