use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expr {
Literal(Value),
Variable(String),
Unary {
op: UnaryOp,
expr: Box<Expr>,
},
Binary {
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
},
Call {
name: String,
args: Vec<Expr>,
},
Access {
base: Box<Expr>,
segment: AccessSegment,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOp {
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOp {
Or,
And,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum AccessSegment {
Property(String),
Index(Box<Expr>),
Wildcard,
}
impl Expr {
pub fn collect_functions(&self, out: &mut Vec<String>) {
match self {
Expr::Call { name, args } => {
out.push(name.to_ascii_lowercase());
for arg in args {
arg.collect_functions(out);
}
}
Expr::Unary { expr, .. } => expr.collect_functions(out),
Expr::Binary { left, right, .. } => {
left.collect_functions(out);
right.collect_functions(out);
}
Expr::Access { base, segment } => {
base.collect_functions(out);
if let AccessSegment::Index(index) = segment {
index.collect_functions(out);
}
}
Expr::Literal(_) | Expr::Variable(_) => {}
}
}
pub fn collect_roots(&self, out: &mut Vec<String>) {
match self {
Expr::Variable(name) => out.push(name.clone()),
Expr::Call { args, .. } => {
for arg in args {
arg.collect_roots(out);
}
}
Expr::Unary { expr, .. } => expr.collect_roots(out),
Expr::Binary { left, right, .. } => {
left.collect_roots(out);
right.collect_roots(out);
}
Expr::Access { base, segment } => {
base.collect_roots(out);
if let AccessSegment::Index(index) = segment {
index.collect_roots(out);
}
}
Expr::Literal(_) => {}
}
}
pub fn contains_status_function(&self) -> bool {
match self {
Expr::Call { name, args } => {
matches!(
name.to_ascii_lowercase().as_str(),
"success" | "failure" | "cancelled" | "always"
) || args.iter().any(Expr::contains_status_function)
}
Expr::Unary { expr, .. } => expr.contains_status_function(),
Expr::Binary { left, right, .. } => {
left.contains_status_function() || right.contains_status_function()
}
Expr::Access { base, segment } => {
base.contains_status_function()
|| matches!(segment, AccessSegment::Index(index) if index.contains_status_function())
}
Expr::Literal(_) | Expr::Variable(_) => false,
}
}
}