Skip to main content

ottl/parser/
mod.rs

1//! OTTL Parser Implementation
2//!
3//! This module is split into submodules for maintainability:
4//! - `ast`: AST type definitions (arena-based and boxed)
5//! - `arena`: Arena storage and AST conversion with constant folding
6//! - `grammar`: Chumsky parser
7//! - `eval`: AST evaluation functions
8
9mod arena;
10pub(crate) mod ast;
11mod eval;
12mod grammar;
13mod ops;
14
15use std::marker::PhantomData;
16
17use arena::{convert_to_arena, AstArena};
18// Re-export AST types that may be needed externally
19pub use ast::*;
20use eval::arena_evaluate_root;
21use grammar::build_parser;
22
23use crate::lexer::Token;
24use crate::{BoxError, CallbackMap, EnumMap, EvalContextFamily, OttlParser, PathResolverMap, Result, Value};
25
26// =====================================================================================================================
27// Parser Implementation
28// =====================================================================================================================
29
30/// OTTL Parser that parses input strings and produces executable objects.
31/// Paths are resolved at parse time (not at execution time) for maximum performance.
32///
33/// The type parameter `F` is the [`EvalContextFamily`] that determines the concrete
34/// evaluation context type used at execution time.
35pub struct Parser<F: EvalContextFamily> {
36    /// Arena-based AST for cache-friendly execution
37    arena: AstArena<F>,
38    /// Arena-based root expression
39    arena_root: Option<ArenaRootExpr>,
40    /// Parsing errors
41    errors: Vec<String>,
42    /// Marker for the context family type
43    _marker: PhantomData<F>,
44}
45
46impl<F: EvalContextFamily> Parser<F> {
47    /// Creates a new parser with the given configuration.
48    /// Each path that appears in the expression must have a corresponding entry in `path_resolvers`;
49    /// otherwise parsing fails with an error.
50    pub fn new(
51        editors_map: &CallbackMap, converters_map: &CallbackMap, enums_map: &EnumMap,
52        path_resolvers: &PathResolverMap<F>, expression: &str,
53    ) -> Self {
54        let mut parser = Parser::<F> {
55            arena: AstArena::new(),
56            arena_root: None,
57            errors: Vec::new(),
58            _marker: PhantomData,
59        };
60
61        // Tokenize the input
62        let tokens_with_spans = match crate::lexer::Lexer::collect_with_spans(expression) {
63            Ok(tokens) => tokens,
64            Err(e) => {
65                parser.errors.push(format!("Lexer error: {}", e));
66                return parser;
67            }
68        };
69
70        if tokens_with_spans.is_empty() && !expression.trim().is_empty() {
71            parser.errors.push("Lexer failed to tokenize input".into());
72            return parser;
73        }
74
75        // Extract just the tokens for parsing
76        let tokens: Vec<Token> = tokens_with_spans.into_iter().map(|(t, _)| t).collect();
77
78        // Build the chumsky parser
79        let chumsky_parser = build_parser(editors_map, converters_map, enums_map);
80
81        // Parse using slice input
82        use chumsky::Parser as ChumskyParser;
83        let result = chumsky_parser.parse(&tokens[..]);
84
85        match result.into_result() {
86            Ok(ast) => {
87                // Convert to arena-based AST for cache-friendly execution
88                // Path resolution happens HERE (once), not at each execution!
89                match convert_to_arena(&ast, &mut parser.arena, path_resolvers) {
90                    Ok(arena_root) => {
91                        parser.arena_root = Some(arena_root);
92                    }
93                    Err(e) => {
94                        parser.errors.push(format!("Path resolution error: {}", e));
95                    }
96                }
97            }
98            Err(errs) => {
99                for err in errs {
100                    parser.errors.push(format!("Parse error: {:?}", err));
101                }
102            }
103        }
104
105        parser
106    }
107}
108
109/// Implementation of the [`OttlParser`] trait for [`Parser`].
110impl<F: EvalContextFamily> OttlParser<F> for Parser<F> {
111    fn is_error(&self) -> Result<()> {
112        if self.errors.is_empty() {
113            Ok(())
114        } else {
115            Err(self.errors.join("; ").into())
116        }
117    }
118
119    fn execute<'a>(&self, ctx: &mut F::Context<'a>) -> Result<Value> {
120        let arena_root = self
121            .arena_root
122            .as_ref()
123            .ok_or_else(|| -> BoxError { "No AST available (parsing failed)".into() })?;
124
125        // No runtime path resolution - paths are pre-resolved at parse time!
126        arena_evaluate_root(arena_root, &self.arena, ctx)
127    }
128}