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]
Unspecified,
Call(CallExpr),
Comprehension(Box<ComprehensionExpr>),
Ident(String),
List(ListExpr),
Literal(LiteralValue),
Map(MapExpr),
Select(SelectExpr),
Struct(StructExpr),
}
impl Expr {
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,
}
}
}
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 {
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;
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);
}
}
#[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"])
);
}
}