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(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
75        let mut left = self.parse_multiplicative_expression()?;
76
77        loop {
78            let op = match self.peek() {
79                Token::Symbol('+') => vibesql_ast::BinaryOperator::Plus,
80                Token::Symbol('-') => vibesql_ast::BinaryOperator::Minus,
81                Token::Operator(crate::token::MultiCharOperator::Concat) => {
82                    vibesql_ast::BinaryOperator::Concat
83                }
84                _ => break,
85            };
86            self.advance();
87
88            let right = self.parse_multiplicative_expression()?;
89            left = vibesql_ast::Expression::BinaryOp { op, left: Box::new(left), right: Box::new(right) };
90        }
91
92        Ok(left)
93    }
94
95    /// Parse multiplicative expression (handles *, /, DIV, %)
96    pub(super) fn parse_multiplicative_expression(
97        &mut self,
98    ) -> Result<vibesql_ast::Expression, ParseError> {
99        let mut left = self.parse_unary_expression()?;
100
101        loop {
102            let op = match self.peek() {
103                Token::Symbol('*') => vibesql_ast::BinaryOperator::Multiply,
104                Token::Symbol('/') => vibesql_ast::BinaryOperator::Divide,
105                Token::Symbol('%') => vibesql_ast::BinaryOperator::Modulo,
106                Token::Keyword(Keyword::Div) => vibesql_ast::BinaryOperator::IntegerDivide,
107                _ => break,
108            };
109            self.advance();
110
111            let right = self.parse_unary_expression()?;
112            left = vibesql_ast::Expression::BinaryOp { op, left: Box::new(left), right: Box::new(right) };
113        }
114
115        Ok(left)
116    }
117
118    /// Parse comparison expression (handles =, <, >, <=, >=, !=, <>, IN, BETWEEN, LIKE, IS NULL)
119    /// These operators have lower precedence than arithmetic operators
120    pub(super) fn parse_comparison_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
121        let mut left = self.parse_additive_expression()?;
122
123        // Check for IN operator (including NOT IN) and BETWEEN (including NOT BETWEEN)
124        if self.peek_keyword(Keyword::Not) {
125            // Peek ahead to see if it's "NOT IN" or "NOT BETWEEN"
126            let saved_pos = self.position;
127            self.advance(); // consume NOT
128
129            if self.peek_keyword(Keyword::In) {
130                // It's NOT IN
131                self.consume_keyword(Keyword::In)?;
132
133                // Expect opening paren
134                self.expect_token(Token::LParen)?;
135
136                // Check if it's a subquery (SELECT ...) or a value list
137                if self.peek_keyword(Keyword::Select) {
138                    // It's a subquery: NOT IN (SELECT ...)
139                    let subquery = self.parse_select_statement()?;
140                    self.expect_token(Token::RParen)?;
141
142                    return Ok(vibesql_ast::Expression::In {
143                        expr: Box::new(left),
144                        subquery: Box::new(subquery),
145                        negated: true,
146                    });
147                } else {
148                    // It's a value list: NOT IN (val1, val2, ...)
149                    let values = self.parse_expression_list()?;
150                    self.expect_token(Token::RParen)?;
151
152                    // Empty IN lists are allowed per SQL:1999 (evaluates to TRUE for NOT IN)
153                    return Ok(vibesql_ast::Expression::InList {
154                        expr: Box::new(left),
155                        values,
156                        negated: true,
157                    });
158                }
159            } else if self.peek_keyword(Keyword::Between) {
160                // It's NOT BETWEEN
161                self.consume_keyword(Keyword::Between)?;
162
163                // Check for optional ASYMMETRIC or SYMMETRIC
164                let symmetric = if self.peek_keyword(Keyword::Symmetric) {
165                    self.consume_keyword(Keyword::Symmetric)?;
166                    true
167                } else {
168                    // ASYMMETRIC is default, but can be explicitly specified
169                    if self.peek_keyword(Keyword::Asymmetric) {
170                        self.consume_keyword(Keyword::Asymmetric)?;
171                    }
172                    false
173                };
174
175                // Parse low AND high
176                let low = self.parse_additive_expression()?;
177                self.consume_keyword(Keyword::And)?;
178                let high = self.parse_additive_expression()?;
179
180                return Ok(vibesql_ast::Expression::Between {
181                    expr: Box::new(left),
182                    low: Box::new(low),
183                    high: Box::new(high),
184                    negated: true,
185                    symmetric,
186                });
187            } else if self.peek_keyword(Keyword::Like) {
188                // It's NOT LIKE
189                self.consume_keyword(Keyword::Like)?;
190
191                // Parse pattern expression
192                let pattern = self.parse_additive_expression()?;
193
194                return Ok(vibesql_ast::Expression::Like {
195                    expr: Box::new(left),
196                    pattern: Box::new(pattern),
197                    negated: true,
198                });
199            } else {
200                // Not "NOT IN", "NOT BETWEEN", or "NOT LIKE", restore position and continue
201                // Note: NOT EXISTS is handled in parse_primary_expression()
202                self.position = saved_pos;
203            }
204        } else if self.peek_keyword(Keyword::In) {
205            // It's IN (not negated)
206            self.consume_keyword(Keyword::In)?;
207
208            // Expect opening paren
209            self.expect_token(Token::LParen)?;
210
211            // Check if it's a subquery (SELECT ...) or a value list
212            if self.peek_keyword(Keyword::Select) {
213                // It's a subquery: IN (SELECT ...)
214                let subquery = self.parse_select_statement()?;
215                self.expect_token(Token::RParen)?;
216
217                return Ok(vibesql_ast::Expression::In {
218                    expr: Box::new(left),
219                    subquery: Box::new(subquery),
220                    negated: false,
221                });
222            } else {
223                // It's a value list: IN (val1, val2, ...)
224                let values = self.parse_expression_list()?;
225                self.expect_token(Token::RParen)?;
226
227                // Empty IN lists are allowed per SQL:1999 (evaluates to FALSE)
228                return Ok(vibesql_ast::Expression::InList {
229                    expr: Box::new(left),
230                    values,
231                    negated: false,
232                });
233            }
234        } else if self.peek_keyword(Keyword::Between) {
235            // It's BETWEEN (not negated)
236            self.consume_keyword(Keyword::Between)?;
237
238            // Check for optional ASYMMETRIC or SYMMETRIC
239            let symmetric = if self.peek_keyword(Keyword::Symmetric) {
240                self.consume_keyword(Keyword::Symmetric)?;
241                true
242            } else {
243                // ASYMMETRIC is default, but can be explicitly specified
244                if self.peek_keyword(Keyword::Asymmetric) {
245                    self.consume_keyword(Keyword::Asymmetric)?;
246                }
247                false
248            };
249
250            // Parse low AND high
251            let low = self.parse_additive_expression()?;
252            self.consume_keyword(Keyword::And)?;
253            let high = self.parse_additive_expression()?;
254
255            return Ok(vibesql_ast::Expression::Between {
256                expr: Box::new(left),
257                low: Box::new(low),
258                high: Box::new(high),
259                negated: false,
260                symmetric,
261            });
262        } else if self.peek_keyword(Keyword::Like) {
263            // It's LIKE (not negated)
264            self.consume_keyword(Keyword::Like)?;
265
266            // Parse pattern expression
267            let pattern = self.parse_additive_expression()?;
268
269            return Ok(vibesql_ast::Expression::Like {
270                expr: Box::new(left),
271                pattern: Box::new(pattern),
272                negated: false,
273            });
274        }
275
276        // Check for comparison operators (both single-char and multi-char)
277        // Note: Exclude || (concat) operator which should be handled in additive expression
278        let is_comparison = match self.peek() {
279            Token::Symbol('=') | Token::Symbol('<') | Token::Symbol('>') => true,
280            Token::Operator(op) => !matches!(op, crate::token::MultiCharOperator::Concat),
281            _ => false,
282        };
283
284        if is_comparison {
285            let op = match self.peek() {
286                Token::Symbol('=') => vibesql_ast::BinaryOperator::Equal,
287                Token::Symbol('<') => vibesql_ast::BinaryOperator::LessThan,
288                Token::Symbol('>') => vibesql_ast::BinaryOperator::GreaterThan,
289                Token::Operator(op) => {
290                    use crate::token::MultiCharOperator;
291                    match op {
292                        MultiCharOperator::LessEqual => vibesql_ast::BinaryOperator::LessThanOrEqual,
293                        MultiCharOperator::GreaterEqual => {
294                            vibesql_ast::BinaryOperator::GreaterThanOrEqual
295                        }
296                        MultiCharOperator::NotEqual | MultiCharOperator::NotEqualAlt => {
297                            vibesql_ast::BinaryOperator::NotEqual
298                        }
299                        MultiCharOperator::Concat => {
300                            return Err(ParseError {
301                                message: format!("Unexpected operator: {}", op),
302                            })
303                        }
304                    }
305                }
306                _ => unreachable!(),
307            };
308            self.advance();
309
310            // Check for quantified comparison (ALL, ANY, SOME)
311            if self.peek_keyword(Keyword::All)
312                || self.peek_keyword(Keyword::Any)
313                || self.peek_keyword(Keyword::Some)
314            {
315                let quantifier = if self.peek_keyword(Keyword::All) {
316                    self.consume_keyword(Keyword::All)?;
317                    vibesql_ast::Quantifier::All
318                } else if self.peek_keyword(Keyword::Any) {
319                    self.consume_keyword(Keyword::Any)?;
320                    vibesql_ast::Quantifier::Any
321                } else {
322                    self.consume_keyword(Keyword::Some)?;
323                    vibesql_ast::Quantifier::Some
324                };
325
326                // Expect opening paren
327                self.expect_token(Token::LParen)?;
328
329                // Parse subquery
330                let subquery = self.parse_select_statement()?;
331
332                // Expect closing paren
333                self.expect_token(Token::RParen)?;
334
335                return Ok(vibesql_ast::Expression::QuantifiedComparison {
336                    expr: Box::new(left),
337                    op,
338                    quantifier,
339                    subquery: Box::new(subquery),
340                });
341            }
342
343            let right = self.parse_additive_expression()?;
344            left = vibesql_ast::Expression::BinaryOp { op, left: Box::new(left), right: Box::new(right) };
345        }
346
347        // Check for IS NULL / IS NOT NULL
348        if self.peek_keyword(Keyword::Is) {
349            self.consume_keyword(Keyword::Is)?;
350
351            // Check for NOT
352            let negated = if self.peek_keyword(Keyword::Not) {
353                self.consume_keyword(Keyword::Not)?;
354                true
355            } else {
356                false
357            };
358
359            // Expect NULL
360            self.expect_keyword(Keyword::Null)?;
361
362            left = vibesql_ast::Expression::IsNull { expr: Box::new(left), negated };
363        }
364
365        Ok(left)
366    }
367
368    /// Parse unary expression (handles unary +/- operators)
369    pub(super) fn parse_unary_expression(&mut self) -> Result<vibesql_ast::Expression, ParseError> {
370        // Check for unary + or -
371        match self.peek() {
372            Token::Symbol('+') => {
373                self.advance();
374                let expr = self.parse_unary_expression()?;
375                Ok(vibesql_ast::Expression::UnaryOp { op: vibesql_ast::UnaryOperator::Plus, expr: Box::new(expr) })
376            }
377            Token::Symbol('-') => {
378                self.advance();
379                let expr = self.parse_unary_expression()?;
380                Ok(vibesql_ast::Expression::UnaryOp { op: vibesql_ast::UnaryOperator::Minus, expr: Box::new(expr) })
381            }
382            _ => self.parse_primary_expression(),
383        }
384    }
385
386    /// Parse a comma-separated list of expressions
387    /// Used for IN (val1, val2, ...) and function arguments
388    /// Does NOT consume the opening or closing parentheses
389    pub fn parse_expression_list(&mut self) -> Result<Vec<vibesql_ast::Expression>, ParseError> {
390        let mut expressions = Vec::new();
391
392        // Check for empty list (SQLite compatibility)
393        if matches!(self.peek(), Token::RParen) {
394            return Ok(expressions);
395        }
396
397        // Parse first expression
398        expressions.push(self.parse_expression()?);
399
400        // Parse remaining expressions
401        while matches!(self.peek(), Token::Comma) {
402            self.advance(); // consume comma
403            expressions.push(self.parse_expression()?);
404        }
405
406        Ok(expressions)
407    }
408}