use super::FromJsonError;
use crate::ast::{self, BoundedDisplay, EntityUID};
use crate::entities::json::{
err::EscapeKind, err::JsonDeserializationError, err::JsonDeserializationErrorContext,
CedarValueJson, FnAndArg,
};
use crate::expr_builder::ExprBuilder;
use crate::extensions::Extensions;
use crate::jsonvalue::JsonValueWithNoDuplicateKeys;
use crate::parser::cst_to_ast;
use crate::parser::err::ParseErrors;
use crate::parser::Node;
use crate::parser::{cst, Loc};
use itertools::Itertools;
use serde::{de::Visitor, Deserialize, Serialize};
use serde_with::serde_as;
use smol_str::{SmolStr, ToSmolStr};
use std::collections::{btree_map, BTreeMap, HashMap};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum Expr {
ExprNoExt(ExprNoExt),
ExtFuncCall(ExtFuncCall),
}
impl<'de> Deserialize<'de> for Expr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ExprVisitor;
impl<'de> Visitor<'de> for ExprVisitor {
type Value = Expr;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("JSON object representing an expression")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let (k, v): (SmolStr, JsonValueWithNoDuplicateKeys) = match map.next_entry()? {
None => {
return Err(serde::de::Error::custom(
"empty map is not a valid expression",
))
}
Some((k, v)) => (k, v),
};
match map.next_key()? {
None => (),
Some(k2) => {
let k2: SmolStr = k2;
return Err(serde::de::Error::custom(format!("JSON object representing an `Expr` should have only one key, but found two keys: `{k}` and `{k2}`")));
}
};
if cst_to_ast::is_known_extension_func_str(&k) {
let obj = serde_json::json!({ k: v });
let extfunccall =
serde_json::from_value(obj).map_err(serde::de::Error::custom)?;
Ok(Expr::ExtFuncCall(extfunccall))
} else {
let obj = serde_json::json!({ k: v });
let exprnoext =
serde_json::from_value(obj).map_err(serde::de::Error::custom)?;
Ok(Expr::ExprNoExt(exprnoext))
}
}
}
deserializer.deserialize_map(ExprVisitor)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum PatternElem {
Wildcard,
Literal(SmolStr),
}
impl From<&[PatternElem]> for crate::ast::Pattern {
fn from(value: &[PatternElem]) -> Self {
let mut elems = Vec::new();
for elem in value {
match elem {
PatternElem::Wildcard => {
elems.push(crate::ast::PatternElem::Wildcard);
}
PatternElem::Literal(s) => {
elems.extend(s.chars().map(crate::ast::PatternElem::Char));
}
}
}
Self::from(elems)
}
}
impl From<crate::ast::PatternElem> for PatternElem {
fn from(value: crate::ast::PatternElem) -> Self {
match value {
crate::ast::PatternElem::Wildcard => Self::Wildcard,
crate::ast::PatternElem::Char(c) => Self::Literal(c.to_smolstr()),
}
}
}
impl From<crate::ast::Pattern> for Vec<PatternElem> {
fn from(value: crate::ast::Pattern) -> Self {
value.iter().map(|elem| (*elem).into()).collect()
}
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub enum ExprNoExt {
Value(CedarValueJson),
Var(ast::Var),
Slot(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::SlotId),
#[serde(rename = "!")]
Not {
arg: Arc<Expr>,
},
#[serde(rename = "neg")]
Neg {
arg: Arc<Expr>,
},
#[serde(rename = "==")]
Eq {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "!=")]
NotEq {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "in")]
In {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "<")]
Less {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "<=")]
LessEq {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = ">")]
Greater {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = ">=")]
GreaterEq {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "&&")]
And {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "||")]
Or {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "+")]
Add {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "-")]
Sub {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "*")]
Mul {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "contains")]
Contains {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "containsAll")]
ContainsAll {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "containsAny")]
ContainsAny {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "isEmpty")]
IsEmpty {
arg: Arc<Expr>,
},
#[serde(rename = "getTag")]
GetTag {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = "hasTag")]
HasTag {
left: Arc<Expr>,
right: Arc<Expr>,
},
#[serde(rename = ".")]
GetAttr {
left: Arc<Expr>,
attr: SmolStr,
},
#[serde(rename = "has")]
HasAttr {
left: Arc<Expr>,
attr: SmolStr,
},
#[serde(rename = "like")]
Like {
left: Arc<Expr>,
pattern: Vec<PatternElem>,
},
#[serde(rename = "is")]
Is {
left: Arc<Expr>,
entity_type: SmolStr,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "in")]
in_expr: Option<Arc<Expr>>,
},
#[serde(rename = "if-then-else")]
If {
#[serde(rename = "if")]
cond_expr: Arc<Expr>,
#[serde(rename = "then")]
then_expr: Arc<Expr>,
#[serde(rename = "else")]
else_expr: Arc<Expr>,
},
Set(Vec<Expr>),
Record(
#[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
#[cfg_attr(feature = "wasm", tsify(type = "Record<string, Expr>"))]
BTreeMap<SmolStr, Expr>,
),
}
#[serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct ExtFuncCall {
#[serde(flatten)]
#[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
#[cfg_attr(feature = "wasm", tsify(type = "Record<string, Array<Expr>>"))]
call: HashMap<SmolStr, Vec<Expr>>,
}
#[derive(Clone, Debug)]
pub struct Builder;
impl ExprBuilder for Builder {
type Expr = Expr;
type Data = ();
fn with_data(_data: Self::Data) -> Self {
Self
}
fn with_maybe_source_loc(self, _: Option<&Loc>) -> Self {
self
}
fn loc(&self) -> Option<&Loc> {
None
}
fn data(&self) -> &Self::Data {
&()
}
fn val(self, lit: impl Into<ast::Literal>) -> Expr {
Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::from_lit(lit.into())))
}
fn var(self, var: ast::Var) -> Expr {
Expr::ExprNoExt(ExprNoExt::Var(var))
}
fn slot(self, slot: ast::SlotId) -> Expr {
Expr::ExprNoExt(ExprNoExt::Slot(slot))
}
fn unknown(self, u: ast::Unknown) -> Expr {
Expr::ExtFuncCall(ExtFuncCall {
call: HashMap::from([("unknown".to_smolstr(), vec![Builder::new().val(u.name)])]),
})
}
fn not(self, e: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Not { arg: Arc::new(e) })
}
fn neg(self, e: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Neg { arg: Arc::new(e) })
}
fn is_eq(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Eq {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn noteq(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::NotEq {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn is_in(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::In {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn less(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Less {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn lesseq(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::LessEq {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn greater(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Greater {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn greatereq(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::GreaterEq {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn and(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::And {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn or(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Or {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn add(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Add {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn sub(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Sub {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn mul(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Mul {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn contains(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Contains {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn contains_all(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::ContainsAll {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn contains_any(self, left: Expr, right: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::ContainsAny {
left: Arc::new(left),
right: Arc::new(right),
})
}
fn is_empty(self, expr: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::IsEmpty {
arg: Arc::new(expr),
})
}
fn get_tag(self, expr: Expr, tag: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::GetTag {
left: Arc::new(expr),
right: Arc::new(tag),
})
}
fn has_tag(self, expr: Expr, tag: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::HasTag {
left: Arc::new(expr),
right: Arc::new(tag),
})
}
fn get_attr(self, expr: Expr, attr: SmolStr) -> Expr {
Expr::ExprNoExt(ExprNoExt::GetAttr {
left: Arc::new(expr),
attr,
})
}
fn has_attr(self, expr: Expr, attr: SmolStr) -> Expr {
Expr::ExprNoExt(ExprNoExt::HasAttr {
left: Arc::new(expr),
attr,
})
}
fn like(self, expr: Expr, pattern: ast::Pattern) -> Expr {
Expr::ExprNoExt(ExprNoExt::Like {
left: Arc::new(expr),
pattern: pattern.into(),
})
}
fn is_entity_type(self, left: Expr, entity_type: ast::EntityType) -> Expr {
Expr::ExprNoExt(ExprNoExt::Is {
left: Arc::new(left),
entity_type: entity_type.to_smolstr(),
in_expr: None,
})
}
fn is_in_entity_type(self, left: Expr, entity_type: ast::EntityType, entity: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::Is {
left: Arc::new(left),
entity_type: entity_type.to_smolstr(),
in_expr: Some(Arc::new(entity)),
})
}
fn ite(self, cond_expr: Expr, then_expr: Expr, else_expr: Expr) -> Expr {
Expr::ExprNoExt(ExprNoExt::If {
cond_expr: Arc::new(cond_expr),
then_expr: Arc::new(then_expr),
else_expr: Arc::new(else_expr),
})
}
fn set(self, elements: impl IntoIterator<Item = Expr>) -> Expr {
Expr::ExprNoExt(ExprNoExt::Set(elements.into_iter().collect()))
}
fn record(
self,
map: impl IntoIterator<Item = (SmolStr, Expr)>,
) -> Result<Expr, ast::ExpressionConstructionError> {
let mut dedup_map = BTreeMap::new();
for (k, v) in map {
match dedup_map.entry(k) {
btree_map::Entry::Occupied(oentry) => {
return Err(ast::expression_construction_errors::DuplicateKeyError {
key: oentry.key().clone(),
context: "in record literal",
}
.into());
}
btree_map::Entry::Vacant(ventry) => {
ventry.insert(v);
}
}
}
Ok(Expr::ExprNoExt(ExprNoExt::Record(dedup_map)))
}
fn call_extension_fn(self, fn_name: ast::Name, args: impl IntoIterator<Item = Expr>) -> Expr {
Expr::ExtFuncCall(ExtFuncCall {
call: HashMap::from([(fn_name.to_smolstr(), args.into_iter().collect())]),
})
}
}
impl Expr {
pub fn into_string_literal(self) -> Result<SmolStr, Self> {
match self {
Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::String(s))) => Ok(s),
_ => Err(self),
}
}
pub fn sub_entity_literals(
self,
mapping: &BTreeMap<EntityUID, EntityUID>,
) -> Result<Self, JsonDeserializationError> {
match self.clone() {
Expr::ExprNoExt(e) => match e {
ExprNoExt::Value(v) => Ok(Expr::ExprNoExt(ExprNoExt::Value(
v.sub_entity_literals(mapping)?,
))),
ExprNoExt::Var(_) => Ok(self),
ExprNoExt::Slot(_) => Ok(self),
ExprNoExt::Not { arg } => Ok(Expr::ExprNoExt(ExprNoExt::Not {
arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
})),
ExprNoExt::Neg { arg } => Ok(Expr::ExprNoExt(ExprNoExt::Neg {
arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
})),
ExprNoExt::Eq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Eq {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::NotEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::NotEq {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::In { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::In {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Less { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Less {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::LessEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::LessEq {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Greater { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Greater {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::GreaterEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::GreaterEq {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::And { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::And {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Or { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Or {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Add { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Add {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Sub { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Sub {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Mul { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Mul {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::Contains { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Contains {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::ContainsAll { left, right } => {
Ok(Expr::ExprNoExt(ExprNoExt::ContainsAll {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
}))
}
ExprNoExt::ContainsAny { left, right } => {
Ok(Expr::ExprNoExt(ExprNoExt::ContainsAny {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
}))
}
ExprNoExt::IsEmpty { arg } => Ok(Expr::ExprNoExt(ExprNoExt::IsEmpty {
arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
})),
ExprNoExt::GetTag { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::GetTag {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::HasTag { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::HasTag {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
})),
ExprNoExt::GetAttr { left, attr } => Ok(Expr::ExprNoExt(ExprNoExt::GetAttr {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
attr,
})),
ExprNoExt::HasAttr { left, attr } => Ok(Expr::ExprNoExt(ExprNoExt::HasAttr {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
attr,
})),
ExprNoExt::Like { left, pattern } => Ok(Expr::ExprNoExt(ExprNoExt::Like {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
pattern,
})),
ExprNoExt::Is {
left,
entity_type,
in_expr,
} => match in_expr {
Some(in_expr) => Ok(Expr::ExprNoExt(ExprNoExt::Is {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
entity_type,
in_expr: Some(Arc::new(
Arc::unwrap_or_clone(in_expr).sub_entity_literals(mapping)?,
)),
})),
None => Ok(Expr::ExprNoExt(ExprNoExt::Is {
left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
entity_type,
in_expr: None,
})),
},
ExprNoExt::If {
cond_expr,
then_expr,
else_expr,
} => Ok(Expr::ExprNoExt(ExprNoExt::If {
cond_expr: Arc::new(
Arc::unwrap_or_clone(cond_expr).sub_entity_literals(mapping)?,
),
then_expr: Arc::new(
Arc::unwrap_or_clone(then_expr).sub_entity_literals(mapping)?,
),
else_expr: Arc::new(
Arc::unwrap_or_clone(else_expr).sub_entity_literals(mapping)?,
),
})),
ExprNoExt::Set(v) => {
let mut new_v = vec![];
for e in v {
new_v.push(e.sub_entity_literals(mapping)?);
}
Ok(Expr::ExprNoExt(ExprNoExt::Set(new_v)))
}
ExprNoExt::Record(m) => {
let mut new_m = BTreeMap::new();
for (k, v) in m {
new_m.insert(k, v.sub_entity_literals(mapping)?);
}
Ok(Expr::ExprNoExt(ExprNoExt::Record(new_m)))
}
},
Expr::ExtFuncCall(e_fn_call) => {
let mut new_m = HashMap::new();
for (k, v) in e_fn_call.call {
let mut new_v = vec![];
for e in v {
new_v.push(e.sub_entity_literals(mapping)?);
}
new_m.insert(k, new_v);
}
Ok(Expr::ExtFuncCall(ExtFuncCall { call: new_m }))
}
}
}
}
impl Expr {
pub fn try_into_ast(self, id: ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
match self {
Expr::ExprNoExt(ExprNoExt::Value(jsonvalue)) => jsonvalue
.into_expr(|| JsonDeserializationErrorContext::Policy { id: id.clone() })
.map(Into::into)
.map_err(Into::into),
Expr::ExprNoExt(ExprNoExt::Var(var)) => Ok(ast::Expr::var(var)),
Expr::ExprNoExt(ExprNoExt::Slot(slot)) => Ok(ast::Expr::slot(slot)),
Expr::ExprNoExt(ExprNoExt::Not { arg }) => {
Ok(ast::Expr::not(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
}
Expr::ExprNoExt(ExprNoExt::Neg { arg }) => {
Ok(ast::Expr::neg(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
}
Expr::ExprNoExt(ExprNoExt::Eq { left, right }) => Ok(ast::Expr::is_eq(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::NotEq { left, right }) => Ok(ast::Expr::noteq(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::In { left, right }) => Ok(ast::Expr::is_in(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Less { left, right }) => Ok(ast::Expr::less(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::LessEq { left, right }) => Ok(ast::Expr::lesseq(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Greater { left, right }) => Ok(ast::Expr::greater(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::GreaterEq { left, right }) => Ok(ast::Expr::greatereq(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::And { left, right }) => Ok(ast::Expr::and(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Or { left, right }) => Ok(ast::Expr::or(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Add { left, right }) => Ok(ast::Expr::add(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Sub { left, right }) => Ok(ast::Expr::sub(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Mul { left, right }) => Ok(ast::Expr::mul(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Contains { left, right }) => Ok(ast::Expr::contains(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::ContainsAll { left, right }) => Ok(ast::Expr::contains_all(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::ContainsAny { left, right }) => Ok(ast::Expr::contains_any(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => Ok(ast::Expr::is_empty(
Arc::unwrap_or_clone(arg).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(ast::Expr::get_tag(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::HasTag { left, right }) => Ok(ast::Expr::has_tag(
Arc::unwrap_or_clone(left).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(right).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::GetAttr { left, attr }) => Ok(ast::Expr::get_attr(
Arc::unwrap_or_clone(left).try_into_ast(id)?,
attr,
)),
Expr::ExprNoExt(ExprNoExt::HasAttr { left, attr }) => Ok(ast::Expr::has_attr(
Arc::unwrap_or_clone(left).try_into_ast(id)?,
attr,
)),
Expr::ExprNoExt(ExprNoExt::Like { left, pattern }) => Ok(ast::Expr::like(
Arc::unwrap_or_clone(left).try_into_ast(id)?,
crate::ast::Pattern::from(pattern.as_slice()),
)),
Expr::ExprNoExt(ExprNoExt::Is {
left,
entity_type,
in_expr,
}) => ast::EntityType::from_normalized_str(entity_type.as_str())
.map_err(FromJsonError::InvalidEntityType)
.and_then(|entity_type_name| {
let left: ast::Expr = Arc::unwrap_or_clone(left).try_into_ast(id.clone())?;
let is_expr = ast::Expr::is_entity_type(left.clone(), entity_type_name);
match in_expr {
Some(in_expr) => Ok(ast::Expr::and(
is_expr,
ast::Expr::is_in(left, Arc::unwrap_or_clone(in_expr).try_into_ast(id)?),
)),
None => Ok(is_expr),
}
}),
Expr::ExprNoExt(ExprNoExt::If {
cond_expr,
then_expr,
else_expr,
}) => Ok(ast::Expr::ite(
Arc::unwrap_or_clone(cond_expr).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(then_expr).try_into_ast(id.clone())?,
Arc::unwrap_or_clone(else_expr).try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::Set(elements)) => Ok(ast::Expr::set(
elements
.into_iter()
.map(|el| el.try_into_ast(id.clone()))
.collect::<Result<Vec<_>, FromJsonError>>()?,
)),
Expr::ExprNoExt(ExprNoExt::Record(map)) => {
#[allow(clippy::expect_used)]
Ok(ast::Expr::record(
map.into_iter()
.map(|(k, v)| Ok((k, v.try_into_ast(id.clone())?)))
.collect::<Result<HashMap<SmolStr, _>, FromJsonError>>()?,
)
.expect("can't have duplicate keys here because the input was already a HashMap"))
}
Expr::ExtFuncCall(ExtFuncCall { call }) => {
match call.len() {
0 => Err(FromJsonError::MissingOperator),
1 => {
#[allow(clippy::expect_used)]
let (fn_name, args) = call
.into_iter()
.next()
.expect("already checked that len was 1");
let fn_name: ast::Name = fn_name.parse().map_err(|errs| {
JsonDeserializationError::parse_escape(
EscapeKind::Extension,
fn_name,
errs,
)
})?;
if !cst_to_ast::is_known_extension_func_name(&fn_name) {
return Err(FromJsonError::UnknownExtensionFunction(fn_name));
}
Ok(ast::Expr::call_extension_fn(
fn_name,
args.into_iter()
.map(|arg| arg.try_into_ast(id.clone()))
.collect::<Result<_, _>>()?,
))
}
_ => Err(FromJsonError::MultipleOperators {
ops: call.into_keys().collect(),
}),
}
}
}
}
}
#[allow(clippy::fallible_impl_from)]
impl<T: Clone> From<ast::Expr<T>> for Expr {
fn from(expr: ast::Expr<T>) -> Expr {
match expr.into_expr_kind() {
ast::ExprKind::Lit(lit) => lit.into(),
ast::ExprKind::Var(var) => var.into(),
ast::ExprKind::Slot(slot) => slot.into(),
ast::ExprKind::Unknown(u) => Builder::new().unknown(u),
ast::ExprKind::If {
test_expr,
then_expr,
else_expr,
} => Builder::new().ite(
Arc::unwrap_or_clone(test_expr).into(),
Arc::unwrap_or_clone(then_expr).into(),
Arc::unwrap_or_clone(else_expr).into(),
),
ast::ExprKind::And { left, right } => Builder::new().and(
Arc::unwrap_or_clone(left).into(),
Arc::unwrap_or_clone(right).into(),
),
ast::ExprKind::Or { left, right } => Builder::new().or(
Arc::unwrap_or_clone(left).into(),
Arc::unwrap_or_clone(right).into(),
),
ast::ExprKind::UnaryApp { op, arg } => {
let arg = Arc::unwrap_or_clone(arg).into();
Builder::new().unary_app(op, arg)
}
ast::ExprKind::BinaryApp { op, arg1, arg2 } => {
let arg1 = Arc::unwrap_or_clone(arg1).into();
let arg2 = Arc::unwrap_or_clone(arg2).into();
Builder::new().binary_app(op, arg1, arg2)
}
ast::ExprKind::ExtensionFunctionApp { fn_name, args } => {
let args = Arc::unwrap_or_clone(args).into_iter().map(Into::into);
Builder::new().call_extension_fn(fn_name, args)
}
ast::ExprKind::GetAttr { expr, attr } => {
Builder::new().get_attr(Arc::unwrap_or_clone(expr).into(), attr)
}
ast::ExprKind::HasAttr { expr, attr } => {
Builder::new().has_attr(Arc::unwrap_or_clone(expr).into(), attr)
}
ast::ExprKind::Like { expr, pattern } => {
Builder::new().like(Arc::unwrap_or_clone(expr).into(), pattern)
}
ast::ExprKind::Is { expr, entity_type } => {
Builder::new().is_entity_type(Arc::unwrap_or_clone(expr).into(), entity_type)
}
ast::ExprKind::Set(set) => {
Builder::new().set(Arc::unwrap_or_clone(set).into_iter().map(Into::into))
}
#[allow(clippy::unwrap_used)]
ast::ExprKind::Record(map) => Builder::new()
.record(
Arc::unwrap_or_clone(map)
.into_iter()
.map(|(k, v)| (k, v.into())),
)
.unwrap(),
}
}
}
impl From<ast::Literal> for Expr {
fn from(lit: ast::Literal) -> Expr {
Builder::new().val(lit)
}
}
impl From<ast::Var> for Expr {
fn from(var: ast::Var) -> Expr {
Builder::new().var(var)
}
}
impl From<ast::SlotId> for Expr {
fn from(slot: ast::SlotId) -> Expr {
Builder::new().slot(slot)
}
}
impl TryFrom<&Node<Option<cst::Expr>>> for Expr {
type Error = ParseErrors;
fn try_from(e: &Node<Option<cst::Expr>>) -> Result<Expr, ParseErrors> {
e.to_expr::<Builder>()
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ExprNoExt(e) => write!(f, "{e}"),
Self::ExtFuncCall(e) => write!(f, "{e}"),
}
}
}
impl BoundedDisplay for Expr {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
match self {
Self::ExprNoExt(e) => BoundedDisplay::fmt(e, f, n),
Self::ExtFuncCall(e) => BoundedDisplay::fmt(e, f, n),
}
}
}
fn display_cedarvaluejson(
f: &mut impl std::fmt::Write,
v: &CedarValueJson,
n: Option<usize>,
) -> std::fmt::Result {
match v {
CedarValueJson::Long(i) if *i < 0 => write!(f, "({i})"),
CedarValueJson::Long(i) => write!(f, "{i}"),
CedarValueJson::Bool(b) => write!(f, "{b}"),
CedarValueJson::String(s) => write!(f, "\"{}\"", s.escape_debug()),
CedarValueJson::EntityEscape { __entity } => {
match ast::EntityUID::try_from(__entity.clone()) {
Ok(euid) => write!(f, "{euid}"),
Err(e) => write!(f, "(invalid entity uid: {})", e),
}
}
CedarValueJson::ExprEscape { __expr } => write!(f, "({__expr})"),
CedarValueJson::ExtnEscape {
__extn: FnAndArg { ext_fn, arg },
} => {
let style = Extensions::all_available().all_funcs().find_map(|f| {
if &f.name().to_string() == ext_fn {
Some(f.style())
} else {
None
}
});
match style {
Some(ast::CallStyle::MethodStyle) => {
display_cedarvaluejson(f, arg, n)?;
write!(f, ".{ext_fn}()")?;
Ok(())
}
Some(ast::CallStyle::FunctionStyle) | None => {
write!(f, "{ext_fn}(")?;
display_cedarvaluejson(f, arg, n)?;
write!(f, ")")?;
Ok(())
}
}
}
CedarValueJson::Set(v) => {
match n {
Some(n) if v.len() > n => {
write!(f, "[")?;
for val in v.iter().take(n) {
display_cedarvaluejson(f, val, Some(n))?;
write!(f, ", ")?;
}
write!(f, "..]")?;
Ok(())
}
_ => {
write!(f, "[")?;
for (i, val) in v.iter().enumerate() {
display_cedarvaluejson(f, val, n)?;
if i < v.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
Ok(())
}
}
}
CedarValueJson::Record(r) => {
match n {
Some(n) if r.len() > n => {
write!(f, "{{")?;
for (k, v) in r.iter().take(n) {
write!(f, "\"{}\": ", k.escape_debug())?;
display_cedarvaluejson(f, v, Some(n))?;
write!(f, ", ")?;
}
write!(f, "..}}")?;
Ok(())
}
_ => {
write!(f, "{{")?;
for (i, (k, v)) in r.iter().enumerate() {
write!(f, "\"{}\": ", k.escape_debug())?;
display_cedarvaluejson(f, v, n)?;
if i < r.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")?;
Ok(())
}
}
}
CedarValueJson::Null => {
write!(f, "null")?;
Ok(())
}
}
}
impl std::fmt::Display for ExprNoExt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
BoundedDisplay::fmt_unbounded(self, f)
}
}
impl BoundedDisplay for ExprNoExt {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
match &self {
ExprNoExt::Value(v) => display_cedarvaluejson(f, v, n),
ExprNoExt::Var(v) => write!(f, "{v}"),
ExprNoExt::Slot(id) => write!(f, "{id}"),
ExprNoExt::Not { arg } => {
write!(f, "!")?;
maybe_with_parens(f, arg, n)
}
ExprNoExt::Neg { arg } => {
write!(f, "-({arg})")
}
ExprNoExt::Eq { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " == ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::NotEq { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " != ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::In { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " in ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Less { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " < ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::LessEq { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " <= ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Greater { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " > ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::GreaterEq { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " >= ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::And { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " && ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Or { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " || ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Add { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " + ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Sub { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " - ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Mul { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, " * ")?;
maybe_with_parens(f, right, n)
}
ExprNoExt::Contains { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, ".contains({right})")
}
ExprNoExt::ContainsAll { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, ".containsAll({right})")
}
ExprNoExt::ContainsAny { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, ".containsAny({right})")
}
ExprNoExt::IsEmpty { arg } => {
maybe_with_parens(f, arg, n)?;
write!(f, ".isEmpty()")
}
ExprNoExt::GetTag { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, ".getTag({right})")
}
ExprNoExt::HasTag { left, right } => {
maybe_with_parens(f, left, n)?;
write!(f, ".hasTag({right})")
}
ExprNoExt::GetAttr { left, attr } => {
maybe_with_parens(f, left, n)?;
write!(f, "[\"{}\"]", attr.escape_debug())
}
ExprNoExt::HasAttr { left, attr } => {
maybe_with_parens(f, left, n)?;
write!(f, " has \"{}\"", attr.escape_debug())
}
ExprNoExt::Like { left, pattern } => {
maybe_with_parens(f, left, n)?;
write!(
f,
" like \"{}\"",
crate::ast::Pattern::from(pattern.as_slice())
)
}
ExprNoExt::Is {
left,
entity_type,
in_expr,
} => {
maybe_with_parens(f, left, n)?;
write!(f, " is {entity_type}")?;
match in_expr {
Some(in_expr) => {
write!(f, " in ")?;
maybe_with_parens(f, in_expr, n)
}
None => Ok(()),
}
}
ExprNoExt::If {
cond_expr,
then_expr,
else_expr,
} => {
write!(f, "if ")?;
maybe_with_parens(f, cond_expr, n)?;
write!(f, " then ")?;
maybe_with_parens(f, then_expr, n)?;
write!(f, " else ")?;
maybe_with_parens(f, else_expr, n)
}
ExprNoExt::Set(v) => {
match n {
Some(n) if v.len() > n => {
write!(f, "[")?;
for element in v.iter().take(n) {
BoundedDisplay::fmt(element, f, Some(n))?;
write!(f, ", ")?;
}
write!(f, "..]")?;
Ok(())
}
_ => {
write!(f, "[")?;
for (i, element) in v.iter().enumerate() {
BoundedDisplay::fmt(element, f, n)?;
if i < v.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")?;
Ok(())
}
}
}
ExprNoExt::Record(m) => {
match n {
Some(n) if m.len() > n => {
write!(f, "{{")?;
for (k, v) in m.iter().take(n) {
write!(f, "\"{}\": ", k.escape_debug())?;
BoundedDisplay::fmt(v, f, Some(n))?;
write!(f, ", ")?;
}
write!(f, "..}}")?;
Ok(())
}
_ => {
write!(f, "{{")?;
for (i, (k, v)) in m.iter().enumerate() {
write!(f, "\"{}\": ", k.escape_debug())?;
BoundedDisplay::fmt(v, f, n)?;
if i < m.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "}}")?;
Ok(())
}
}
}
}
}
}
impl std::fmt::Display for ExtFuncCall {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
BoundedDisplay::fmt_unbounded(self, f)
}
}
impl BoundedDisplay for ExtFuncCall {
fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
#[allow(clippy::unreachable)]
let Some((fn_name, args)) = self.call.iter().next() else {
unreachable!("invariant violated: empty ExtFuncCall")
};
let style = Extensions::all_available().all_funcs().find_map(|ext_fn| {
if &ext_fn.name().to_string() == fn_name {
Some(ext_fn.style())
} else {
None
}
});
match (style, args.iter().next()) {
(Some(ast::CallStyle::MethodStyle), Some(receiver)) => {
maybe_with_parens(f, receiver, n)?;
write!(f, ".{}({})", fn_name, args.iter().skip(1).join(", "))
}
(_, _) => {
write!(f, "{}({})", fn_name, args.iter().join(", "))
}
}
}
}
fn maybe_with_parens(
f: &mut impl std::fmt::Write,
expr: &Expr,
n: Option<usize>,
) -> std::fmt::Result {
match expr {
Expr::ExprNoExt(ExprNoExt::Set(_)) |
Expr::ExprNoExt(ExprNoExt::Record(_)) |
Expr::ExprNoExt(ExprNoExt::Value(_)) |
Expr::ExprNoExt(ExprNoExt::Var(_)) |
Expr::ExprNoExt(ExprNoExt::Slot(_)) => BoundedDisplay::fmt(expr, f, n),
Expr::ExprNoExt(ExprNoExt::Not { .. }) |
Expr::ExprNoExt(ExprNoExt::Neg { .. }) |
Expr::ExprNoExt(ExprNoExt::Eq { .. }) |
Expr::ExprNoExt(ExprNoExt::NotEq { .. }) |
Expr::ExprNoExt(ExprNoExt::In { .. }) |
Expr::ExprNoExt(ExprNoExt::Less { .. }) |
Expr::ExprNoExt(ExprNoExt::LessEq { .. }) |
Expr::ExprNoExt(ExprNoExt::Greater { .. }) |
Expr::ExprNoExt(ExprNoExt::GreaterEq { .. }) |
Expr::ExprNoExt(ExprNoExt::And { .. }) |
Expr::ExprNoExt(ExprNoExt::Or { .. }) |
Expr::ExprNoExt(ExprNoExt::Add { .. }) |
Expr::ExprNoExt(ExprNoExt::Sub { .. }) |
Expr::ExprNoExt(ExprNoExt::Mul { .. }) |
Expr::ExprNoExt(ExprNoExt::Contains { .. }) |
Expr::ExprNoExt(ExprNoExt::ContainsAll { .. }) |
Expr::ExprNoExt(ExprNoExt::ContainsAny { .. }) |
Expr::ExprNoExt(ExprNoExt::IsEmpty { .. }) |
Expr::ExprNoExt(ExprNoExt::GetAttr { .. }) |
Expr::ExprNoExt(ExprNoExt::HasAttr { .. }) |
Expr::ExprNoExt(ExprNoExt::GetTag { .. }) |
Expr::ExprNoExt(ExprNoExt::HasTag { .. }) |
Expr::ExprNoExt(ExprNoExt::Like { .. }) |
Expr::ExprNoExt(ExprNoExt::Is { .. }) |
Expr::ExprNoExt(ExprNoExt::If { .. }) |
Expr::ExtFuncCall { .. } => {
write!(f, "(")?;
BoundedDisplay::fmt(expr, f, n)?;
write!(f, ")")?;
Ok(())
}
}
}
#[cfg(test)]
#[allow(clippy::indexing_slicing)]
#[allow(clippy::panic)]
mod test {
use crate::parser::{
err::{ParseError, ToASTErrorKind},
parse_expr,
};
use super::*;
use ast::BoundedToString;
use cool_asserts::assert_matches;
#[test]
fn test_invalid_expr_from_cst_name() {
let e = crate::parser::text_to_cst::parse_expr("some_long_str::else").unwrap();
assert_matches!(Expr::try_from(&e), Err(e) => {
assert!(e.len() == 1);
assert_matches!(&e[0],
ParseError::ToAST(to_ast_error) => {
assert_matches!(to_ast_error.kind(), ToASTErrorKind::ReservedIdentifier(s) => {
assert_eq!(s.to_string(), "else");
});
}
);
});
}
#[test]
fn display_and_bounded_display() {
let expr = Expr::from(parse_expr(r#"[100, [3, 4, 5], -20, "foo"]"#).unwrap());
assert_eq!(format!("{expr}"), r#"[100, [3, 4, 5], (-20), "foo"]"#);
assert_eq!(
BoundedToString::to_string(&expr, None),
r#"[100, [3, 4, 5], (-20), "foo"]"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(4)),
r#"[100, [3, 4, 5], (-20), "foo"]"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(3)),
r#"[100, [3, 4, 5], (-20), ..]"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(2)),
r#"[100, [3, 4, ..], ..]"#
);
assert_eq!(BoundedToString::to_string(&expr, Some(1)), r#"[100, ..]"#);
assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"[..]"#);
let expr = Expr::from(
parse_expr(
r#"{
a: 12,
b: [3, 4, true],
c: -20,
"hello ∞ world": "∂µß≈¥"
}"#,
)
.unwrap(),
);
assert_eq!(
format!("{expr}"),
r#"{"a": 12, "b": [3, 4, true], "c": (-20), "hello ∞ world": "∂µß≈¥"}"#
);
assert_eq!(
BoundedToString::to_string(&expr, None),
r#"{"a": 12, "b": [3, 4, true], "c": (-20), "hello ∞ world": "∂µß≈¥"}"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(4)),
r#"{"a": 12, "b": [3, 4, true], "c": (-20), "hello ∞ world": "∂µß≈¥"}"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(3)),
r#"{"a": 12, "b": [3, 4, true], "c": (-20), ..}"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(2)),
r#"{"a": 12, "b": [3, 4, ..], ..}"#
);
assert_eq!(
BoundedToString::to_string(&expr, Some(1)),
r#"{"a": 12, ..}"#
);
assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"{..}"#);
}
}