Skip to main content

eventql_parser/
lib.rs

1//! EventQL parser library for parsing event sourcing query language.
2//!
3//! This library provides a complete lexer and parser for EventQL (EQL), a query language
4//! designed for event sourcing systems. It allows you to parse EQL query strings into
5//! an abstract syntax tree (AST) that can be analyzed or executed.
6pub mod arena;
7mod ast;
8mod error;
9mod lexer;
10mod parser;
11#[cfg(test)]
12mod tests;
13mod token;
14mod typing;
15
16use crate::arena::Arena;
17use crate::lexer::tokenize;
18use crate::prelude::{
19    Analysis, AnalysisOptions, FunArgs, Type, Typed, display_type, name_to_type, parse,
20};
21use crate::token::Token;
22pub use ast::*;
23use rustc_hash::FxHashMap;
24use unicase::Ascii;
25
26/// Convenience module that re-exports all public types and functions.
27///
28/// This module provides a single import point for all the library's public API,
29/// including AST types, error types, lexer, parser, and token types.
30pub mod prelude {
31    pub use super::ast::*;
32    pub use super::error::*;
33    pub use super::parser::*;
34    pub use super::token::*;
35    pub use super::typing::analysis::*;
36    pub use super::typing::*;
37}
38
39/// Builder for function argument specifications.
40///
41/// Allows defining function signatures with both required and optional parameters.
42/// When `required` equals the length of `args`, all parameters are required.
43pub struct FunArgsBuilder<'a> {
44    args: &'a [Type],
45    required: usize,
46}
47
48impl<'a> FunArgsBuilder<'a> {
49    /// Creates a new `FunArgsBuilder` with the given argument types and required count.
50    pub fn new(args: &'a [Type], required: usize) -> Self {
51        Self { args, required }
52    }
53}
54
55impl<'a> From<&'a [Type]> for FunArgsBuilder<'a> {
56    fn from(args: &'a [Type]) -> Self {
57        Self {
58            args,
59            required: args.len(),
60        }
61    }
62}
63
64impl<'a, const N: usize> From<&'a [Type; N]> for FunArgsBuilder<'a> {
65    fn from(value: &'a [Type; N]) -> Self {
66        Self {
67            args: value.as_slice(),
68            required: value.len(),
69        }
70    }
71}
72
73/// Builder for configuring event type information on a [`SessionBuilder`].
74///
75/// Obtained by calling [`SessionBuilder::declare_event_type`]. Use [`record`](EventTypeBuilder::record)
76/// to define a record-shaped event type or [`custom`](EventTypeBuilder::custom) for a named custom type.
77pub struct EventTypeBuilder {
78    parent: SessionBuilder,
79}
80
81impl EventTypeBuilder {
82    /// Starts building a record-shaped event type with named fields.
83    pub fn record(self) -> EventTypeRecordBuilder {
84        EventTypeRecordBuilder {
85            inner: self,
86            props: Default::default(),
87        }
88    }
89
90    /// Declares a custom (non-record) event type by name.
91    pub fn custom(self, _name: &str) -> SessionBuilder {
92        todo!("deal with custom type later")
93    }
94}
95
96/// Builder for defining the fields of a record-shaped event type.
97///
98/// Obtained by calling [`EventTypeBuilder::record`]. Add fields with [`prop`](EventTypeRecordBuilder::prop)
99/// and finalize with [`build`](EventTypeRecordBuilder::build) to return to the [`SessionBuilder`].
100pub struct EventTypeRecordBuilder {
101    inner: EventTypeBuilder,
102    props: FxHashMap<StrRef, Type>,
103}
104
105impl EventTypeRecordBuilder {
106    /// Conditionally adds a field to the event record type.
107    pub fn prop_when(mut self, test: bool, name: &str, tpe: Type) -> Self {
108        if test {
109            self.props
110                .insert(self.inner.parent.arena.strings.alloc(name), tpe);
111        }
112
113        self
114    }
115
116    /// Adds a field with the given name and type to the event record type.
117    pub fn prop(mut self, name: &str, tpe: Type) -> Self {
118        self.props
119            .insert(self.inner.parent.arena.strings.alloc(name), tpe);
120        self
121    }
122
123    /// Conditionally adds a field with a custom type to the event record type.
124    pub fn prop_with_custom_when(mut self, test: bool, name: &str, tpe: &str) -> Self {
125        if test {
126            let tpe = self.inner.parent.arena.strings.alloc(tpe);
127            self.props.insert(
128                self.inner.parent.arena.strings.alloc(name),
129                Type::Custom(tpe),
130            );
131        }
132
133        self
134    }
135
136    /// Finalizes the event record type and returns the [`SessionBuilder`].
137    pub fn build(mut self) -> SessionBuilder {
138        let ptr = self.inner.parent.arena.types.alloc_record(self.props);
139        self.inner.parent.options.event_type_info = Type::Record(ptr);
140        self.inner.parent
141    }
142}
143
144/// A specialized `Result` type for EventQL parser operations.
145pub type Result<A> = std::result::Result<A, error::Error>;
146
147/// `SessionBuilder` is a builder for `Session` objects.
148///
149/// It allows for the configuration of analysis options, such as declaring
150/// functions (both regular and aggregate), event types, and custom types,
151/// before building an `EventQL` parsing session.
152pub struct SessionBuilder {
153    arena: Arena,
154    options: AnalysisOptions,
155}
156
157impl SessionBuilder {
158    /// Declares a new function with the given name, arguments, and return type.
159    ///
160    /// This function adds a new entry to the session's default scope, allowing
161    /// the parser to recognize and type-check calls to this function.
162    ///
163    /// # Arguments
164    ///
165    /// * `name` - The name of the function.
166    /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`.
167    /// * `result` - The return type of the function.
168    pub fn declare_func<'a>(
169        self,
170        name: &'a str,
171        args: impl Into<FunArgsBuilder<'a>>,
172        result: Type,
173    ) -> Self {
174        self.declare_func_when(true, name, args, result)
175    }
176
177    /// Conditionally declares a new function with the given name, arguments, and return type.
178    ///
179    /// This function behaves like `declare_func` but only declares the function
180    /// if the `test` argument is `true`. This is useful for conditionally
181    /// including functions based on configuration or features.
182    ///
183    /// # Arguments
184    ///
185    /// * `test` - A boolean indicating whether to declare the function.
186    /// * `name` - The name of the function.
187    /// * `args` - The arguments the function accepts, which can be converted into `FunArgs`.
188    /// * `result` - The return type of the function.
189    pub fn declare_func_when<'a>(
190        mut self,
191        test: bool,
192        name: &'a str,
193        args: impl Into<FunArgsBuilder<'a>>,
194        result: Type,
195    ) -> Self {
196        if test {
197            let builder = args.into();
198            let name = self.arena.strings.alloc_no_case(name);
199            let args = self.arena.types.alloc_args(builder.args);
200
201            self.options.default_scope.declare(
202                name,
203                Type::App {
204                    args: FunArgs {
205                        values: args,
206                        needed: builder.required,
207                    },
208                    result: self.arena.types.register_type(result),
209                    aggregate: false,
210                },
211            );
212        }
213
214        self
215    }
216
217    /// Declares a new aggregate function with the given name, arguments, and return type.
218    ///
219    /// Similar to `declare_func`, but marks the function as an aggregate function.
220    /// Aggregate functions have specific rules for where they can be used in an EQL query.
221    ///
222    /// # Arguments
223    ///
224    /// * `name` - The name of the aggregate function.
225    /// * `args` - The arguments the aggregate function accepts.
226    /// * `result` - The return type of the aggregate function.
227    pub fn declare_agg_func<'a>(
228        self,
229        name: &'a str,
230        args: impl Into<FunArgsBuilder<'a>>,
231        result: Type,
232    ) -> Self {
233        self.declare_agg_func_when(true, name, args, result)
234    }
235
236    /// Conditionally declares a new aggregate function.
237    ///
238    /// Behaves like `declare_agg_func` but only declares the function
239    /// if the `test` argument is `true`.
240    ///
241    /// # Arguments
242    ///
243    /// * `test` - A boolean indicating whether to declare the aggregate function.
244    /// * `name` - The name of the aggregate function.
245    /// * `args` - The arguments the aggregate function accepts.
246    /// * `result` - The return type of the aggregate function.
247    pub fn declare_agg_func_when<'a>(
248        mut self,
249        test: bool,
250        name: &'a str,
251        args: impl Into<FunArgsBuilder<'a>>,
252        result: Type,
253    ) -> Self {
254        if test {
255            let builder = args.into();
256            let name = self.arena.strings.alloc_no_case(name);
257            let args = self.arena.types.alloc_args(builder.args);
258
259            self.options.default_scope.declare(
260                name,
261                Type::App {
262                    args: FunArgs {
263                        values: args,
264                        needed: builder.required,
265                    },
266                    result: self.arena.types.register_type(result),
267                    aggregate: true,
268                },
269            );
270        }
271
272        self
273    }
274
275    /// Conditionally declares the expected type of event records.
276    ///
277    /// This type information is crucial for type-checking event properties
278    /// accessed in EQL queries (e.g., `e.id`, `e.data.value`).
279    /// The declaration only happens if `test` is `true`.
280    ///
281    /// # Arguments
282    ///
283    /// * `test` - A boolean indicating whether to declare the event type.
284    /// * `tpe` - The `Type` representing the structure of event records.
285    pub fn declare_event_type_when(mut self, test: bool, tpe: Type) -> Self {
286        if test {
287            self.options.event_type_info = tpe;
288        }
289
290        self
291    }
292
293    /// Declares the expected type of event records.
294    ///
295    /// This type information is crucial for type-checking event properties
296    /// accessed in EQL queries (e.g., `e.id`, `e.data.value`).
297    ///
298    /// # Arguments
299    ///
300    /// * `tpe` - The `Type` representing the structure of event records.
301    pub fn declare_event_type(self) -> EventTypeBuilder {
302        EventTypeBuilder { parent: self }
303    }
304
305    /// Conditionally declares a custom type that can be used in EQL queries.
306    ///
307    /// This allows the type-checker to recognize and validate custom types
308    /// that might be used in type conversions or record definitions.
309    /// The declaration only happens if `test` is `true`.
310    ///
311    /// # Arguments
312    ///
313    /// * `test` - A boolean indicating whether to declare the custom type.
314    /// * `name` - The name of the custom type.
315    pub fn declare_custom_type_when(mut self, test: bool, name: &str) -> Self {
316        if test {
317            self.options
318                .custom_types
319                .insert(Ascii::new(name.to_owned()));
320        }
321
322        self
323    }
324
325    /// Declares a custom type that can be used in EQL queries.
326    ///
327    /// This allows the type-checker to recognize and validate custom types
328    /// that might be used in type conversions or record definitions.
329    ///
330    /// # Arguments
331    ///
332    /// * `name` - The name of the custom type.
333    pub fn declare_custom_type(mut self, name: &str) -> Self {
334        self.options
335            .custom_types
336            .insert(Ascii::new(name.to_owned()));
337        self
338    }
339
340    /// Includes the standard library of functions and event types in the session.
341    ///
342    /// This method pre-configures the `SessionBuilder` with a set of commonly
343    /// used functions (e.g., mathematical, string, date/time) and a default
344    /// event type definition. Calling this method is equivalent to calling
345    /// `declare_func` and `declare_agg_func` for all standard library functions,
346    /// and `declare_event_type` for the default event structure.
347    pub fn use_stdlib(self) -> Self {
348        self.declare_func("abs", &[Type::Number], Type::Number)
349            .declare_func("ceil", &[Type::Number], Type::Number)
350            .declare_func("floor", &[Type::Number], Type::Number)
351            .declare_func("round", &[Type::Number], Type::Number)
352            .declare_func("cos", &[Type::Number], Type::Number)
353            .declare_func("exp", &[Type::Number], Type::Number)
354            .declare_func("pow", &[Type::Number, Type::Number], Type::Number)
355            .declare_func("sqrt", &[Type::Number], Type::Number)
356            .declare_func("rand", &[], Type::Number)
357            .declare_func("pi", &[Type::Number], Type::Number)
358            .declare_func("lower", &[Type::String], Type::String)
359            .declare_func("upper", &[Type::String], Type::String)
360            .declare_func("trim", &[Type::String], Type::String)
361            .declare_func("ltrim", &[Type::String], Type::String)
362            .declare_func("rtrim", &[Type::String], Type::String)
363            .declare_func("len", &[Type::String], Type::Number)
364            .declare_func("instr", &[Type::String], Type::Number)
365            .declare_func(
366                "substring",
367                &[Type::String, Type::Number, Type::Number],
368                Type::String,
369            )
370            .declare_func(
371                "replace",
372                &[Type::String, Type::String, Type::String],
373                Type::String,
374            )
375            .declare_func("startswith", &[Type::String, Type::String], Type::Bool)
376            .declare_func("endswith", &[Type::String, Type::String], Type::Bool)
377            .declare_func("now", &[], Type::DateTime)
378            .declare_func("year", &[Type::Date], Type::Number)
379            .declare_func("month", &[Type::Date], Type::Number)
380            .declare_func("day", &[Type::Date], Type::Number)
381            .declare_func("hour", &[Type::Time], Type::Number)
382            .declare_func("minute", &[Type::Time], Type::Number)
383            .declare_func("second", &[Type::Time], Type::Number)
384            .declare_func("weekday", &[Type::Date], Type::Number)
385            .declare_func(
386                "IF",
387                &[Type::Bool, Type::Unspecified, Type::Unspecified],
388                Type::Unspecified,
389            )
390            .declare_agg_func(
391                "count",
392                FunArgsBuilder {
393                    args: &[Type::Bool],
394                    required: 0,
395                },
396                Type::Number,
397            )
398            .declare_agg_func("sum", &[Type::Number], Type::Number)
399            .declare_agg_func("avg", &[Type::Number], Type::Number)
400            .declare_agg_func("min", &[Type::Number], Type::Number)
401            .declare_agg_func("max", &[Type::Number], Type::Number)
402            .declare_agg_func("median", &[Type::Number], Type::Number)
403            .declare_agg_func("stddev", &[Type::Number], Type::Number)
404            .declare_agg_func("variance", &[Type::Number], Type::Number)
405            .declare_agg_func("unique", &[Type::Unspecified], Type::Unspecified)
406            .declare_event_type()
407            .record()
408            .prop("specversion", Type::String)
409            .prop("id", Type::String)
410            .prop("time", Type::DateTime)
411            .prop("source", Type::String)
412            .prop("subject", Type::Subject)
413            .prop("type", Type::String)
414            .prop("datacontenttype", Type::String)
415            .prop("data", Type::Unspecified)
416            .prop("predecessorhash", Type::String)
417            .prop("hash", Type::String)
418            .prop("traceparent", Type::String)
419            .prop("tracestate", Type::String)
420            .prop("signature", Type::String)
421            .build()
422    }
423
424    /// Builds the `Session` object with the configured analysis options.
425    ///
426    /// This consumes the `SessionBuilder` and returns a `Session` instance
427    /// ready for tokenizing, parsing, and analyzing EventQL queries.
428    pub fn build(mut self) -> Session {
429        self.arena.types.freeze();
430
431        Session {
432            arena: self.arena,
433            options: self.options,
434        }
435    }
436}
437
438impl Default for SessionBuilder {
439    fn default() -> Self {
440        Self {
441            arena: Default::default(),
442            options: AnalysisOptions::empty(),
443        }
444    }
445}
446
447/// `Session` is the main entry point for parsing and analyzing EventQL queries.
448///
449/// It holds the necessary context, such as the expression arena and analysis options,
450/// to perform lexical analysis, parsing, and static analysis of EQL query strings.
451pub struct Session {
452    arena: Arena,
453    options: AnalysisOptions,
454}
455
456impl Session {
457    /// Creates a new `SessionBuilder` for configuring and building a `Session`.
458    ///
459    /// This is the recommended way to create a `Session` instance, allowing
460    /// for customization of functions, event types, and custom types.
461    ///
462    /// # Returns
463    ///
464    /// A new `SessionBuilder` instance.
465    pub fn builder() -> SessionBuilder {
466        SessionBuilder::default()
467    }
468
469    /// Tokenize an EventQL query string.
470    ///
471    /// This function performs lexical analysis on the input string, converting it
472    /// into a sequence of tokens. Each token includes position information (line
473    /// and column numbers) for error reporting.
474    /// # Recognized Tokens
475    ///
476    /// - **Identifiers**: Alphanumeric names starting with a letter (e.g., `events`, `e`)
477    /// - **Keywords**: Case-insensitive SQL-like keywords detected by the parser
478    /// - **Numbers**: Floating-point literals (e.g., `42`, `3.14`)
479    /// - **Strings**: Double-quoted string literals (e.g., `"hello"`)
480    /// - **Operators**: Arithmetic (`+`, `-`, `*`, `/`), comparison (`==`, `!=`, `<`, `<=`, `>`, `>=`), logical (`AND`, `OR`, `XOR`, `NOT`)
481    /// - **Symbols**: Structural characters (`(`, `)`, `[`, `]`, `{`, `}`, `.`, `,`, `:`)
482    pub fn tokenize<'a>(&self, input: &'a str) -> Result<Vec<Token<'a>>> {
483        let tokens = tokenize(input)?;
484        Ok(tokens)
485    }
486
487    /// Parse an EventQL query string into an abstract syntax tree.
488    ///
489    /// This is the main entry point for parsing EventQL queries. It performs both
490    /// lexical analysis (tokenization) and syntactic analysis (parsing) in a single call.
491    /// # Examples
492    ///
493    /// ```
494    /// use eventql_parser::Session;
495    ///
496    /// // Parse a simple query
497    /// let mut session = Session::builder().use_stdlib().build();
498    /// let query = session.parse("FROM e IN events WHERE e.id == \"1\" PROJECT INTO e").unwrap();
499    /// assert!(query.predicate.is_some());
500    /// ```
501    pub fn parse(&mut self, input: &str) -> Result<Query<Raw>> {
502        let tokens = self.tokenize(input)?;
503        Ok(parse(&mut self.arena, tokens.as_slice())?)
504    }
505
506    /// Performs static analysis on an EventQL query.
507    ///
508    /// This function takes a raw (untyped) query and performs type checking and
509    /// variable scoping analysis. It validates that:
510    /// - All variables are properly declared
511    /// - Types match expected types in expressions and operations
512    /// - Field accesses are valid for their record types
513    /// - Function calls have the correct argument types
514    /// - Aggregate functions are only used in PROJECT INTO clauses
515    /// - Aggregate functions are not mixed with source-bound fields in projections
516    /// - Aggregate function arguments are source-bound fields (not constants or function results)
517    /// - Record literals are non-empty in projection contexts
518    ///
519    /// # Arguments
520    ///
521    /// * `options` - Configuration containing type information and default scope
522    /// * `query` - The raw query to analyze
523    ///
524    /// # Returns
525    ///
526    /// Returns a typed query on success, or an `AnalysisError` if type checking fails.
527    pub fn run_static_analysis(&mut self, query: Query<Raw>) -> Result<Query<Typed>> {
528        let mut analysis = self.analysis();
529        Ok(analysis.analyze_query(query)?)
530    }
531
532    /// Converts a type name string to its corresponding [`Type`] variant.
533    ///
534    /// This function performs case-insensitive matching for built-in type names and checks
535    /// against custom types defined in the analysis options.
536    ///
537    /// # Returns
538    ///
539    /// * `Some(Type)` - If the name matches a built-in type or custom type
540    /// * `None` - If the name doesn't match any known type
541    ///
542    /// # Built-in Type Mappings
543    ///
544    /// The following type names are recognized (case-insensitive):
545    /// - `"string"` → [`Type::String`]
546    /// - `"int"` or `"float64"` → [`Type::Number`]
547    /// - `"boolean"` → [`Type::Bool`]
548    /// - `"date"` → [`Type::Date`]
549    /// - `"time"` → [`Type::Time`]
550    /// - `"datetime"` → [`Type::DateTime`]
551    pub fn get_type_from_name(&mut self, name: &str) -> Option<Type> {
552        let str_ref = self.arena.strings.alloc(name);
553        name_to_type(&self.arena, &self.options, str_ref)
554    }
555
556    /// Provides human-readable string formatting for types.
557    ///
558    /// Function types display optional parameters with a `?` suffix. For example,
559    /// a function with signature `(boolean, number?) -> string` accepts 1 or 2 arguments.
560    /// Aggregate functions use `=>` instead of `->` in their signature.
561    pub fn display_type(&self, tpe: &Type) -> String {
562        display_type(&self.arena, *tpe)
563    }
564
565    /// Creates an [`Analysis`] instance for fine-grained control over static analysis.
566    ///
567    /// Use this when you need to analyze individual expressions or manage scopes manually,
568    /// rather than using [`run_static_analysis`](Session::run_static_analysis) for whole queries.
569    pub fn analysis(&mut self) -> Analysis<'_> {
570        Analysis::new(&mut self.arena, &self.options)
571    }
572}