1pub(crate) mod alter_drop_show;
2pub(crate) mod batch;
3pub(crate) mod config_parsers;
4pub(crate) mod config_parsers_diff;
5pub(crate) mod config_validation;
6pub(crate) mod create;
7pub(crate) mod filter;
8pub(crate) mod formula;
9pub(crate) mod helpers;
10pub(crate) mod point_ops;
11pub(crate) mod query;
12mod recover;
13pub(crate) mod r#update;
14pub(crate) mod upsert;
15pub(crate) mod with_clause;
16
17use crate::ast::Stmt;
18use crate::error::{QqlError, Span};
19use crate::lexer::Lexer;
20use crate::token::{Token, TokenKind};
21use alloc::string::String;
22use alloc::vec::Vec;
23pub use config_validation::{
24 STRICT_MODE_KEYS, check_deleted_threshold, config_bool, config_float_range, config_has_key,
25 config_max_optimization_threads, config_non_negative_u64, config_positive_u64, config_value,
26 is_strict_mode_key, merge_collection_config, validate_hnsw_value, validate_index_options,
27 validate_optimizers_value, validate_params_value, validate_strict_mode_value,
28 validate_vectors_value, validate_wal_value,
29};
30pub use recover::RecoveredScript;
31
32pub struct Parser;
39
40pub(crate) struct AstLowerer<'a> {
41 pub input: &'a str,
42 tokens: Vec<Token<'a>>,
43 index: usize,
44 positional_param_count: usize,
45}
46
47pub const MAX_STATEMENTS: usize = 256;
50
51pub(crate) fn syntax_err(
52 message: impl Into<alloc::borrow::Cow<'static, str>>,
53 span: Span,
54) -> QqlError {
55 QqlError::parse("QQL-PARSE-SYNTAX", message, span)
56}
57
58pub fn ascii_equal(s: &str, other: &str) -> bool {
60 s.eq_ignore_ascii_case(other)
61}
62
63pub fn is_contextual_field_name(kind: TokenKind) -> bool {
65 kind.is_keyword_or_identifier()
66}
67
68impl Parser {
69 pub fn parse(input: &str) -> Result<Stmt, QqlError> {
71 AstLowerer::lower_statement(input)
72 }
73
74 pub fn parse_all(input: &str) -> Result<Vec<Stmt>, QqlError> {
76 AstLowerer::lower_script(input)
77 }
78
79 pub fn parse_all_with_spans(input: &str) -> Result<Vec<(Stmt, Span)>, QqlError> {
81 AstLowerer::lower_script_with_spans(input)
82 }
83
84 pub fn parse_all_recovering(input: &str) -> RecoveredScript {
91 AstLowerer::lower_script_recovering(input)
92 }
93
94 pub fn parse_value(input: &str) -> Result<crate::ast::Value, QqlError> {
98 let tokens = AstLowerer::lex(input)?;
99 let mut parser = AstLowerer::new(input, tokens);
100 let val = parser.parse_value()?;
101 parser.expect_end()?;
102 Ok(val)
103 }
104}
105
106impl<'a> AstLowerer<'a> {
107 fn new(input: &'a str, tokens: Vec<Token<'a>>) -> Self {
108 Self {
109 input,
110 tokens,
111 index: 0,
112 positional_param_count: 0,
113 }
114 }
115
116 fn lower_statement(input: &'a str) -> Result<Stmt, QqlError> {
117 let tokens = Self::lex(input)?;
118 let mut parser = AstLowerer::new(input, tokens);
119 let stmt = parser.parse_stmt()?;
120 if parser.peek()?.kind == TokenKind::Semicolon {
121 parser.advance()?;
122 }
123 parser.expect_end()?;
124 Ok(stmt)
125 }
126
127 fn lower_script(input: &'a str) -> Result<Vec<Stmt>, QqlError> {
128 let with_spans = Self::lower_script_with_spans(input)?;
129 Ok(with_spans.into_iter().map(|(s, _)| s).collect())
130 }
131
132 pub(crate) fn lower_script_with_spans(input: &'a str) -> Result<Vec<(Stmt, Span)>, QqlError> {
133 let tokens = Self::lex(input)?;
134 let mut parser = AstLowerer::new(input, tokens);
135 let mut statements = Vec::new();
136 if parser.peek()?.kind == TokenKind::Semicolon {
137 return Err(QqlError::parse(
138 "QQL-PARSE-EMPTY-STATEMENT",
139 "leading or empty statements are not allowed",
140 parser.peek()?.span,
141 ));
142 }
143
144 while parser.peek()?.kind != TokenKind::Eof {
145 if statements.len() >= MAX_STATEMENTS {
146 return Err(QqlError::parse(
147 "QQL-PARSE-STATEMENT-LIMIT",
148 alloc::format!("a script may contain at most {MAX_STATEMENTS} statements"),
149 parser.peek()?.span,
150 ));
151 }
152 let start_tok = parser.peek()?;
153 let start_pos = start_tok.span.start;
154 let stmt = parser.parse_stmt()?;
155 let end_pos = match parser.peek()?.kind {
156 TokenKind::Semicolon => {
157 let semi_span = parser.peek()?.span;
158 parser.advance()?;
159 if parser.peek()?.kind == TokenKind::Semicolon {
160 return Err(QqlError::parse(
161 "QQL-PARSE-EMPTY-STATEMENT",
162 "repeated semicolons are not allowed",
163 parser.peek()?.span,
164 ));
165 }
166 semi_span.end
167 }
168 TokenKind::Eof => {
169 let prev_idx = parser.index.saturating_sub(1);
170 parser
171 .tokens
172 .get(prev_idx)
173 .map(|t| t.span.end)
174 .unwrap_or(start_tok.span.end)
175 }
176 _ => {
177 return Err(QqlError::parse(
178 "QQL-PARSE-SEPARATOR",
179 "multiple statements must be separated by a semicolon",
180 parser.peek()?.span,
181 ));
182 }
183 };
184 statements.push((stmt, Span::new(start_pos, end_pos)));
185 }
186 Ok(statements)
187 }
188
189 fn lex(input: &'a str) -> Result<Vec<Token<'a>>, QqlError> {
190 let lexer = Lexer::new(input);
191 let mut tokens = Vec::with_capacity(input.len() / 6 + 1);
192 for token_res in lexer {
193 tokens.push(token_res?);
194 }
195 Ok(tokens)
196 }
197
198 fn expect_end(&mut self) -> Result<(), QqlError> {
199 if self.index < self.tokens.len() {
200 let tok = self.tokens[self.index];
201 return Err(QqlError::parse(
202 "QQL-PARSE-TRAILING",
203 alloc::format!("unexpected trailing token '{}'", tok.text),
204 tok.span,
205 ));
206 }
207
208 Ok(())
209 }
210
211 pub fn parse_stmt(&mut self) -> Result<Stmt, QqlError> {
212 let tok = self.peek()?;
213 match tok.kind {
214 TokenKind::Create => self.parse_create(),
215 TokenKind::Alter => self.parse_alter(),
216 TokenKind::Drop => self.parse_drop(),
217 TokenKind::Show => self.parse_show(),
218 TokenKind::Upsert => self.parse_upsert(),
219 TokenKind::Scroll => self.parse_scroll(),
220 TokenKind::Query => self.parse_query(),
221 TokenKind::With => self.parse_query_with_cte(),
222 TokenKind::Delete => self.parse_delete(),
223 TokenKind::Clear => self.parse_clear(),
224 TokenKind::Update => self.parse_update(),
225 TokenKind::Count => self.parse_count(),
226 TokenKind::Facet => self.parse_facet(),
227 TokenKind::Set => self.parse_set_quota(),
228 TokenKind::Batch => self.parse_batch(),
229 _ => Err(QqlError::parse(
230 "QQL-PARSE-STATEMENT",
231 alloc::format!("expected a QQL statement keyword, got '{}'", tok.text),
232 tok.span,
233 )),
234 }
235 }
236
237 pub fn peek(&mut self) -> Result<Token<'a>, QqlError> {
240 if self.index < self.tokens.len() {
241 Ok(self.tokens[self.index])
242 } else {
243 Ok(Token::eof(self.input.len()))
244 }
245 }
246
247 pub fn peek_nth(&self, offset: usize) -> Token<'a> {
248 let idx = self.index + offset;
249 if idx < self.tokens.len() {
250 self.tokens[idx]
251 } else {
252 Token::eof(self.input.len())
253 }
254 }
255
256 pub fn advance(&mut self) -> Result<Token<'a>, QqlError> {
257 let tok = self.peek()?;
258 if self.index < self.tokens.len() {
259 self.index += 1;
260 }
261 Ok(tok)
262 }
263
264 pub(crate) fn prev_span(&self) -> Span {
265 let prev_idx = self.index.saturating_sub(1);
266 self.tokens
267 .get(prev_idx)
268 .map(|t| t.span)
269 .unwrap_or(Span::new(0, 0))
270 }
271
272 pub fn expect(&mut self, kind: TokenKind) -> Result<Token<'a>, QqlError> {
273 let tok = self.peek()?;
274 if tok.kind != kind {
275 return Err(QqlError::parse(
276 "QQL-PARSE-EXPECTED",
277 alloc::format!("expected {} but got '{}'", kind, tok.text),
278 tok.span,
279 ));
280 }
281 self.advance()
282 }
283
284 pub fn parse_identifier_str(&mut self) -> Result<String, QqlError> {
287 let tok = self.peek()?;
288 if tok.kind == TokenKind::String {
289 self.advance()?;
290 return self.decode_string(tok);
291 }
292 if tok.is_keyword_or_identifier() {
293 self.advance()?;
294 Ok(tok.text.to_string())
295 } else {
296 Err(QqlError::parse(
297 "QQL-PARSE-IDENTIFIER",
298 alloc::format!("expected identifier or quoted name, got '{}'", tok.text),
299 tok.span,
300 ))
301 }
302 }
303
304 pub fn parse_identifier(&mut self) -> Result<String, QqlError> {
305 self.parse_identifier_str()
306 }
307
308 pub fn parse_value(&mut self) -> Result<crate::ast::Value, QqlError> {
311 let tok = self.peek()?;
312 match tok.kind {
313 TokenKind::String => {
314 self.advance()?;
315 self.decode_string(tok).map(crate::ast::Value::Str)
316 }
317 TokenKind::Float => {
318 self.advance()?;
319 if let Ok(v) = tok.text.parse::<f64>() {
322 if !v.is_finite() {
325 return Err(QqlError::parse(
326 "QQL-PARSE-FLOAT",
327 alloc::format!("float literal '{}' is not finite", tok.text),
328 tok.span,
329 ));
330 }
331 Ok(crate::ast::Value::Float(v))
332 } else {
333 Ok(crate::ast::Value::Str(tok.text.to_string()))
334 }
335 }
336 TokenKind::Integer => {
337 self.advance()?;
338 if let Ok(v) = tok.text.parse::<i64>() {
342 Ok(crate::ast::Value::Int(v))
343 } else if let Ok(v) = tok.text.parse::<u64>() {
344 Ok(crate::ast::Value::UInt(v))
345 } else {
346 Ok(crate::ast::Value::Str(tok.text.to_string()))
347 }
348 }
349 TokenKind::Null => {
350 self.advance()?;
351 Ok(crate::ast::Value::Null)
352 }
353 TokenKind::True => {
354 self.advance()?;
355 Ok(crate::ast::Value::Bool(true))
356 }
357 TokenKind::False => {
358 self.advance()?;
359 Ok(crate::ast::Value::Bool(false))
360 }
361 kind if kind.is_keyword_or_identifier() => {
362 self.advance()?;
364 Ok(crate::ast::Value::Str(tok.text.to_string()))
365 }
366 TokenKind::Colon => {
367 let colon_tok = self.advance()?;
368 let name = self.parse_param_name()?;
369 let span = Span::new(colon_tok.span.start, self.prev_span().end);
370 Ok(crate::ast::Value::Param(
371 name,
372 Some(alloc::boxed::Box::new(span)),
373 ))
374 }
375 TokenKind::Question => {
376 let q_tok = self.advance()?;
377 let idx = self.next_positional_param();
378 Ok(crate::ast::Value::PositionalParam(
379 idx,
380 Some(alloc::boxed::Box::new(q_tok.span)),
381 ))
382 }
383 TokenKind::Lbrace => self.parse_payload_dict().map(crate::ast::Value::Dict),
384 TokenKind::Lbracket => self.parse_list().map(crate::ast::Value::List),
385 _ => Err(QqlError::parse(
386 "QQL-PARSE-VALUE",
387 alloc::format!("unexpected value token '{}'", tok.text),
388 tok.span,
389 )),
390 }
391 }
392
393 pub(crate) fn next_positional_param(&mut self) -> usize {
394 let idx = self.positional_param_count;
395 self.positional_param_count += 1;
396 idx
397 }
398
399 pub(crate) fn parse_param_name(&mut self) -> Result<String, QqlError> {
400 let tok = self.peek()?;
401 if tok.is_keyword_or_identifier() {
402 self.advance()?;
403 Ok(tok.text.to_string())
404 } else {
405 Err(QqlError::parse(
406 "QQL-PARSE-PARAM",
407 alloc::format!(
408 "expected parameter identifier after ':', found '{}'",
409 tok.text
410 ),
411 tok.span,
412 ))
413 }
414 }
415
416 fn decode_string(&self, token: Token<'a>) -> Result<String, QqlError> {
417 let input = self.input.as_bytes();
418 let start = token.span.start;
419 let end = token.span.end;
420 let first_byte = input.get(start).copied().unwrap_or(0);
421 let is_raw_or_backtick = first_byte == b'r' || first_byte == b'`';
422 let triple_quoted = end >= start + 6
428 && (input[start..start + 3] == b"'''"[..] || input[start..start + 3] == b"\"\"\""[..])
429 && input[start..start + 3] == input[end - 3..end];
430 if is_raw_or_backtick
431 || triple_quoted
432 || !(token.text.contains('\\') || first_byte == b'\'' && token.text.contains("''"))
433 {
434 return Ok(token.text.to_string());
435 }
436 let single_quoted = first_byte == b'\'';
437 let mut decoded = String::with_capacity(token.text.len());
438 let mut chars = token.text.chars().peekable();
439 while let Some(ch) = chars.next() {
440 if single_quoted && ch == '\'' && chars.peek() == Some(&'\'') {
441 chars.next();
442 decoded.push('\'');
443 continue;
444 }
445 if ch != '\\' {
446 decoded.push(ch);
447 continue;
448 }
449 let escaped = chars.next().ok_or_else(|| {
450 QqlError::parse(
451 "QQL-PARSE-ESCAPE",
452 "unterminated escape sequence",
453 token.span,
454 )
455 })?;
456 decoded.push(match escaped {
457 'n' => '\n',
458 'r' => '\r',
459 't' => '\t',
460 '\\' => '\\',
461 '\'' => '\'',
462 '"' => '"',
463 '$' => '$',
464 _ => {
465 return Err(QqlError::parse(
466 "QQL-PARSE-ESCAPE",
467 alloc::format!("unsupported escape sequence \\{}", escaped),
468 token.span,
469 ));
470 }
471 });
472 }
473 Ok(decoded)
474 }
475}