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