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}