Add JSON support for exprs (fixes #146)

This commit is contained in:
elkowar 2021-04-14 14:18:08 +02:00
parent 72457aaa11
commit ce4c22e2a6
5 changed files with 97 additions and 4 deletions

18
Cargo.lock generated
View file

@ -326,6 +326,7 @@ dependencies = [
"regex",
"roxmltree",
"serde",
"serde_json",
"simple-signal",
"smart-default",
"structopt",
@ -830,6 +831,12 @@ dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lasso"
version = "0.3.1"
@ -1424,6 +1431,17 @@ dependencies = [
"syn 1.0.64",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"

View file

@ -33,6 +33,7 @@ derive_more = "0.99"
maplit = "1"
structopt = "0.3"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
extend = "1"
grass = "0.10"
num = "0.4"

View file

@ -58,6 +58,7 @@ pub enum AttrValueExpr {
BinOp(Box<AttrValueExpr>, BinOp, Box<AttrValueExpr>),
UnaryOp(UnaryOp, Box<AttrValueExpr>),
IfElse(Box<AttrValueExpr>, Box<AttrValueExpr>, Box<AttrValueExpr>),
JsonAccessIndex(Box<AttrValueExpr>, Box<AttrValueExpr>),
}
impl std::fmt::Display for AttrValueExpr {
@ -68,6 +69,7 @@ impl std::fmt::Display for AttrValueExpr {
AttrValueExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r),
AttrValueExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x),
AttrValueExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c),
AttrValueExpr::JsonAccessIndex(value, index) => write!(f, "{}[{}]", value, index),
}
}
}
@ -101,6 +103,7 @@ impl AttrValueExpr {
IfElse(box a, box b, box c) => {
Ok(IfElse(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?))
}
JsonAccessIndex(box a, box b) => Ok(JsonAccessIndex(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?)),
}
}
@ -121,6 +124,11 @@ impl AttrValueExpr {
refs.append(&mut c.var_refs());
refs
}
JsonAccessIndex(box a, box b) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs
}
}
}
@ -163,6 +171,25 @@ impl AttrValueExpr {
no.eval(values)
}
}
AttrValueExpr::JsonAccessIndex(val, index) => {
let val = val.eval(values)?;
let index = index.eval(values)?;
match val.as_json_value()? {
serde_json::Value::Array(val) => {
let index = index.as_i32()?;
let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null);
Ok(PrimitiveValue::from(indexed_value))
}
serde_json::Value::Object(val) => {
let indexed_value = val
.get(&index.as_string()?)
.or_else(|| val.get(&format!("{}", index.as_i32().ok()?)))
.unwrap_or(&serde_json::Value::Null);
Ok(PrimitiveValue::from(indexed_value))
}
_ => bail!("Unable to index into value {}", val),
}
}
}
}

View file

@ -70,12 +70,22 @@ fn parse_factor(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> {
))
}
fn parse_term3(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> {
fn parse_object_index(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> {
let (i, initial) = parse_factor(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("*"), parse_factor), |x| (BinOp::Times, x)),
map(preceded(tag("/"), parse_factor), |x| (BinOp::Div, x)),
map(preceded(tag("%"), parse_factor), |x| (BinOp::Mod, x)),
delimited(tag("["), ws(parse_expr), tag("]")),
map(preceded(tag("."), parse_identifier), |x| AttrValueExpr::Literal(AttrValue::from_primitive(x))),
)))(i)?;
let indexes = remainder.into_iter().fold(initial, |acc, index| AttrValueExpr::JsonAccessIndex(box acc, box index));
Ok((i, indexes))
}
fn parse_term3(i: &str) -> IResult<&str, AttrValueExpr, VerboseError<&str>> {
let (i, initial) = parse_object_index(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("*"), parse_object_index), |x| (BinOp::Times, x)),
map(preceded(tag("/"), parse_object_index), |x| (BinOp::Div, x)),
map(preceded(tag("%"), parse_object_index), |x| (BinOp::Mod, x)),
)))(i)?;
let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValueExpr::BinOp(box acc, op, box expr));
@ -172,6 +182,27 @@ mod test {
),
AttrValueExpr::parse("if a then b else c").unwrap()
);
assert_eq!(
AttrValueExpr::JsonAccessIndex(
box AttrValueExpr::VarRef(VarName("array".to_string())),
box AttrValueExpr::BinOp(
box AttrValueExpr::Literal(AttrValue::from_primitive("1")),
BinOp::Plus,
box AttrValueExpr::Literal(AttrValue::from_primitive("2"))
)
),
AttrValueExpr::parse(r#"(array)[1+2]"#).unwrap()
);
assert_eq!(
AttrValueExpr::JsonAccessIndex(
box AttrValueExpr::JsonAccessIndex(
box AttrValueExpr::VarRef(VarName("object".to_string())),
box AttrValueExpr::Literal(AttrValue::from_primitive("field".to_string())),
),
box AttrValueExpr::Literal(AttrValue::from_primitive("field2".to_string())),
),
AttrValueExpr::parse(r#"object.field.field2"#).unwrap()
);
}
#[test]
fn test_complex() {

View file

@ -77,6 +77,17 @@ impl From<&str> for PrimitiveValue {
}
}
impl From<&serde_json::Value> for PrimitiveValue {
fn from(v: &serde_json::Value) -> Self {
PrimitiveValue(
v.as_str()
.map(|x| x.to_string())
.or_else(|| serde_json::to_string(v).ok())
.unwrap_or_else(|| "<invalid json value>".to_string()),
)
}
}
impl PrimitiveValue {
pub fn from_string(s: String) -> Self {
PrimitiveValue(s)
@ -106,6 +117,11 @@ impl PrimitiveValue {
pub fn as_vec(&self) -> Result<Vec<String>> {
parse_vec(self.0.to_owned()).map_err(|e| anyhow!("Couldn't convert {:#?} to a vec: {}", &self, e))
}
pub fn as_json_value(&self) -> Result<serde_json::Value> {
serde_json::from_str::<serde_json::Value>(&self.0)
.with_context(|| format!("Couldn't convert {:#?} to a json object", &self))
}
}
fn parse_vec(a: String) -> Result<Vec<String>> {