vibesql_parser/parser/expressions/
operators.rs

1use super::*;
2
3impl Parser {
4    /// Parse OR expression (lowest precedence)
5    pub(super) fn parse_or_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
6        let mut left = self.parse_and_expression()?;
7
8        while self.peek_keyword(Keyword::Or) {
9            self.consume_keyword(Keyword::Or)?;
10            let right = self.parse_and_expression()?;
11            left = vibesql_ast::Expression::BinaryOp {
12                op: vibesql_ast::BinaryOperator::Or,
13                left: Box::new(left),
14                right: Box::new(right),
15            };
16        }
17
18        Ok(left)
19    }
20
21    /// Parse AND expression
22    pub(super) fn parse_and_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
23        let mut left = self.parse_not_expression()?;
24
25        while self.peek_keyword(Keyword::And) {
26            self.consume_keyword(Keyword::And)?;
27            let right = self.parse_not_expression()?;
28            left = vibesql_ast::Expression::BinaryOp {
29                op: vibesql_ast::BinaryOperator::And,
30                left: Box::new(left),
31                right: Box::new(right),
32            };
33        }
34
35        Ok(left)
36    }
37
38    /// Parse NOT expression
39    /// NOT has precedence between AND and comparison operators
40    /// This ensures "NOT col IS NULL" parses as "NOT (col IS NULL)" not "(NOT col) IS NULL"
41    pub(super) fn parse_not_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
42        // Check for NOT keyword (but not NOT IN, NOT BETWEEN, NOT LIKE, NOT EXISTS)
43        // Those are handled in parse_comparison_expression and parse_primary_expression
44        if self.peek_keyword(Keyword::Not) {
45            // Peek ahead to see if it's a special case
46            let saved_pos = self.position;
47            self.advance(); // consume NOT
48
49            // Check for special cases that are NOT unary NOT
50            if self.peek_keyword(Keyword::In)
51                || self.peek_keyword(Keyword::Between)
52                || self.peek_keyword(Keyword::Like)
53                || self.peek_keyword(Keyword::Exists)
54            {
55                // Restore position and let the other parsers handle it
56                self.position = saved_pos;
57                return self.parse_comparison_expression();
58            }
59
60            // It's a unary NOT - parse the expression it applies to
61            // Recursively call parse_not_expression to handle multiple NOTs
62            let expr = self.parse_not_expression()?;
63
64            Ok(vibesql_ast::Expression::UnaryOp {
65                op: vibesql_ast::UnaryOperator::Not,
66                expr: Box::new(expr),
67            })
68        } else {
69            self.parse_comparison_expression()
70        }
71    }
72
73    /// Parse additive expression (handles +, -, and ||)
74    pub(super) fn parse_additive_expression(
75        &mut self,
76    ) -> Result<vibesql_ast::Expression, ParseError> {
77        let mut left = self.parse_multiplicative_expression()?;
78
79        loop {
80            let op = match self.peek() {
81                Token::Symbol('+') => vibesql_ast::BinaryOperator::Plus,
82                Token::Symbol('-') => vibesql_ast::BinaryOperator::Minus,
83                Token::Operator(crate::token::MultiCharOperator::Concat) => {
84                    vibesql_ast::BinaryOperator::Concat
85                }
86                _ => break,
87            };
88            self.advance();
89
90            let right = self.parse_multiplicative_expression()?;
91            left = vibesql_ast::Expression::BinaryOp {
92                op,
93                left: Box::new(left),
94                right: Box::new(right),
95            };
96        }
97
98        Ok(left)
99    }
100
101    /// Parse multiplicative expression (handles *, /, DIV, %)
102    pub(super) fn parse_multiplicative_expression(
103        &mut self,
104    ) -> Result<vibesql_ast::Expression, ParseError> {
105        let mut left = self.parse_unary_expression()?;
106
107        loop {
108            let op = match self.peek() {
109                Token::Symbol('*') => vibesql_ast::BinaryOperator::Multiply,
110                Token::Symbol('/') => vibesql_ast::BinaryOperator::Divide,
111                Token::Symbol('%') => vibesql_ast::BinaryOperator::Modulo,
112                Token::Keyword(Keyword::Div) => vibesql_ast::BinaryOperator::IntegerDivide,
113                _ => break,
114            };
115            self.advance();
116
117            let right = self.parse_unary_expression()?;
118            left = vibesql_ast::Expression::BinaryOp {
119                op,
120                left: Box::new(left),
121                right: Box::new(right),
122            };
123        }
124
125        Ok(left)
126    }
127
128    /// Parse comparison expression (handles =, <, >, <=, >=, !=, <>, IN, BETWEEN, LIKE, IS NULL)
129    /// These operators have lower precedence than arithmetic operators
130    pub(super) fn parse_comparison_expression(
131        &mut self,
132    ) -> Result<vibesql_ast::Expression, ParseError> {
133        let mut left = self.parse_additive_expression()?;
134
135        // Check for IN operator (including NOT IN) and BETWEEN (including NOT BETWEEN)
136        if self.peek_keyword(Keyword::Not) {
137            // Peek ahead to see if it's "NOT IN" or "NOT BETWEEN"
138            let saved_pos = self.position;
139            self.advance(); // consume NOT
140
141            if self.peek_keyword(Keyword::In) {
142                // It's NOT IN
143                self.consume_keyword(Keyword::In)?;
144
145                // Expect opening paren
146                self.expect_token(Token::LParen)?;
147
148                // Check if it's a subquery (SELECT ...) or a value list
149                if self.peek_keyword(Keyword::Select) {
150                    // It's a subquery: NOT IN (SELECT ...)
151                    let subquery = self.parse_select_statement()?;
152                    self.expect_token(Token::RParen)?;
153
154                    return Ok(vibesql_ast::Expression::In {
155                        expr: Box::new(left),
156                        subquery: Box::new(subquery),
157                        negated: true,
158                    });
159                } else {
160                    // It's a value list: NOT IN (val1, val2, ...)
161                    let values = self.parse_expression_list()?;
162                    self.expect_token(Token::RParen)?;
163
164                    // Empty IN lists are allowed per SQL:1999 (evaluates to TRUE for NOT IN)
165                    return Ok(vibesql_ast::Expression::InList {
166                        expr: Box::new(left),
167                        values,
168                        negated: true,
169                    });
170                }
171            } else if self.peek_keyword(Keyword::Between) {
172                // It's NOT BETWEEN
173                self.consume_keyword(Keyword::Between)?;
174
175                // Check for optional ASYMMETRIC or SYMMETRIC
176                let symmetric = if self.peek_keyword(Keyword::Symmetric) {
177                    self.consume_keyword(Keyword::Symmetric)?;
178                    true
179                } else {
180                    // ASYMMETRIC is default, but can be explicitly specified
181                    if self.peek_keyword(Keyword::Asymmetric) {
182                        self.consume_keyword(Keyword::Asymmetric)?;
183                    }
184                    false
185                };
186
187                // Parse low AND high
188                let low = self.parse_additive_expression()?;
189                self.consume_keyword(Keyword::And)?;
190                let high = self.parse_additive_expression()?;
191
192                return Ok(vibesql_ast::Expression::Between {
193                    expr: Box::new(left),
194                    low: Box::new(low),
195                    high: Box::new(high),
196                    negated: true,
197                    symmetric,
198                });
199            } else if self.peek_keyword(Keyword::Like) {
200                // It's NOT LIKE
201                self.consume_keyword(Keyword::Like)?;
202
203                // Parse pattern expression
204                let pattern = self.parse_additive_expression()?;
205
206                return Ok(vibesql_ast::Expression::Like {
207                    expr: Box::new(left),
208                    pattern: Box::new(pattern),
209                    negated: true,
210                });
211            } else {
212                // Not "NOT IN", "NOT BETWEEN", or "NOT LIKE", restore position and continue
213                // Note: NOT EXISTS is handled in parse_primary_expression()
214                self.position = saved_pos;
215            }
216        } else if self.peek_keyword(Keyword::In) {
217            // It's IN (not negated)
218            self.consume_keyword(Keyword::In)?;
219
220            // Expect opening paren
221            self.expect_token(Token::LParen)?;
222
223            // Check if it's a subquery (SELECT ...) or a value list
224            if self.peek_keyword(Keyword::Select) {
225                // It's a subquery: IN (SELECT ...)
226                let subquery = self.parse_select_statement()?;
227                self.expect_token(Token::RParen)?;
228
229                return Ok(vibesql_ast::Expression::In {
230                    expr: Box::new(left),
231                    subquery: Box::new(subquery),
232                    negated: false,
233                });
234            } else {
235                // It's a value list: IN (val1, val2, ...)
236                let values = self.parse_expression_list()?;
237                self.expect_token(Token::RParen)?;
238
239                // Empty IN lists are allowed per SQL:1999 (evaluates to FALSE)
240                return Ok(vibesql_ast::Expression::InList {
241                    expr: Box::new(left),
242                    values,
243                    negated: false,
244                });
245            }
246        } else if self.peek_keyword(Keyword::Between) {
247            // It's BETWEEN (not negated)
248            self.consume_keyword(Keyword::Between)?;
249
250            // Check for optional ASYMMETRIC or SYMMETRIC
251            let symmetric = if self.peek_keyword(Keyword::Symmetric) {
252                self.consume_keyword(Keyword::Symmetric)?;
253                true
254            } else {
255                // ASYMMETRIC is default, but can be explicitly specified
256                if self.peek_keyword(Keyword::Asymmetric) {
257                    self.consume_keyword(Keyword::Asymmetric)?;
258                }
259                false
260            };
261
262            // Parse low AND high
263            let low = self.parse_additive_expression()?;
264            self.consume_keyword(Keyword::And)?;
265            let high = self.parse_additive_expression()?;
266
267            return Ok(vibesql_ast::Expression::Between {
268                expr: Box::new(left),
269                low: Box::new(low),
270                high: Box::new(high),
271                negated: false,
272                symmetric,
273            });
274        } else if self.peek_keyword(Keyword::Like) {
275            // It's LIKE (not negated)
276            self.consume_keyword(Keyword::Like)?;
277
278            // Parse pattern expression
279            let pattern = self.parse_additive_expression()?;
280
281            return Ok(vibesql_ast::Expression::Like {
282                expr: Box::new(left),
283                pattern: Box::new(pattern),
284                negated: false,
285            });
286        }
287
288        // Check for comparison operators (both single-char and multi-char)
289        // Note: Exclude || (concat) operator which should be handled in additive expression
290        let is_comparison = match self.peek() {
291            Token::Symbol('=') | Token::Symbol('<') | Token::Symbol('>') => true,
292            Token::Operator(op) => !matches!(op, crate::token::MultiCharOperator::Concat),
293            _ => false,
294        };
295
296        if is_comparison {
297            let op = match self.peek() {
298                Token::Symbol('=') => vibesql_ast::BinaryOperator::Equal,
299                Token::Symbol('<') => vibesql_ast::BinaryOperator::LessThan,
300                Token::Symbol('>') => vibesql_ast::BinaryOperator::GreaterThan,
301                Token::Operator(op) => {
302                    use crate::token::MultiCharOperator;
303                    match op {
304                        MultiCharOperator::LessEqual => {
305                            vibesql_ast::BinaryOperator::LessThanOrEqual
306                        }
307                        MultiCharOperator::GreaterEqual => {
308                            vibesql_ast::BinaryOperator::GreaterThanOrEqual
309                        }
310                        MultiCharOperator::NotEqual | MultiCharOperator::NotEqualAlt => {
311                            vibesql_ast::BinaryOperator::NotEqual
312                        }
313                        // Vector distance operators (pgvector compatible)
314                        MultiCharOperator::CosineDistance => {
315                            vibesql_ast::BinaryOperator::CosineDistance
316                        }
317                        MultiCharOperator::NegativeInnerProduct => {
318                            vibesql_ast::BinaryOperator::NegativeInnerProduct
319                        }
320                        MultiCharOperator::L2Distance => vibesql_ast::BinaryOperator::L2Distance,
321                        MultiCharOperator::Concat => {
322                            return Err(ParseError {
323                                message: format!("Unexpected operator: {}", op),
324                            })
325                        }
326                    }
327                }
328                _ => unreachable!(),
329            };
330            self.advance();
331
332            // Check for quantified comparison (ALL, ANY, SOME)
333            if self.peek_keyword(Keyword::All)
334                || self.peek_keyword(Keyword::Any)
335                || self.peek_keyword(Keyword::Some)
336            {
337                let quantifier = if self.peek_keyword(Keyword::All) {
338                    self.consume_keyword(Keyword::All)?;
339                    vibesql_ast::Quantifier::All
340                } else if self.peek_keyword(Keyword::Any) {
341                    self.consume_keyword(Keyword::Any)?;
342                    vibesql_ast::Quantifier::Any
343                } else {
344                    self.consume_keyword(Keyword::Some)?;
345                    vibesql_ast::Quantifier::Some
346                };
347
348                // Expect opening paren
349                self.expect_token(Token::LParen)?;
350
351                // Parse subquery
352                let subquery = self.parse_select_statement()?;
353
354                // Expect closing paren
355                self.expect_token(Token::RParen)?;
356
357                return Ok(vibesql_ast::Expression::QuantifiedComparison {
358                    expr: Box::new(left),
359                    op,
360                    quantifier,
361                    subquery: Box::new(subquery),
362                });
363            }
364
365            let right = self.parse_additive_expression()?;
366            left = vibesql_ast::Expression::BinaryOp {
367                op,
368                left: Box::new(left),
369                right: Box::new(right),
370            };
371        }
372
373        // Check for IS NULL / IS NOT NULL
374        if self.peek_keyword(Keyword::Is) {
375            self.consume_keyword(Keyword::Is)?;
376
377            // Check for NOT
378            let negated = if self.peek_keyword(Keyword::Not) {
379                self.consume_keyword(Keyword::Not)?;
380                true
381            } else {
382                false
383            };
384
385            // Expect NULL
386            self.expect_keyword(Keyword::Null)?;
387
388            left = vibesql_ast::Expression::IsNull { expr: Box::new(left), negated };
389        }
390
391        Ok(left)
392    }
393
394    /// Parse unary expression (handles unary +/- operators)
395    pub(super) fn parse_unary_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
396        // Check for unary + or -
397        match self.peek() {
398            Token::Symbol('+') => {
399                self.advance();
400                let expr = self.parse_unary_expression()?;
401                Ok(vibesql_ast::Expression::UnaryOp {
402                    op: vibesql_ast::UnaryOperator::Plus,
403                    expr: Box::new(expr),
404                })
405            }
406            Token::Symbol('-') => {
407                self.advance();
408                let expr = self.parse_unary_expression()?;
409                Ok(vibesql_ast::Expression::UnaryOp {
410                    op: vibesql_ast::UnaryOperator::Minus,
411                    expr: Box::new(expr),
412                })
413            }
414            _ => self.parse_primary_expression(),
415        }
416    }
417
418    /// Parse a comma-separated list of expressions
419    /// Used for IN (val1, val2, ...) and function arguments
420    /// Does NOT consume the opening or closing parentheses
421    pub fn parse_expression_list(&mut self) -> Result<Vec<vibesql_ast::Expression>, ParseError> {
422        let mut expressions = Vec::new();
423
424        // Check for empty list (SQLite compatibility)
425        if matches!(self.peek(), Token::RParen) {
426            return Ok(expressions);
427        }
428
429        // Parse first expression
430        expressions.push(self.parse_expression()?);
431
432        // Parse remaining expressions
433        while matches!(self.peek(), Token::Comma) {
434            self.advance(); // consume comma
435            expressions.push(self.parse_expression()?);
436        }
437
438        Ok(expressions)
439    }
440}