sql_cli/sql/parser/expressions/
arithmetic.rs

1// Arithmetic expression parsing
2// Handles additive (+, -) and multiplicative (*, /, %) expressions
3// Also handles method calls (e.g., column.upper()) as part of multiplicative precedence
4
5use crate::sql::parser::ast::SqlExpression;
6use crate::sql::parser::lexer::Token;
7use tracing::debug;
8
9use super::{log_parse_decision, trace_parse_entry, trace_parse_exit};
10
11/// Parse an additive expression (+ and - operators)
12/// This handles left-associative binary operators at the additive precedence level
13pub fn parse_additive<P>(parser: &mut P) -> Result<SqlExpression, String>
14where
15    P: ParseArithmetic + ?Sized,
16{
17    trace_parse_entry("parse_additive", parser.current_token());
18
19    let mut left = parser.parse_multiplicative()?;
20
21    while matches!(parser.current_token(), Token::Plus | Token::Minus) {
22        let op = match parser.current_token() {
23            Token::Plus => "+",
24            Token::Minus => "-",
25            _ => unreachable!(),
26        };
27
28        log_parse_decision(
29            "parse_additive",
30            parser.current_token(),
31            &format!("Binary operator '{}' found, parsing right operand", op),
32        );
33
34        parser.advance();
35        let right = parser.parse_multiplicative()?;
36
37        debug!(operator = op, "Creating additive binary operation");
38
39        left = SqlExpression::BinaryOp {
40            left: Box::new(left),
41            op: op.to_string(),
42            right: Box::new(right),
43        };
44    }
45
46    let result = Ok(left);
47    trace_parse_exit("parse_additive", &result);
48    result
49}
50
51/// Parse a multiplicative expression (*, /, % operators and method calls)
52/// This handles left-associative binary operators at the multiplicative precedence level
53/// Also handles method calls (.) which have the same precedence as multiplication
54pub fn parse_multiplicative<P>(parser: &mut P) -> Result<SqlExpression, String>
55where
56    P: ParseArithmetic + ?Sized,
57{
58    trace_parse_entry("parse_multiplicative", parser.current_token());
59
60    let mut left = parser.parse_primary()?;
61
62    // Handle method calls on the primary expression
63    // Method calls have the same precedence as multiplication
64    while matches!(parser.current_token(), Token::Dot) {
65        debug!("Found dot operator, parsing method call");
66        parser.advance();
67
68        if let Token::Identifier(method) = parser.current_token() {
69            let method_name = method.clone();
70            parser.advance();
71
72            if matches!(parser.current_token(), Token::LeftParen) {
73                log_parse_decision(
74                    "parse_multiplicative",
75                    parser.current_token(),
76                    &format!("Method call '{}' detected", method_name),
77                );
78
79                parser.advance();
80                let args = parser.parse_method_args()?;
81                parser.consume(Token::RightParen)?;
82
83                // Support chained method calls
84                left = match left {
85                    SqlExpression::Column(obj) => {
86                        // First method call on a column
87                        debug!(
88                            column = %obj,
89                            method = %method_name,
90                            "Creating method call on column"
91                        );
92                        SqlExpression::MethodCall {
93                            object: obj,
94                            method: method_name,
95                            args,
96                        }
97                    }
98                    SqlExpression::MethodCall { .. } | SqlExpression::ChainedMethodCall { .. } => {
99                        // Chained method call on a previous method call
100                        debug!(
101                            method = %method_name,
102                            "Creating chained method call"
103                        );
104                        SqlExpression::ChainedMethodCall {
105                            base: Box::new(left),
106                            method: method_name,
107                            args,
108                        }
109                    }
110                    _ => {
111                        // Method call on any other expression
112                        debug!(
113                            method = %method_name,
114                            "Creating method call on expression"
115                        );
116                        SqlExpression::ChainedMethodCall {
117                            base: Box::new(left),
118                            method: method_name,
119                            args,
120                        }
121                    }
122                };
123            } else {
124                return Err(format!("Expected '(' after method name '{method_name}'"));
125            }
126        } else {
127            return Err("Expected method name after '.'".to_string());
128        }
129    }
130
131    // Handle multiplicative binary operators
132    while matches!(
133        parser.current_token(),
134        Token::Star | Token::Divide | Token::Modulo
135    ) {
136        let op = match parser.current_token() {
137            Token::Star => "*",
138            Token::Divide => "/",
139            Token::Modulo => "%",
140            _ => unreachable!(),
141        };
142
143        log_parse_decision(
144            "parse_multiplicative",
145            parser.current_token(),
146            &format!("Binary operator '{}' found, parsing right operand", op),
147        );
148
149        parser.advance();
150        let right = parser.parse_primary()?;
151
152        debug!(operator = op, "Creating multiplicative binary operation");
153
154        left = SqlExpression::BinaryOp {
155            left: Box::new(left),
156            op: op.to_string(),
157            right: Box::new(right),
158        };
159    }
160
161    let result = Ok(left);
162    trace_parse_exit("parse_multiplicative", &result);
163    result
164}
165
166/// Trait that parsers must implement to use arithmetic expression parsing
167pub trait ParseArithmetic {
168    fn current_token(&self) -> &Token;
169    fn advance(&mut self);
170    fn consume(&mut self, expected: Token) -> Result<(), String>;
171
172    // These methods are called from arithmetic parsing
173    fn parse_primary(&mut self) -> Result<SqlExpression, String>;
174    fn parse_multiplicative(&mut self) -> Result<SqlExpression, String>;
175    fn parse_method_args(&mut self) -> Result<Vec<SqlExpression>, String>;
176}