Skip to main content

ottl/parser/
ast.rs

1//! AST type definitions for OTTL parser
2//!
3//! Contains both arena-based types (for efficient evaluation) and
4//! original boxed types (used during parsing).
5
6use std::sync::Arc;
7
8use crate::{CallbackFn, EvalContextFamily, PathAccessor, Value};
9
10// =====================================================================================================================
11// Shared Types
12// =====================================================================================================================
13
14/// Comparison operators
15#[derive(Debug, Clone, PartialEq, Copy)]
16pub enum CompOp {
17    Eq,
18    NotEq,
19    Less,
20    Greater,
21    LessEq,
22    GreaterEq,
23}
24
25/// Math operators
26#[derive(Debug, Clone, Copy)]
27pub enum MathOp {
28    Add,
29    Sub,
30    Mul,
31    Div,
32}
33
34/// Index for path or converter result
35#[derive(Debug, Clone)]
36pub enum IndexExpr {
37    /// String index like ["key"]
38    String(String),
39    /// Integer index like [0]
40    Int(usize),
41}
42
43/// A single field in a path expression.
44///
45/// Each field has a name and zero or more index keys. For example, in the path
46/// `resource.attributes["key"]`, there are two fields: `Field { name: "resource", keys: [] }`
47/// and `Field { name: "attributes", keys: [IndexExpr::String("key")] }`.
48///
49/// Pre-allocated at parse time and stored in [`ResolvedPath`]; on the hot path the evaluator
50/// passes `&[Field]` by reference with zero allocation and zero copy.
51#[derive(Debug, Clone)]
52pub struct Field {
53    /// Identifier for this field segment.
54    pub name: String,
55    /// Index keys attached to this field (e.g. `["key"]`, `[0]`).
56    pub keys: Vec<IndexExpr>,
57}
58
59// =====================================================================================================================
60// Arena-based AST Types (used during evaluation - cache-friendly)
61// we are using u32 as index on purpose: usize is considered as too fat
62// and cache locality will be better for 32 bits.
63// 16 bits are considered as too small for generated AST (test purposes)
64// =====================================================================================================================
65
66/// Index into the arena for BoolExpr nodes
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct BoolExprRef(pub(crate) u32);
69
70/// Index into the arena for MathExpr nodes
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct MathExprRef(pub(crate) u32);
73
74/// Index into the arena for ValueExpr nodes
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct ValueExprRef(pub(crate) u32);
77
78/// Index into the arena for FunctionCall nodes
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct FunctionCallRef(pub(crate) u32);
81
82/// Resolved path with pre-allocated fields and accessor (resolved at parse time).
83///
84/// The type parameter `F` is the [`EvalContextFamily`] that determines the context type
85/// used by the accessor.
86///
87/// All data is allocated once during parsing. At execution time the evaluator passes
88/// `&self.fields` by reference -- zero allocation, zero copy.
89pub struct ResolvedPath<F: EvalContextFamily> {
90    /// Structured path fields, each carrying its own index keys.
91    pub fields: Vec<Field>,
92    /// Pre-resolved accessor (resolved once at parse time, not at each execution)
93    pub accessor: Arc<dyn PathAccessor<F>>,
94}
95
96impl<F: EvalContextFamily> Clone for ResolvedPath<F> {
97    fn clone(&self) -> Self {
98        Self {
99            fields: self.fields.clone(),
100            accessor: self.accessor.clone(),
101        }
102    }
103}
104
105impl<F: EvalContextFamily> std::fmt::Debug for ResolvedPath<F> {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        f.debug_struct("ResolvedPath").field("fields", &self.fields).finish()
108    }
109}
110
111/// Arena-based BoolExpr using indices instead of Box
112#[derive(Debug)]
113pub enum ArenaBoolExpr<F: EvalContextFamily> {
114    Literal(bool),
115    Comparison {
116        left: ValueExprRef,
117        op: CompOp,
118        right: ValueExprRef,
119    },
120    Converter(FunctionCallRef),
121    /// Path with pre-resolved accessor
122    Path(ResolvedPath<F>),
123    Not(BoolExprRef),
124    And(BoolExprRef, BoolExprRef),
125    Or(BoolExprRef, BoolExprRef),
126}
127
128impl<F: EvalContextFamily> Clone for ArenaBoolExpr<F> {
129    fn clone(&self) -> Self {
130        match self {
131            Self::Literal(b) => Self::Literal(*b),
132            Self::Comparison { left, op, right } => Self::Comparison {
133                left: *left,
134                op: *op,
135                right: *right,
136            },
137            Self::Converter(r) => Self::Converter(*r),
138            Self::Path(p) => Self::Path(p.clone()),
139            Self::Not(r) => Self::Not(*r),
140            Self::And(l, r) => Self::And(*l, *r),
141            Self::Or(l, r) => Self::Or(*l, *r),
142        }
143    }
144}
145
146/// Arena-based MathExpr using indices instead of Box
147#[derive(Debug, Clone)]
148pub enum ArenaMathExpr {
149    Primary(ValueExprRef),
150    Negate(MathExprRef),
151    Binary {
152        left: MathExprRef,
153        op: MathOp,
154        right: MathExprRef,
155    },
156}
157
158/// Arena-based ValueExpr using indices instead of Box
159#[derive(Debug)]
160pub enum ArenaValueExpr<F: EvalContextFamily> {
161    Literal(Value),
162    /// Path with pre-resolved accessor (no runtime lookup!)
163    Path(ResolvedPath<F>),
164    List(Vec<ValueExprRef>),
165    Map(Vec<(String, ValueExprRef)>),
166    FunctionCall(FunctionCallRef),
167    Math(MathExprRef),
168}
169
170impl<F: EvalContextFamily> Clone for ArenaValueExpr<F> {
171    fn clone(&self) -> Self {
172        match self {
173            Self::Literal(v) => Self::Literal(v.clone()),
174            Self::Path(p) => Self::Path(p.clone()),
175            Self::List(l) => Self::List(l.clone()),
176            Self::Map(m) => Self::Map(m.clone()),
177            Self::FunctionCall(r) => Self::FunctionCall(*r),
178            Self::Math(r) => Self::Math(*r),
179        }
180    }
181}
182
183/// Arena-based FunctionCall using indices for args
184pub struct ArenaFunctionCall {
185    pub name: String,
186    pub is_editor: bool,
187    pub args: Vec<ArenaArgExpr>,
188    pub indexes: Vec<IndexExpr>,
189    pub callback: Option<CallbackFn>,
190}
191
192impl Clone for ArenaFunctionCall {
193    fn clone(&self) -> Self {
194        Self {
195            name: self.name.clone(),
196            is_editor: self.is_editor,
197            args: self.args.clone(),
198            indexes: self.indexes.clone(),
199            callback: self.callback.clone(),
200        }
201    }
202}
203
204impl std::fmt::Debug for ArenaFunctionCall {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        f.debug_struct("ArenaFunctionCall")
207            .field("name", &self.name)
208            .field("is_editor", &self.is_editor)
209            .field("args", &self.args)
210            .field("indexes", &self.indexes)
211            .field("callback", &self.callback.is_some())
212            .finish()
213    }
214}
215
216/// Arena-based ArgExpr
217#[derive(Debug, Clone)]
218pub enum ArenaArgExpr {
219    Positional(ValueExprRef),
220    Named { name: String, value: ValueExprRef },
221}
222
223/// Arena-based EditorStatement
224#[derive(Debug, Clone)]
225pub struct ArenaEditorStatement {
226    pub editor: FunctionCallRef,
227    pub condition: Option<BoolExprRef>,
228}
229
230/// Arena-based root expression
231#[derive(Debug, Clone)]
232pub enum ArenaRootExpr {
233    EditorStatement(ArenaEditorStatement),
234    BooleanExpression(BoolExprRef),
235    MathExpression(MathExprRef),
236}
237
238// =====================================================================================================================
239// Original AST Node Types (used during parsing, then converted to arena)
240// =====================================================================================================================
241
242/// Path expression (e.g., `resource.attributes["key"]`).
243///
244/// Each dot-separated segment is a [`Field`] with its own index keys.
245#[derive(Debug, Clone)]
246pub struct PathExpr {
247    /// Ordered fields of the path (e.g., `[Field("resource", []), Field("attributes", ["key"])]`).
248    pub fields: Vec<Field>,
249}
250
251/// Function invocation (Editor or Converter)
252#[derive(Clone)]
253pub struct FunctionCall {
254    /// Function name
255    pub name: String,
256    /// Whether this is an editor (lowercase) or converter (uppercase)
257    pub is_editor: bool,
258    /// Arguments
259    pub args: Vec<ArgExpr>,
260    /// Optional indexes (for converters)
261    pub indexes: Vec<IndexExpr>,
262    /// Callback reference (resolved at parse time)
263    pub callback: Option<CallbackFn>,
264}
265
266impl std::fmt::Debug for FunctionCall {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        f.debug_struct("FunctionCall")
269            .field("name", &self.name)
270            .field("is_editor", &self.is_editor)
271            .field("args", &self.args)
272            .field("indexes", &self.indexes)
273            .field("callback", &self.callback.is_some())
274            .finish()
275    }
276}
277
278/// Argument expression
279#[derive(Debug, Clone)]
280pub enum ArgExpr {
281    /// Positional argument
282    Positional(ValueExpr),
283    /// Named argument
284    Named { name: String, value: ValueExpr },
285}
286
287/// Value expression - any value in OTTL
288#[derive(Debug, Clone)]
289pub enum ValueExpr {
290    /// Literal value
291    Literal(Value),
292    /// Path expression
293    Path(PathExpr),
294    /// List literal
295    List(Vec<ValueExpr>),
296    /// Map literal
297    Map(Vec<(String, ValueExpr)>),
298    /// Function call (converter or editor)
299    FunctionCall(Box<FunctionCall>),
300    /// Math expression
301    Math(Box<MathExpr>),
302}
303
304/// Math expression with operator precedence
305#[derive(Debug, Clone)]
306pub enum MathExpr {
307    /// Primary value (literal, path, converter, or grouped expression)
308    Primary(ValueExpr),
309    /// Unary negation
310    Negate(Box<MathExpr>),
311    /// Binary operation: term (+/-) or factor (*/)
312    Binary {
313        left: Box<MathExpr>,
314        op: MathOp,
315        right: Box<MathExpr>,
316    },
317}
318
319/// Boolean expression with operator precedence
320#[derive(Debug, Clone)]
321pub enum BoolExpr {
322    /// Literal boolean
323    Literal(bool),
324    /// Comparison expression
325    Comparison {
326        left: ValueExpr,
327        op: CompOp,
328        right: ValueExpr,
329    },
330    /// Converter call returning boolean
331    Converter(Box<FunctionCall>),
332    /// Path that evaluates to boolean
333    Path(PathExpr),
334    /// Logical NOT
335    Not(Box<BoolExpr>),
336    /// Logical AND
337    And(Box<BoolExpr>, Box<BoolExpr>),
338    /// Logical OR
339    Or(Box<BoolExpr>, Box<BoolExpr>),
340}
341
342/// Editor invocation statement
343#[derive(Debug, Clone)]
344pub struct EditorStatement {
345    /// The editor function call
346    pub editor: FunctionCall,
347    /// Optional WHERE clause condition
348    pub condition: Option<BoolExpr>,
349}
350
351/// Root AST node - either an editor statement, a boolean expression, or a math expression
352#[derive(Debug, Clone)]
353pub enum RootExpr {
354    /// Editor invocation with optional WHERE clause
355    EditorStatement(EditorStatement),
356    /// Standalone boolean expression
357    BooleanExpression(BoolExpr),
358    /// Standalone math expression
359    MathExpression(MathExpr),
360}