Skip to main content

spreadsheet_formulas/
parser.rs

1/// Formula parser — parses tokens into an AST.
2/// Recursive descent parser with operator precedence.
3
4use crate::ast::{BinOp, Expr, UnaryOp};
5use crate::tokenizer::{Token, tokenize};
6
7pub struct Parser {
8    tokens: Vec<Token>,
9    pos: usize,
10}
11
12impl Parser {
13    pub fn new(tokens: Vec<Token>) -> Self {
14        Parser { tokens, pos: 0 }
15    }
16
17    fn peek(&self) -> Option<&Token> {
18        self.tokens.get(self.pos)
19    }
20
21    fn advance(&mut self) -> Option<Token> {
22        if self.pos < self.tokens.len() {
23            let t = self.tokens[self.pos].clone();
24            self.pos += 1;
25            Some(t)
26        } else {
27            None
28        }
29    }
30
31    fn expect(&mut self, expected: &Token) -> Result<(), String> {
32        let t = self.advance().ok_or("Unexpected end of input")?;
33        if &t != expected {
34            return Err(format!("Expected {:?}, got {:?}", expected, t));
35        }
36        Ok(())
37    }
38
39    /// Parse a formula string into an AST.
40    pub fn parse_formula(input: &str) -> Result<Expr, String> {
41        let trimmed = input.trim();
42        // Strip leading =
43        let formula = if trimmed.starts_with('=') { &trimmed[1..] } else { trimmed };
44        let tokens = tokenize(formula)?;
45        let mut parser = Parser::new(tokens);
46        let expr = parser.parse_comparison()?;
47        if parser.pos < parser.tokens.len() {
48            return Err(format!("Unexpected token at position {}", parser.pos));
49        }
50        Ok(expr)
51    }
52
53    // Precedence levels (lowest to highest):
54    // comparison: = != < > <= >=
55    // concat: &
56    // addition: + -
57    // multiplication: * /
58    // power: ^
59    // unary: -
60    // percent: %
61    // primary: number, string, cell, range, function, parens
62
63    fn parse_comparison(&mut self) -> Result<Expr, String> {
64        let mut left = self.parse_concat()?;
65        loop {
66            let op = match self.peek() {
67                Some(Token::Eq) => BinOp::Eq,
68                Some(Token::Neq) => BinOp::Neq,
69                Some(Token::Lt) => BinOp::Lt,
70                Some(Token::Gt) => BinOp::Gt,
71                Some(Token::Lte) => BinOp::Lte,
72                Some(Token::Gte) => BinOp::Gte,
73                _ => break,
74            };
75            self.advance();
76            let right = self.parse_concat()?;
77            left = Expr::BinaryOp {
78                op,
79                left: Box::new(left),
80                right: Box::new(right),
81            };
82        }
83        Ok(left)
84    }
85
86    fn parse_concat(&mut self) -> Result<Expr, String> {
87        let mut left = self.parse_addition()?;
88        loop {
89            if matches!(self.peek(), Some(Token::Amp)) {
90                self.advance();
91                let right = self.parse_addition()?;
92                left = Expr::BinaryOp {
93                    op: BinOp::Concat,
94                    left: Box::new(left),
95                    right: Box::new(right),
96                };
97            } else {
98                break;
99            }
100        }
101        Ok(left)
102    }
103
104    fn parse_addition(&mut self) -> Result<Expr, String> {
105        let mut left = self.parse_multiplication()?;
106        loop {
107            let op = match self.peek() {
108                Some(Token::Plus) => BinOp::Add,
109                Some(Token::Minus) => BinOp::Sub,
110                _ => break,
111            };
112            self.advance();
113            let right = self.parse_multiplication()?;
114            left = Expr::BinaryOp {
115                op,
116                left: Box::new(left),
117                right: Box::new(right),
118            };
119        }
120        Ok(left)
121    }
122
123    fn parse_multiplication(&mut self) -> Result<Expr, String> {
124        let mut left = self.parse_power()?;
125        loop {
126            let op = match self.peek() {
127                Some(Token::Star) => BinOp::Mul,
128                Some(Token::Slash) => BinOp::Div,
129                _ => break,
130            };
131            self.advance();
132            let right = self.parse_power()?;
133            left = Expr::BinaryOp {
134                op,
135                left: Box::new(left),
136                right: Box::new(right),
137            };
138        }
139        Ok(left)
140    }
141
142    fn parse_power(&mut self) -> Result<Expr, String> {
143        let mut left = self.parse_unary()?;
144        if matches!(self.peek(), Some(Token::Caret)) {
145            self.advance();
146            let right = self.parse_unary()?; // right-associative would recurse parse_power
147            left = Expr::BinaryOp {
148                op: BinOp::Pow,
149                left: Box::new(left),
150                right: Box::new(right),
151            };
152        }
153        Ok(left)
154    }
155
156    fn parse_unary(&mut self) -> Result<Expr, String> {
157        if matches!(self.peek(), Some(Token::Minus)) {
158            self.advance();
159            let operand = self.parse_unary()?;
160            return Ok(Expr::UnaryOp {
161                op: UnaryOp::Negate,
162                operand: Box::new(operand),
163            });
164        }
165        self.parse_percent()
166    }
167
168    fn parse_percent(&mut self) -> Result<Expr, String> {
169        let mut expr = self.parse_primary()?;
170        while matches!(self.peek(), Some(Token::Percent)) {
171            self.advance();
172            expr = Expr::Percent(Box::new(expr));
173        }
174        Ok(expr)
175    }
176
177    fn parse_primary(&mut self) -> Result<Expr, String> {
178        let token = self.advance().ok_or("Unexpected end of input")?;
179        match token {
180            Token::Number(n) => Ok(Expr::Number(n)),
181            Token::String(s) => Ok(Expr::String(s)),
182            Token::CellRef(c) => Ok(Expr::CellRef(c)),
183            Token::Range(r) => Ok(Expr::Range(r)),
184            Token::LParen => {
185                let expr = self.parse_comparison()?;
186                self.expect(&Token::RParen).map_err(|e| format!("{} (expected closing paren)", e))?;
187                Ok(expr)
188            }
189            Token::Ident(name) => {
190                // Function call
191                if matches!(self.peek(), Some(Token::LParen)) {
192                    self.advance(); // consume (
193                    let mut args = Vec::new();
194                    if !matches!(self.peek(), Some(Token::RParen)) {
195                        args.push(self.parse_comparison()?);
196                        while matches!(self.peek(), Some(Token::Comma)) {
197                            self.advance();
198                            args.push(self.parse_comparison()?);
199                        }
200                    }
201                    self.expect(&Token::RParen).map_err(|e| format!("{} (in function {})", e, name))?;
202                    Ok(Expr::FunctionCall { name, args })
203                } else {
204                    // Boolean literals
205                    if name == "TRUE" {
206                        Ok(Expr::Boolean(true))
207                    } else if name == "FALSE" {
208                        Ok(Expr::Boolean(false))
209                    } else {
210                        Err(format!("Unknown identifier: {}", name))
211                    }
212                }
213            }
214            _ => Err(format!("Unexpected token: {:?}", token)),
215        }
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn parse_number() {
225        let expr = Parser::parse_formula("42").unwrap();
226        assert_eq!(expr, Expr::Number(42.0));
227    }
228
229    #[test]
230    fn parse_string() {
231        let expr = Parser::parse_formula("\"hello\"").unwrap();
232        assert_eq!(expr, Expr::String("hello".into()));
233    }
234
235    #[test]
236    fn parse_binary_op() {
237        let expr = Parser::parse_formula("1+2").unwrap();
238        if let Expr::BinaryOp { op, .. } = &expr {
239            assert_eq!(*op, BinOp::Add);
240        } else {
241            panic!("Expected BinaryOp");
242        }
243    }
244
245    #[test]
246    fn parse_precedence() {
247        // 1+2*3 should be 1+(2*3)
248        let expr = Parser::parse_formula("1+2*3").unwrap();
249        if let Expr::BinaryOp { op, left, right } = &expr {
250            assert_eq!(*op, BinOp::Add);
251            assert!(matches!(**left, Expr::Number(1.0)));
252            assert!(matches!(**right, Expr::BinaryOp { op: BinOp::Mul, .. }));
253        } else {
254            panic!("Expected BinaryOp");
255        }
256    }
257
258    #[test]
259    fn parse_function_call() {
260        let expr = Parser::parse_formula("SUM(A1:A10)").unwrap();
261        if let Expr::FunctionCall { name, args } = &expr {
262            assert_eq!(name, "SUM");
263            assert_eq!(args.len(), 1);
264        } else {
265            panic!("Expected FunctionCall");
266        }
267    }
268
269    #[test]
270    fn parse_nested_function() {
271        let expr = Parser::parse_formula("EVOLVE(A1:A10, 100)").unwrap();
272        if let Expr::FunctionCall { name, args } = &expr {
273            assert_eq!(name, "EVOLVE");
274            assert_eq!(args.len(), 2);
275        } else {
276            panic!("Expected FunctionCall");
277        }
278    }
279
280    #[test]
281    fn parse_cell_ref() {
282        let expr = Parser::parse_formula("A1").unwrap();
283        assert!(matches!(expr, Expr::CellRef(_)));
284    }
285
286    #[test]
287    fn parse_range() {
288        let expr = Parser::parse_formula("A1:B5").unwrap();
289        assert!(matches!(expr, Expr::Range(_)));
290    }
291
292    #[test]
293    fn parse_unary_negate() {
294        let expr = Parser::parse_formula("-5").unwrap();
295        assert!(matches!(expr, Expr::UnaryOp { op: UnaryOp::Negate, .. }));
296    }
297
298    #[test]
299    fn parse_percent() {
300        let expr = Parser::parse_formula("50%").unwrap();
301        assert!(matches!(expr, Expr::Percent(_)));
302    }
303
304    #[test]
305    fn parse_with_leading_equals() {
306        let expr = Parser::parse_formula("=SUM(A1:A10)").unwrap();
307        assert!(matches!(expr, Expr::FunctionCall { .. }));
308    }
309
310    #[test]
311    fn parse_boolean() {
312        assert_eq!(Parser::parse_formula("TRUE").unwrap(), Expr::Boolean(true));
313        assert_eq!(Parser::parse_formula("FALSE").unwrap(), Expr::Boolean(false));
314    }
315
316    #[test]
317    fn parse_comparison() {
318        let expr = Parser::parse_formula("A1>5").unwrap();
319        if let Expr::BinaryOp { op, .. } = &expr {
320            assert_eq!(*op, BinOp::Gt);
321        } else {
322            panic!("Expected BinaryOp");
323        }
324    }
325
326    #[test]
327    fn parse_power() {
328        let expr = Parser::parse_formula("2^3").unwrap();
329        if let Expr::BinaryOp { op, .. } = &expr {
330            assert_eq!(*op, BinOp::Pow);
331        } else {
332            panic!("Expected BinaryOp");
333        }
334    }
335}