1pub(crate) mod alter_drop_show;
2pub(crate) mod config_parsers;
3pub(crate) mod config_validation;
4pub(crate) mod create;
5pub(crate) mod filter;
6pub(crate) mod formula;
7pub(crate) mod helpers;
8pub(crate) mod point_ops;
9pub(crate) mod query;
10mod syntax;
11pub(crate) mod r#update;
12pub(crate) mod upsert;
13pub(crate) mod with_clause;
14
15use crate::ast::Stmt;
16use crate::error::QqlError;
17use crate::lexer::Lexer;
18use crate::token::{Token, TokenKind};
19use alloc::string::String;
20use alloc::vec::Vec;
21pub use config_validation::{
22 check_deleted_threshold, config_bool, config_float_range, config_has_key,
23 config_max_optimization_threads, config_non_negative_u64, config_positive_u64, config_value,
24 merge_collection_config, validate_hnsw_value, validate_index_options,
25 validate_optimizers_value, validate_params_value, validate_vectors_value,
26};
27
28pub struct Parser;
33
34pub(crate) struct AstLowerer<'a> {
35 pub input: &'a str,
36 tokens: Vec<Token<'a>>,
37 index: usize,
38}
39
40pub const MAX_STATEMENTS: usize = 256;
43
44pub fn ascii_equal(s: &str, upper: &str) -> bool {
45 if s.len() != upper.len() {
46 return false;
47 }
48 s.as_bytes()
49 .iter()
50 .zip(upper.as_bytes().iter())
51 .all(|(a, b)| a.to_ascii_uppercase() == *b)
52}
53
54pub fn ascii_equal_lower(s: &str, lower: &str) -> bool {
55 if s.len() != lower.len() {
56 return false;
57 }
58 s.as_bytes()
59 .iter()
60 .zip(lower.as_bytes().iter())
61 .all(|(a, b)| a.to_ascii_lowercase() == *b)
62}
63
64pub fn is_contextual_field_name(kind: TokenKind) -> bool {
65 matches!(
66 kind,
67 TokenKind::Offset
68 | TokenKind::Score
69 | TokenKind::Threshold
70 | TokenKind::Lookup
71 | TokenKind::Id
72 | TokenKind::Dense
73 | TokenKind::Sparse
74 | TokenKind::Vector
75 | TokenKind::By
76 | TokenKind::Count
77 | TokenKind::Clear
78 | TokenKind::Field
79 | TokenKind::Into
80 )
81}
82
83fn is_contextual_identifier(kind: TokenKind) -> bool {
84 matches!(
85 kind,
86 TokenKind::Offset
87 | TokenKind::Score
88 | TokenKind::Threshold
89 | TokenKind::Lookup
90 | TokenKind::Id
91 | TokenKind::Dense
92 | TokenKind::Sparse
93 | TokenKind::Vector
94 | TokenKind::Count
95 | TokenKind::Clear
96 | TokenKind::Field
97 | TokenKind::Into
98 )
99}
100
101impl Parser {
102 pub fn parse(input: &str) -> Result<Stmt, QqlError> {
103 let statement = AstLowerer::lower_statement(input)?;
104 syntax::validate_statement(input)?;
105 Ok(statement)
106 }
107
108 pub fn parse_all(input: &str) -> Result<Vec<Stmt>, QqlError> {
109 let statements = AstLowerer::lower_script(input)?;
110 syntax::validate_script(input)?;
111 Ok(statements)
112 }
113}
114
115impl<'a> AstLowerer<'a> {
116 fn new(input: &'a str, tokens: Vec<Token<'a>>) -> Self {
117 Self {
118 input,
119 tokens,
120 index: 0,
121 }
122 }
123
124 fn lower_statement(input: &'a str) -> Result<Stmt, QqlError> {
125 let tokens = Self::lex(input)?;
126 let mut parser = AstLowerer::new(input, tokens);
127 let stmt = parser.parse_stmt()?;
128 if parser.peek()?.kind == TokenKind::Semicolon {
129 parser.advance()?;
130 }
131 parser.expect_end()?;
132 Ok(stmt)
133 }
134
135 fn lower_script(input: &'a str) -> Result<Vec<Stmt>, QqlError> {
136 let tokens = Self::lex(input)?;
137 let mut parser = AstLowerer::new(input, tokens);
138 let mut statements = Vec::new();
139 if parser.peek()?.kind == TokenKind::Semicolon {
140 return Err(QqlError::parse(
141 "QQL-PARSE-EMPTY-STATEMENT",
142 "leading or empty statements are not allowed",
143 parser.peek()?.span,
144 ));
145 }
146
147 while parser.peek()?.kind != TokenKind::Eof {
148 if statements.len() >= MAX_STATEMENTS {
149 return Err(QqlError::parse(
150 "QQL-PARSE-STATEMENT-LIMIT",
151 alloc::format!("a script may contain at most {MAX_STATEMENTS} statements"),
152 parser.peek()?.span,
153 ));
154 }
155 statements.push(parser.parse_stmt()?);
156 match parser.peek()?.kind {
157 TokenKind::Semicolon => {
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 }
167 TokenKind::Eof => break,
168 _ => {
169 return Err(QqlError::parse(
170 "QQL-PARSE-SEPARATOR",
171 "multiple statements must be separated by a semicolon",
172 parser.peek()?.span,
173 ));
174 }
175 }
176 }
177 Ok(statements)
178 }
179
180 fn lex(input: &'a str) -> Result<Vec<Token<'a>>, QqlError> {
181 let lexer = Lexer::new(input);
182 let mut tokens = Vec::with_capacity(input.len() / 6 + 1);
183 for token_res in lexer {
184 tokens.push(token_res?);
185 }
186 Ok(tokens)
187 }
188
189 fn expect_end(&mut self) -> Result<(), QqlError> {
190 if self.index < self.tokens.len() {
191 let tok = self.tokens[self.index];
192 return Err(QqlError::parse(
193 "QQL-PARSE-TRAILING",
194 alloc::format!("unexpected trailing token '{}'", tok.text),
195 tok.span,
196 ));
197 }
198
199 Ok(())
200 }
201
202 pub fn parse_stmt(&mut self) -> Result<Stmt, QqlError> {
203 let tok = self.peek()?;
204 match tok.kind {
205 TokenKind::Create => self.parse_create(),
206 TokenKind::Alter => self.parse_alter(),
207 TokenKind::Drop => self.parse_drop(),
208 TokenKind::Show => self.parse_show(),
209 TokenKind::Upsert => self.parse_upsert(),
210 TokenKind::Scroll => self.parse_scroll(),
211 TokenKind::Query => self.parse_query(),
212 TokenKind::With => self.parse_query_with_cte(),
213 TokenKind::Delete => self.parse_delete(),
214 TokenKind::Clear => self.parse_clear(),
215 TokenKind::Update => self.parse_update(),
216 TokenKind::Count => self.parse_count(),
217 _ => Err(QqlError::parse(
218 "QQL-PARSE-STATEMENT",
219 alloc::format!("expected a QQL statement keyword, got '{}'", tok.text),
220 tok.span,
221 )),
222 }
223 }
224
225 pub fn peek(&mut self) -> Result<Token<'a>, QqlError> {
228 if self.index < self.tokens.len() {
229 Ok(self.tokens[self.index])
230 } else {
231 Ok(Token::eof(self.input.len()))
232 }
233 }
234
235 pub fn peek_nth(&self, offset: usize) -> Token<'a> {
236 let idx = self.index + offset;
237 if idx < self.tokens.len() {
238 self.tokens[idx]
239 } else {
240 Token::eof(self.input.len())
241 }
242 }
243
244 pub fn advance(&mut self) -> Result<Token<'a>, QqlError> {
245 let tok = self.peek()?;
246 if self.index < self.tokens.len() {
247 self.index += 1;
248 }
249 Ok(tok)
250 }
251
252 pub fn expect(&mut self, kind: TokenKind) -> Result<Token<'a>, QqlError> {
253 let tok = self.peek()?;
254 if tok.kind != kind {
255 return Err(QqlError::parse(
256 "QQL-PARSE-EXPECTED",
257 alloc::format!("expected {} but got '{}'", kind, tok.text),
258 tok.span,
259 ));
260 }
261 self.advance()
262 }
263
264 pub fn parse_identifier_str(&mut self) -> Result<&'a str, QqlError> {
267 let tok = self.peek()?;
268 if tok.kind == TokenKind::Identifier
269 || tok.kind == TokenKind::String
270 || is_contextual_identifier(tok.kind)
271 {
272 self.advance()?;
273 Ok(tok.text)
274 } else {
275 Err(QqlError::parse(
276 "QQL-PARSE-IDENTIFIER",
277 alloc::format!("expected identifier or quoted name, got '{}'", tok.text),
278 tok.span,
279 ))
280 }
281 }
282
283 pub fn parse_identifier(&mut self) -> Result<String, QqlError> {
284 self.parse_identifier_str().map(String::from)
285 }
286
287 pub fn parse_value(&mut self) -> Result<crate::ast::Value, QqlError> {
290 let tok = self.peek()?;
291 match tok.kind {
292 TokenKind::String => {
293 self.advance()?;
294 self.decode_string(tok).map(crate::ast::Value::Str)
295 }
296 TokenKind::Float => {
297 self.advance()?;
298 let v: f64 = tok.text.parse().map_err(|_| {
299 QqlError::parse(
300 "QQL-PARSE-FLOAT",
301 alloc::format!("invalid float literal '{}'", tok.text),
302 tok.span,
303 )
304 })?;
305 Ok(crate::ast::Value::Float(v))
306 }
307 TokenKind::Integer => {
308 self.advance()?;
309 let v: i64 = tok.text.parse().map_err(|_| {
310 QqlError::parse(
311 "QQL-PARSE-INTEGER",
312 alloc::format!("invalid integer literal '{}'", tok.text),
313 tok.span,
314 )
315 })?;
316 Ok(crate::ast::Value::Int(v))
317 }
318 TokenKind::Null => {
319 self.advance()?;
320 Ok(crate::ast::Value::Null)
321 }
322 TokenKind::Identifier => {
323 self.advance()?;
324 if ascii_equal(tok.text, "TRUE") {
325 Ok(crate::ast::Value::Bool(true))
326 } else if ascii_equal(tok.text, "FALSE") {
327 Ok(crate::ast::Value::Bool(false))
328 } else if ascii_equal(tok.text, "NULL") {
329 Ok(crate::ast::Value::Null)
330 } else {
331 Ok(crate::ast::Value::Str(tok.text.to_string()))
332 }
333 }
334 TokenKind::Lbrace => self
335 .parse_payload_dict()
336 .map(|items| crate::ast::Value::Dict(items.into_iter().collect())),
337 TokenKind::Lbracket => self.parse_list().map(crate::ast::Value::List),
338 _ => Err(QqlError::parse(
339 "QQL-PARSE-VALUE",
340 alloc::format!("unexpected value token '{}'", tok.text),
341 tok.span,
342 )),
343 }
344 }
345
346 fn decode_string(&self, token: Token<'a>) -> Result<String, QqlError> {
347 let first_byte = self
348 .input
349 .as_bytes()
350 .get(token.span.start)
351 .copied()
352 .unwrap_or(0);
353 let is_raw_or_backtick = first_byte == b'r' || first_byte == b'`';
354 if is_raw_or_backtick
355 || !(token.text.contains('\\') || first_byte == b'\'' && token.text.contains("''"))
356 {
357 return Ok(token.text.to_string());
358 }
359 let single_quoted = first_byte == b'\'';
360 let mut decoded = String::with_capacity(token.text.len());
361 let mut chars = token.text.chars().peekable();
362 while let Some(ch) = chars.next() {
363 if single_quoted && ch == '\'' && chars.peek() == Some(&'\'') {
364 chars.next();
365 decoded.push('\'');
366 continue;
367 }
368 if ch != '\\' {
369 decoded.push(ch);
370 continue;
371 }
372 let escaped = chars.next().ok_or_else(|| {
373 QqlError::parse(
374 "QQL-PARSE-ESCAPE",
375 "unterminated escape sequence",
376 token.span,
377 )
378 })?;
379 decoded.push(match escaped {
380 'n' => '\n',
381 'r' => '\r',
382 't' => '\t',
383 '\\' => '\\',
384 '\'' => '\'',
385 '"' => '"',
386 '$' => '$',
387 _ => {
388 return Err(QqlError::parse(
389 "QQL-PARSE-ESCAPE",
390 alloc::format!("unsupported escape sequence \\{}", escaped),
391 token.span,
392 ));
393 }
394 });
395 }
396 Ok(decoded)
397 }
398}