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 and qualified column names
63    // Method calls have the same precedence as multiplication
64    while matches!(parser.current_token(), Token::Dot) {
65        debug!("Found dot operator");
66        parser.advance();
67
68        if let Token::Identifier(name) = parser.current_token() {
69            let name_str = name.clone();
70            parser.advance();
71
72            if matches!(parser.current_token(), Token::LeftParen) {
73                // This is a method call
74                log_parse_decision(
75                    "parse_multiplicative",
76                    parser.current_token(),
77                    &format!("Method call '{}' detected", name_str),
78                );
79
80                parser.advance();
81                let args = parser.parse_method_args()?;
82                parser.consume(Token::RightParen)?;
83
84                // Support chained method calls
85                left = match left {
86                    SqlExpression::Column(obj) => {
87                        // First method call on a column
88                        debug!(
89                            column = %obj,
90                            method = %name_str,
91                            "Creating method call on column"
92                        );
93                        SqlExpression::MethodCall {
94                            object: obj,
95                            method: name_str,
96                            args,
97                        }
98                    }
99                    SqlExpression::MethodCall { .. } | SqlExpression::ChainedMethodCall { .. } => {
100                        // Chained method call on a previous method call
101                        debug!(
102                            method = %name_str,
103                            "Creating chained method call"
104                        );
105                        SqlExpression::ChainedMethodCall {
106                            base: Box::new(left),
107                            method: name_str,
108                            args,
109                        }
110                    }
111                    _ => {
112                        // Method call on any other expression
113                        debug!(
114                            method = %name_str,
115                            "Creating method call on expression"
116                        );
117                        SqlExpression::ChainedMethodCall {
118                            base: Box::new(left),
119                            method: name_str,
120                            args,
121                        }
122                    }
123                };
124            } else {
125                // This is a qualified column name (table.column or alias.column)
126                // Combine the left expression with the column name
127                left = match left {
128                    SqlExpression::Column(table_or_alias) => {
129                        debug!(
130                            table = %table_or_alias,
131                            column = %name_str,
132                            "Creating qualified column reference"
133                        );
134                        SqlExpression::Column(format!("{}.{}", table_or_alias, name_str))
135                    }
136                    _ => {
137                        // If left is not a simple column, this is an error
138                        return Err(format!(
139                            "Invalid qualified column reference with expression"
140                        ));
141                    }
142                };
143            }
144        } else {
145            return Err("Expected identifier after '.'".to_string());
146        }
147    }
148
149    // Handle multiplicative binary operators
150    while matches!(
151        parser.current_token(),
152        Token::Star | Token::Divide | Token::Modulo
153    ) {
154        let op = match parser.current_token() {
155            Token::Star => "*",
156            Token::Divide => "/",
157            Token::Modulo => "%",
158            _ => unreachable!(),
159        };
160
161        log_parse_decision(
162            "parse_multiplicative",
163            parser.current_token(),
164            &format!("Binary operator '{}' found, parsing right operand", op),
165        );
166
167        parser.advance();
168        let right = parser.parse_primary()?;
169
170        debug!(operator = op, "Creating multiplicative binary operation");
171
172        left = SqlExpression::BinaryOp {
173            left: Box::new(left),
174            op: op.to_string(),
175            right: Box::new(right),
176        };
177    }
178
179    let result = Ok(left);
180    trace_parse_exit("parse_multiplicative", &result);
181    result
182}
183
184/// Trait that parsers must implement to use arithmetic expression parsing
185pub trait ParseArithmetic {
186    fn current_token(&self) -> &Token;
187    fn advance(&mut self);
188    fn consume(&mut self, expected: Token) -> Result<(), String>;
189
190    // These methods are called from arithmetic parsing
191    fn parse_primary(&mut self) -> Result<SqlExpression, String>;
192    fn parse_multiplicative(&mut self) -> Result<SqlExpression, String>;
193    fn parse_method_args(&mut self) -> Result<Vec<SqlExpression>, String>;
194}