cel 0.15.0

A parser and interpreter for the Common Expression Language (CEL)
Documentation
use crate::common::types::{CelBool, CelBytes, CelDouble, CelInt, CelNull, CelString, CelUInt};
use crate::common::value::CowVal;
use std::collections::BTreeMap;

pub mod operators;

pub struct Ast {
    pub expr: IdedExpr,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub enum Expr {
    #[default]
    /// UnspecifiedExprKind represents an unset expression with no specified properties.
    Unspecified,

    /// CallKind represents a function call.
    Call(CallExpr),

    /// ComprehensionKind represents a comprehension expression generated by a macro.
    Comprehension(Box<ComprehensionExpr>),

    /// IdentKind represents a simple variable, constant, or type identifier.
    Ident(String),

    /// ListKind represents a list literal expression.
    List(ListExpr),

    /// LiteralKind represents a primitive scalar literal.
    Literal(LiteralValue),

    /// MapKind represents a map literal expression.
    Map(MapExpr),

    /// SelectKind represents a field selection expression.
    Select(SelectExpr),

    /// StructKind represents a struct literal expression.
    Struct(StructExpr),
}

impl Expr {
    /// The first segment of the qualified name this expression spells, see
    /// [`qualified_name_segments`](Self::qualified_name_segments), found
    /// without allocating: `a.b.c` is `a`.
    pub(crate) fn qualified_name_root(&self) -> Option<&str> {
        let mut expr = self;
        loop {
            match expr {
                Expr::Ident(name) => return Some(name),
                Expr::Select(select) if !select.test => expr = &select.operand.expr,
                _ => return None,
            }
        }
    }

    /// The segments of the qualified name this expression spells, root first:
    /// `a.b.c` is `["a", "b", "c"]`. Only an identifier, or field selections on
    /// one, spells a name: anything else, a presence test included, is `None`.
    pub(crate) fn qualified_name_segments(&self) -> Option<Vec<&str>> {
        let mut segments = Vec::new();
        let mut expr = self;
        loop {
            match expr {
                Expr::Ident(name) => {
                    segments.push(name.as_str());
                    segments.reverse();
                    return Some(segments);
                }
                Expr::Select(select) if !select.test => {
                    segments.push(select.field.as_str());
                    expr = &select.operand.expr;
                }
                _ => return None,
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum LiteralValue {
    Boolean(CelBool),
    Bytes(CelBytes<'static>),
    Double(CelDouble),
    Int(CelInt),
    Null,
    String(CelString<'static>),
    UInt(CelUInt),
}

static NULL: CelNull = CelNull;

impl LiteralValue {
    /// The literal as a value borrowed from the AST: no allocation.
    pub fn to_val<'b, 'v>(&'b self) -> CowVal<'b, 'v> {
        match self {
            LiteralValue::Boolean(b) => CowVal::Borrowed(b),
            LiteralValue::Bytes(b) => CowVal::Borrowed(b),
            LiteralValue::Double(f) => CowVal::Borrowed(f),
            LiteralValue::Int(i) => CowVal::Borrowed(i),
            LiteralValue::Null => CowVal::Borrowed(&NULL),
            LiteralValue::String(s) => CowVal::Borrowed(s),
            LiteralValue::UInt(ui) => CowVal::Borrowed(ui),
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum EntryExpr {
    StructField(StructFieldExpr),
    MapEntry(MapEntryExpr),
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct IdedExpr {
    pub id: u64,
    pub expr: Expr,
}

#[derive(Clone, Debug, PartialEq)]
pub struct IdedEntryExpr {
    pub id: u64,
    pub expr: EntryExpr,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct CallExpr {
    pub func_name: String,
    pub target: Option<Box<IdedExpr>>,
    pub args: Vec<IdedExpr>,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectExpr {
    pub operand: Box<IdedExpr>,
    pub field: String,
    pub test: bool,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct StructExpr {
    pub type_name: String,
    pub entries: Vec<IdedEntryExpr>,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct MapExpr {
    pub entries: Vec<IdedEntryExpr>,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct ListExpr {
    pub elements: Vec<IdedExpr>,
    pub optional_indices: Vec<usize>,
}

impl ListExpr {
    pub fn new(elements: Vec<IdedExpr>) -> Self {
        Self::new_with_optionals(elements, Vec::default())
    }

    pub fn new_with_optionals(elements: Vec<IdedExpr>, optional_indices: Vec<usize>) -> Self {
        Self {
            elements,
            optional_indices,
        }
    }
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct StructFieldExpr {
    pub field: String,
    pub value: IdedExpr,
    pub optional: bool,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct MapEntryExpr {
    pub key: IdedExpr,
    pub value: IdedExpr,
    pub optional: bool,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct ComprehensionExpr {
    pub iter_range: IdedExpr,
    pub iter_var: String,
    pub iter_var2: Option<String>,
    pub accu_var: String,
    pub accu_init: IdedExpr,
    pub loop_cond: IdedExpr,
    pub loop_step: IdedExpr,
    pub result: IdedExpr,
}

#[derive(Debug, Default)]
pub struct SourceInfo {
    offsets: BTreeMap<u64, OffsetRange>,
    pub source: String,
}

impl SourceInfo {
    pub fn add_offset(&mut self, id: u64, start: u32, stop: u32) {
        self.offsets.insert(id, OffsetRange { start, stop });
    }

    pub fn offset_for(&self, id: u64) -> Option<(u32, u32)> {
        self.offsets.get(&id).map(|range| (range.start, range.stop))
    }

    pub(crate) fn pos_for(&self, id: u64) -> Option<(isize, isize)> {
        match self.offset_for(id) {
            Some((start, _)) => {
                let start = start as isize;
                let mut offset = 0;
                let mut line = 0;
                for l in self.source.split_inclusive('\n') {
                    line += 1;
                    offset += l.len() as isize;
                    if start < offset {
                        return Some((line, start + (l.len() as isize) - offset + 1));
                    }
                }
                None
            }
            None => None,
        }
    }

    pub fn snippet(&self, line: isize) -> Option<&str> {
        self.source.lines().nth(line as usize)
    }
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct OffsetRange {
    pub start: u32,
    pub stop: u32,
}

#[cfg(test)]
mod tests {
    use super::IdedExpr;

    /// Checks `qualified_name_segments`, and `qualified_name_root`, against the
    /// AST of every parser backend.
    fn assert_segments(source: &str, expected: Option<&[&str]>) {
        let asts: Vec<(&str, IdedExpr)> = vec![
            (
                "parser",
                crate::parser::Parser::default()
                    .enable_ident_escape_syntax(true)
                    .enable_optional_syntax(true)
                    .parse(source)
                    .unwrap(),
            ),
            #[cfg(feature = "parser_pratt")]
            (
                "pratt_parser",
                crate::parser::PrattParser::default()
                    .enable_ident_escape_syntax(true)
                    .enable_optional_syntax(true)
                    .parse(source)
                    .unwrap(),
            ),
        ];
        for (backend, ast) in asts {
            assert_eq!(
                ast.expr.qualified_name_segments().as_deref(),
                expected,
                "{source} with {backend}"
            );
            assert_eq!(
                ast.expr.qualified_name_root(),
                expected.map(|segments| segments[0]),
                "{source} with {backend}"
            );
        }
    }

    #[test]
    fn an_identifier_is_a_one_segment_name() {
        assert_segments("a", Some(&["a"]));
    }

    #[test]
    fn field_selections_on_an_identifier_are_a_qualified_name() {
        assert_segments("a.b.c", Some(&["a", "b", "c"]));
        assert_segments(
            "google.protobuf.Duration",
            Some(&["google", "protobuf", "Duration"]),
        );
        assert_segments("a.`b-c`", Some(&["a", "b-c"]));
    }

    #[test]
    fn other_expressions_are_not_a_name() {
        for source in [
            "1",
            "a[0].b",
            "a.b()",
            "f().b",
            "a.?b",
            "(a + b).c",
            "{'b': 1}.b",
            "has(a.b)",
        ] {
            assert_segments(source, None);
        }
    }

    /// `has(a.b.c)` tests for `c` on the name `a.b`.
    #[test]
    fn the_operand_of_a_presence_test_can_be_a_name() {
        let ast = crate::parser::Parser::default()
            .parse("has(a.b.c)")
            .unwrap();
        let super::Expr::Select(select) = &ast.expr else {
            panic!("has() is a select, got {:?}", ast.expr);
        };
        assert!(select.test);
        assert_eq!(
            select.operand.expr.qualified_name_segments(),
            Some(vec!["a", "b"])
        );
    }
}