use super::{BinaryOperator, CelType, ComprehensionOp, Expr, Literal, UnaryOperator};
pub trait Visitor<'ast>
where
Self: Sized,
{
fn visit_expr(&mut self, expr: &'ast Expr) {
walk_expr(self, expr); }
fn visit_literal(&mut self, _literal: &'ast Literal) {
}
fn visit_identifier(&mut self, _ident: &'ast str) {
}
fn visit_type(&mut self, _cel_type: &'ast CelType) {
}
fn visit_unary_op(&mut self, op: UnaryOperator, operand: &'ast Expr) {
walk_unary_op(self, op, operand);
}
fn visit_binary_op(&mut self, op: BinaryOperator, left: &'ast Expr, right: &'ast Expr) {
walk_binary_op(self, op, left, right);
}
fn visit_conditional(
&mut self,
cond: &'ast Expr,
true_branch: &'ast Expr,
false_branch: &'ast Expr,
) {
walk_conditional(self, cond, true_branch, false_branch);
}
fn visit_list(&mut self, elements: &'ast [Expr]) {
walk_list(self, elements);
}
fn visit_field_access(&mut self, base: &'ast Expr, field: &'ast str) {
walk_field_access(self, base, field);
}
fn visit_call(&mut self, target: &'ast Expr, args: &'ast [Expr]) {
walk_call(self, target, args);
}
fn visit_index(&mut self, base: &'ast Expr, index: &'ast Expr) {
walk_index(self, base, index);
}
fn visit_map_literal(&mut self, entries: &'ast [(Expr, Expr)]) {
walk_map_literal(self, entries);
}
fn visit_message_literal(&mut self, type_name: &'ast str, fields: &'ast [(String, Expr)]) {
walk_message_literal(self, type_name, fields);
}
fn visit_has(&mut self, target: &'ast Expr) {
walk_has(self, target);
}
fn visit_comprehension(
&mut self,
op: ComprehensionOp,
target: &'ast Expr,
iter_var: &'ast str,
predicate: &'ast Expr,
) {
walk_comprehension(self, op, target, iter_var, predicate);
}
fn visit_map(
&mut self,
target: &'ast Expr,
iter_var: &'ast str,
filter: Option<&'ast Expr>,
transform: &'ast Expr,
) {
walk_map(self, target, iter_var, filter, transform);
}
}
pub fn walk_expr<'ast, V: Visitor<'ast>>(visitor: &mut V, expr: &'ast Expr) {
match expr {
Expr::Literal(lit) => visitor.visit_literal(lit),
Expr::Identifier(s) => visitor.visit_identifier(s),
Expr::UnaryOp { op, operand } => visitor.visit_unary_op(*op, operand),
Expr::BinaryOp { op, left, right } => visitor.visit_binary_op(*op, left, right),
Expr::Conditional {
cond,
true_branch,
false_branch,
} => visitor.visit_conditional(cond, true_branch, false_branch),
Expr::List { elements } => visitor.visit_list(elements),
Expr::FieldAccess { base, field } => visitor.visit_field_access(base, field),
Expr::Call { target, args } => visitor.visit_call(target, args),
Expr::Index { base, index } => visitor.visit_index(base, index),
Expr::MapLiteral { entries } => visitor.visit_map_literal(entries),
Expr::MessageLiteral { type_name, fields } => {
visitor.visit_message_literal(type_name, fields)
}
Expr::Has { target } => visitor.visit_has(target),
Expr::Comprehension {
op,
target,
iter_var,
predicate,
} => visitor.visit_comprehension(*op, target, iter_var, predicate),
Expr::Map {
target,
iter_var,
filter,
transform,
} => visitor.visit_map(target, iter_var, filter.as_deref(), transform),
Expr::Type(cel_type) => visitor.visit_type(cel_type),
}
}
pub fn walk_unary_op<'ast, V: Visitor<'ast>>(
visitor: &mut V,
_op: UnaryOperator,
operand: &'ast Expr,
) {
visitor.visit_expr(operand);
}
pub fn walk_binary_op<'ast, V: Visitor<'ast>>(
visitor: &mut V,
_op: BinaryOperator,
left: &'ast Expr,
right: &'ast Expr,
) {
visitor.visit_expr(left);
visitor.visit_expr(right);
}
pub fn walk_conditional<'ast, V: Visitor<'ast>>(
visitor: &mut V,
cond: &'ast Expr,
true_branch: &'ast Expr,
false_branch: &'ast Expr,
) {
visitor.visit_expr(cond);
visitor.visit_expr(true_branch);
visitor.visit_expr(false_branch);
}
pub fn walk_list<'ast, V: Visitor<'ast>>(visitor: &mut V, elements: &'ast [Expr]) {
for element in elements {
visitor.visit_expr(element);
}
}
pub fn walk_field_access<'ast, V: Visitor<'ast>>(
visitor: &mut V,
base: &'ast Expr,
_field: &'ast str,
) {
visitor.visit_expr(base);
}
pub fn walk_call<'ast, V: Visitor<'ast>>(visitor: &mut V, target: &'ast Expr, args: &'ast [Expr]) {
visitor.visit_expr(target);
for arg in args {
visitor.visit_expr(arg);
}
}
pub fn walk_index<'ast, V: Visitor<'ast>>(visitor: &mut V, base: &'ast Expr, index: &'ast Expr) {
visitor.visit_expr(base);
visitor.visit_expr(index);
}
pub fn walk_map_literal<'ast, V: Visitor<'ast>>(visitor: &mut V, entries: &'ast [(Expr, Expr)]) {
for (key, value) in entries {
visitor.visit_expr(key);
visitor.visit_expr(value);
}
}
pub fn walk_message_literal<'ast, V: Visitor<'ast>>(
visitor: &mut V,
_type_name: &'ast str,
fields: &'ast [(String, Expr)],
) {
for (_name, value) in fields {
visitor.visit_expr(value);
}
}
pub fn walk_has<'ast, V: Visitor<'ast>>(visitor: &mut V, target: &'ast Expr) {
visitor.visit_expr(target);
}
pub fn walk_comprehension<'ast, V: Visitor<'ast>>(
visitor: &mut V,
_op: ComprehensionOp,
target: &'ast Expr,
_iter_var: &'ast str,
predicate: &'ast Expr,
) {
visitor.visit_expr(target);
visitor.visit_expr(predicate);
}
pub fn walk_map<'ast, V: Visitor<'ast>>(
visitor: &mut V,
target: &'ast Expr,
_iter_var: &'ast str,
filter: Option<&'ast Expr>,
transform: &'ast Expr,
) {
visitor.visit_expr(target);
if let Some(filter_expr) = filter {
visitor.visit_expr(filter_expr);
}
visitor.visit_expr(transform);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse_cel_program;
use std::collections::HashSet;
struct IdentifierCollector<'a> {
names: HashSet<&'a str>,
}
impl<'ast> Visitor<'ast> for IdentifierCollector<'ast> {
fn visit_identifier(&mut self, ident: &'ast str) {
self.names.insert(ident);
}
}
#[test]
fn test_identifier_collector() {
let ast = parse_cel_program("request.auth.user + params.id + request.time").unwrap();
let mut collector = IdentifierCollector {
names: HashSet::new(),
};
collector.visit_expr(&ast);
let expected: HashSet<&str> = ["request", "params"].iter().cloned().collect();
assert_eq!(collector.names, expected);
}
#[test]
fn test_find_specific_function_calls() {
let ast = parse_cel_program("size(list_a) + other_func(size(list_b))").unwrap();
struct SizeCallArgumentCollector<'a> {
size_args: Vec<&'a Expr>,
}
impl<'ast> Visitor<'ast> for SizeCallArgumentCollector<'ast> {
fn visit_call(&mut self, target: &'ast Expr, args: &'ast [Expr]) {
if let Some("size") = target.as_identifier() {
if !args.is_empty() {
self.size_args.push(&args[0]);
}
}
walk_call(self, target, args);
}
}
let mut collector = SizeCallArgumentCollector { size_args: vec![] };
ast.accept(&mut collector);
assert_eq!(collector.size_args.len(), 2);
assert_eq!(collector.size_args[0].as_identifier(), Some("list_a"));
assert_eq!(collector.size_args[1].as_identifier(), Some("list_b"));
}
}