qail_core/parser/mod.rs
1//! QAIL Parser using nom.
2//!
3//! Parses QAIL v2 keyword-based syntax into an AST.
4//!
5//! # Syntax Overview
6//!
7//! ```text
8//! get users
9//! fields id, email
10//! where active = true
11//! order by created_at desc
12//! limit 10
13//! ```
14
15/// Grammar rules and parsing combinators.
16pub mod grammar;
17pub mod query_file;
18pub mod schema;
19
20#[cfg(test)]
21mod tests;
22
23use crate::ast::*;
24use crate::error::{QailError, QailResult};
25
26/// Maximum Qail input length (64 KB).
27/// Prevents stack overflow via deeply nested parenthesized expressions
28/// (the recursive descent parser has no depth limit, but 64KB is insufficient
29/// to encode enough nesting to blow the stack while being generous for any
30/// legitimate query).
31const MAX_INPUT_LENGTH: usize = 64 * 1024;
32
33/// Parse a complete QAIL query string (v2 syntax only).
34/// Uses keyword-based syntax: `get table fields * where col = value`
35/// Also supports shorthand: `get table[filter]` desugars to `get table where filter`
36pub fn parse(input: &str) -> QailResult<Qail> {
37 let input = input.trim();
38
39 // R8-A: Reject oversized inputs before recursive descent to prevent stack overflow
40 if input.len() > MAX_INPUT_LENGTH {
41 return Err(QailError::parse(
42 0,
43 format!(
44 "Input too large: {} bytes (max {} bytes)",
45 input.len(),
46 MAX_INPUT_LENGTH,
47 ),
48 ));
49 }
50
51 // Use grammar::parse which handles comment stripping + [filter] desugaring
52 match grammar::parse(input) {
53 Ok(cmd) => Ok(cmd),
54 Err(e) => Err(QailError::parse(0, e)),
55 }
56}