Skip to main content

showcase_calculator/core/
parser.rs

1//! Expression parser with 100% test coverage
2//!
3//! Probar: Error prevention - Type-safe tokens prevent invalid syntax
4
5use crate::core::{CalcError, CalcResult, Operation};
6
7/// Token types from lexical analysis
8#[derive(Debug, Clone, PartialEq)]
9pub enum Token {
10    /// Numeric literal
11    Number(f64),
12    /// Binary operator
13    Operator(Operation),
14    /// Left parenthesis
15    LeftParen,
16    /// Right parenthesis
17    RightParen,
18}
19
20impl Token {
21    /// Returns true if this token is an operator
22    #[must_use]
23    pub const fn is_operator(&self) -> bool {
24        matches!(self, Self::Operator(_))
25    }
26
27    /// Returns true if this token is a number
28    #[must_use]
29    pub const fn is_number(&self) -> bool {
30        matches!(self, Self::Number(_))
31    }
32
33    /// Returns true if this token is a left parenthesis
34    #[must_use]
35    pub const fn is_left_paren(&self) -> bool {
36        matches!(self, Self::LeftParen)
37    }
38
39    /// Returns true if this token is a right parenthesis
40    #[must_use]
41    pub const fn is_right_paren(&self) -> bool {
42        matches!(self, Self::RightParen)
43    }
44}
45
46/// Abstract Syntax Tree node
47#[derive(Debug, Clone, PartialEq)]
48pub enum AstNode {
49    /// Numeric literal
50    Number(f64),
51    /// Binary operation
52    BinaryOp {
53        /// Left operand
54        left: Box<AstNode>,
55        /// Operator
56        op: Operation,
57        /// Right operand
58        right: Box<AstNode>,
59    },
60    /// Unary negation
61    Negate(Box<AstNode>),
62}
63
64impl AstNode {
65    /// Creates a new number node
66    #[must_use]
67    pub fn number(value: f64) -> Self {
68        Self::Number(value)
69    }
70
71    /// Creates a new binary operation node
72    #[must_use]
73    pub fn binary(left: AstNode, op: Operation, right: AstNode) -> Self {
74        Self::BinaryOp {
75            left: Box::new(left),
76            op,
77            right: Box::new(right),
78        }
79    }
80
81    /// Creates a new negation node
82    #[must_use]
83    pub fn negate(inner: AstNode) -> Self {
84        Self::Negate(Box::new(inner))
85    }
86}
87
88/// Tokenizer for converting expression strings to tokens
89#[derive(Debug)]
90pub struct Tokenizer<'a> {
91    input: &'a str,
92    pos: usize,
93}
94
95impl<'a> Tokenizer<'a> {
96    /// Creates a new tokenizer for the given input
97    #[must_use]
98    pub fn new(input: &'a str) -> Self {
99        Self { input, pos: 0 }
100    }
101
102    /// Returns the remaining input
103    #[must_use]
104    pub fn remaining(&self) -> &str {
105        &self.input[self.pos..]
106    }
107
108    /// Tokenizes the entire input
109    pub fn tokenize(&mut self) -> CalcResult<Vec<Token>> {
110        let mut tokens = Vec::new();
111        while let Some(token) = self.next_token()? {
112            tokens.push(token);
113        }
114        Ok(tokens)
115    }
116
117    /// Returns the next token, or None if at end of input
118    pub fn next_token(&mut self) -> CalcResult<Option<Token>> {
119        self.skip_whitespace();
120
121        if self.pos >= self.input.len() {
122            return Ok(None);
123        }
124
125        let ch = self
126            .current_char()
127            .ok_or_else(|| CalcError::ParseError("Unexpected end of input".into()))?;
128
129        let token = match ch {
130            '0'..='9' | '.' => self.read_number()?,
131            '+' => {
132                self.advance();
133                Token::Operator(Operation::Add)
134            }
135            '-' => {
136                self.advance();
137                Token::Operator(Operation::Subtract)
138            }
139            '*' => {
140                self.advance();
141                Token::Operator(Operation::Multiply)
142            }
143            '/' => {
144                self.advance();
145                Token::Operator(Operation::Divide)
146            }
147            '%' => {
148                self.advance();
149                Token::Operator(Operation::Modulo)
150            }
151            '^' => {
152                self.advance();
153                Token::Operator(Operation::Power)
154            }
155            '(' => {
156                self.advance();
157                Token::LeftParen
158            }
159            ')' => {
160                self.advance();
161                Token::RightParen
162            }
163            _ => {
164                return Err(CalcError::ParseError(format!(
165                    "Unexpected character: '{ch}'"
166                )));
167            }
168        };
169
170        Ok(Some(token))
171    }
172
173    fn current_char(&self) -> Option<char> {
174        self.input[self.pos..].chars().next()
175    }
176
177    fn advance(&mut self) {
178        if let Some(ch) = self.current_char() {
179            self.pos += ch.len_utf8();
180        }
181    }
182
183    fn skip_whitespace(&mut self) {
184        while let Some(ch) = self.current_char() {
185            if ch.is_whitespace() {
186                self.advance();
187            } else {
188                break;
189            }
190        }
191    }
192
193    fn read_number(&mut self) -> CalcResult<Token> {
194        let start = self.pos;
195        let mut has_dot = false;
196
197        while let Some(ch) = self.current_char() {
198            if ch.is_ascii_digit() {
199                self.advance();
200            } else if ch == '.' && !has_dot {
201                has_dot = true;
202                self.advance();
203            } else {
204                break;
205            }
206        }
207
208        let num_str = &self.input[start..self.pos];
209        let value: f64 = num_str
210            .parse()
211            .map_err(|_| CalcError::ParseError(format!("Invalid number: '{num_str}'")))?;
212
213        Ok(Token::Number(value))
214    }
215}
216
217/// Recursive descent parser for expressions
218///
219/// Grammar:
220/// ```text
221/// expression ::= term (('+' | '-') term)*
222/// term       ::= factor (('*' | '/' | '%') factor)*
223/// factor     ::= base ('^' factor)?    // Right associative
224/// base       ::= '-' base | primary
225/// primary    ::= NUMBER | '(' expression ')'
226/// ```
227#[derive(Debug)]
228pub struct Parser {
229    tokens: Vec<Token>,
230    pos: usize,
231}
232
233impl Parser {
234    /// Creates a new parser from tokens
235    #[must_use]
236    pub fn new(tokens: Vec<Token>) -> Self {
237        Self { tokens, pos: 0 }
238    }
239
240    /// Parses a string expression into an AST
241    pub fn parse_str(input: &str) -> CalcResult<AstNode> {
242        let trimmed = input.trim();
243        if trimmed.is_empty() {
244            return Err(CalcError::EmptyExpression);
245        }
246
247        let mut tokenizer = Tokenizer::new(trimmed);
248        let tokens = tokenizer.tokenize()?;
249
250        if tokens.is_empty() {
251            return Err(CalcError::EmptyExpression);
252        }
253
254        let mut parser = Self::new(tokens);
255        let ast = parser.parse_expression()?;
256
257        // Ensure all tokens consumed
258        if parser.pos < parser.tokens.len() {
259            return Err(CalcError::ParseError(format!(
260                "Unexpected token at position {}",
261                parser.pos
262            )));
263        }
264
265        Ok(ast)
266    }
267
268    /// Parses tokens into an AST
269    pub fn parse(&mut self) -> CalcResult<AstNode> {
270        if self.tokens.is_empty() {
271            return Err(CalcError::EmptyExpression);
272        }
273        self.parse_expression()
274    }
275
276    fn current(&self) -> Option<&Token> {
277        self.tokens.get(self.pos)
278    }
279
280    fn advance(&mut self) -> Option<&Token> {
281        let token = self.tokens.get(self.pos);
282        if token.is_some() {
283            self.pos += 1;
284        }
285        token
286    }
287
288    fn parse_expression(&mut self) -> CalcResult<AstNode> {
289        let mut left = self.parse_term()?;
290
291        while let Some(token) = self.current() {
292            let op = match token {
293                Token::Operator(Operation::Add) => Operation::Add,
294                Token::Operator(Operation::Subtract) => Operation::Subtract,
295                _ => break,
296            };
297            self.advance();
298            let right = self.parse_term()?;
299            left = AstNode::binary(left, op, right);
300        }
301
302        Ok(left)
303    }
304
305    fn parse_term(&mut self) -> CalcResult<AstNode> {
306        let mut left = self.parse_factor()?;
307
308        while let Some(token) = self.current() {
309            let op = match token {
310                Token::Operator(Operation::Multiply) => Operation::Multiply,
311                Token::Operator(Operation::Divide) => Operation::Divide,
312                Token::Operator(Operation::Modulo) => Operation::Modulo,
313                _ => break,
314            };
315            self.advance();
316            let right = self.parse_factor()?;
317            left = AstNode::binary(left, op, right);
318        }
319
320        Ok(left)
321    }
322
323    fn parse_factor(&mut self) -> CalcResult<AstNode> {
324        let base = self.parse_base()?;
325
326        // Power is right-associative
327        if matches!(self.current(), Some(Token::Operator(Operation::Power))) {
328            self.advance();
329            let exponent = self.parse_factor()?; // Recurse for right associativity
330            return Ok(AstNode::binary(base, Operation::Power, exponent));
331        }
332
333        Ok(base)
334    }
335
336    fn parse_base(&mut self) -> CalcResult<AstNode> {
337        // Handle unary minus
338        if matches!(self.current(), Some(Token::Operator(Operation::Subtract))) {
339            self.advance();
340            let inner = self.parse_base()?;
341            return Ok(AstNode::negate(inner));
342        }
343
344        self.parse_primary()
345    }
346
347    fn parse_primary(&mut self) -> CalcResult<AstNode> {
348        let token = self
349            .advance()
350            .ok_or_else(|| CalcError::ParseError("Unexpected end of expression".into()))?;
351
352        match token {
353            Token::Number(n) => Ok(AstNode::number(*n)),
354            Token::LeftParen => {
355                let expr = self.parse_expression()?;
356                match self.advance() {
357                    Some(Token::RightParen) => Ok(expr),
358                    Some(t) => Err(CalcError::ParseError(format!(
359                        "Expected ')' but found {t:?}"
360                    ))),
361                    None => Err(CalcError::ParseError("Unclosed parenthesis".into())),
362                }
363            }
364            _ => Err(CalcError::ParseError(format!(
365                "Unexpected token: {token:?}"
366            ))),
367        }
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374
375    // ===== Token tests =====
376
377    #[test]
378    fn test_token_is_operator() {
379        assert!(Token::Operator(Operation::Add).is_operator());
380        assert!(!Token::Number(5.0).is_operator());
381        assert!(!Token::LeftParen.is_operator());
382    }
383
384    #[test]
385    fn test_token_is_number() {
386        assert!(Token::Number(5.0).is_number());
387        assert!(!Token::Operator(Operation::Add).is_number());
388    }
389
390    #[test]
391    fn test_token_is_left_paren() {
392        assert!(Token::LeftParen.is_left_paren());
393        assert!(!Token::RightParen.is_left_paren());
394    }
395
396    #[test]
397    fn test_token_is_right_paren() {
398        assert!(Token::RightParen.is_right_paren());
399        assert!(!Token::LeftParen.is_right_paren());
400    }
401
402    // ===== AstNode tests =====
403
404    #[test]
405    fn test_ast_node_number() {
406        let node = AstNode::number(42.0);
407        assert_eq!(node, AstNode::Number(42.0));
408    }
409
410    #[test]
411    fn test_ast_node_binary() {
412        let node = AstNode::binary(AstNode::number(1.0), Operation::Add, AstNode::number(2.0));
413        match node {
414            AstNode::BinaryOp { left, op, right } => {
415                assert_eq!(*left, AstNode::Number(1.0));
416                assert_eq!(op, Operation::Add);
417                assert_eq!(*right, AstNode::Number(2.0));
418            }
419            _ => panic!("Expected BinaryOp"),
420        }
421    }
422
423    #[test]
424    fn test_ast_node_negate() {
425        let node = AstNode::negate(AstNode::number(5.0));
426        match node {
427            AstNode::Negate(inner) => {
428                assert_eq!(*inner, AstNode::Number(5.0));
429            }
430            _ => panic!("Expected Negate"),
431        }
432    }
433
434    // ===== Tokenizer tests =====
435
436    #[test]
437    fn test_tokenize_single_number() {
438        let mut t = Tokenizer::new("42");
439        let tokens = t.tokenize().unwrap();
440        assert_eq!(tokens, vec![Token::Number(42.0)]);
441    }
442
443    #[test]
444    fn test_tokenize_decimal_number() {
445        let mut t = Tokenizer::new("3.14");
446        let tokens = t.tokenize().unwrap();
447        assert_eq!(tokens, vec![Token::Number(3.14)]);
448    }
449
450    #[test]
451    fn test_tokenize_operators() {
452        let mut t = Tokenizer::new("+ - * / % ^");
453        let tokens = t.tokenize().unwrap();
454        assert_eq!(
455            tokens,
456            vec![
457                Token::Operator(Operation::Add),
458                Token::Operator(Operation::Subtract),
459                Token::Operator(Operation::Multiply),
460                Token::Operator(Operation::Divide),
461                Token::Operator(Operation::Modulo),
462                Token::Operator(Operation::Power),
463            ]
464        );
465    }
466
467    #[test]
468    fn test_tokenize_parentheses() {
469        let mut t = Tokenizer::new("()");
470        let tokens = t.tokenize().unwrap();
471        assert_eq!(tokens, vec![Token::LeftParen, Token::RightParen]);
472    }
473
474    #[test]
475    fn test_tokenize_expression() {
476        let mut t = Tokenizer::new("2 + 3 * 4");
477        let tokens = t.tokenize().unwrap();
478        assert_eq!(
479            tokens,
480            vec![
481                Token::Number(2.0),
482                Token::Operator(Operation::Add),
483                Token::Number(3.0),
484                Token::Operator(Operation::Multiply),
485                Token::Number(4.0),
486            ]
487        );
488    }
489
490    #[test]
491    fn test_tokenize_with_parens() {
492        let mut t = Tokenizer::new("(2 + 3) * 4");
493        let tokens = t.tokenize().unwrap();
494        assert_eq!(
495            tokens,
496            vec![
497                Token::LeftParen,
498                Token::Number(2.0),
499                Token::Operator(Operation::Add),
500                Token::Number(3.0),
501                Token::RightParen,
502                Token::Operator(Operation::Multiply),
503                Token::Number(4.0),
504            ]
505        );
506    }
507
508    #[test]
509    fn test_tokenize_no_spaces() {
510        let mut t = Tokenizer::new("1+2*3");
511        let tokens = t.tokenize().unwrap();
512        assert_eq!(tokens.len(), 5);
513    }
514
515    #[test]
516    fn test_tokenize_invalid_char() {
517        let mut t = Tokenizer::new("2 @ 3");
518        let result = t.tokenize();
519        assert!(matches!(result, Err(CalcError::ParseError(_))));
520    }
521
522    #[test]
523    fn test_tokenize_empty() {
524        let mut t = Tokenizer::new("");
525        let tokens = t.tokenize().unwrap();
526        assert!(tokens.is_empty());
527    }
528
529    #[test]
530    fn test_tokenize_whitespace_only() {
531        let mut t = Tokenizer::new("   ");
532        let tokens = t.tokenize().unwrap();
533        assert!(tokens.is_empty());
534    }
535
536    #[test]
537    fn test_tokenizer_remaining() {
538        let mut t = Tokenizer::new("1 + 2");
539        t.next_token().unwrap();
540        assert_eq!(t.remaining(), " + 2");
541    }
542
543    #[test]
544    fn test_tokenize_leading_decimal() {
545        let mut t = Tokenizer::new(".5");
546        let tokens = t.tokenize().unwrap();
547        assert_eq!(tokens, vec![Token::Number(0.5)]);
548    }
549
550    // ===== Parser tests =====
551
552    #[test]
553    fn test_parse_single_number() {
554        let ast = Parser::parse_str("42").unwrap();
555        assert_eq!(ast, AstNode::Number(42.0));
556    }
557
558    #[test]
559    fn test_parse_decimal() {
560        let ast = Parser::parse_str("3.14").unwrap();
561        assert_eq!(ast, AstNode::Number(3.14));
562    }
563
564    #[test]
565    fn test_parse_simple_addition() {
566        let ast = Parser::parse_str("2 + 3").unwrap();
567        assert_eq!(
568            ast,
569            AstNode::binary(AstNode::number(2.0), Operation::Add, AstNode::number(3.0))
570        );
571    }
572
573    #[test]
574    fn test_parse_simple_subtraction() {
575        let ast = Parser::parse_str("5 - 2").unwrap();
576        assert_eq!(
577            ast,
578            AstNode::binary(
579                AstNode::number(5.0),
580                Operation::Subtract,
581                AstNode::number(2.0)
582            )
583        );
584    }
585
586    #[test]
587    fn test_parse_simple_multiplication() {
588        let ast = Parser::parse_str("3 * 4").unwrap();
589        assert_eq!(
590            ast,
591            AstNode::binary(
592                AstNode::number(3.0),
593                Operation::Multiply,
594                AstNode::number(4.0)
595            )
596        );
597    }
598
599    #[test]
600    fn test_parse_simple_division() {
601        let ast = Parser::parse_str("8 / 2").unwrap();
602        assert_eq!(
603            ast,
604            AstNode::binary(
605                AstNode::number(8.0),
606                Operation::Divide,
607                AstNode::number(2.0)
608            )
609        );
610    }
611
612    #[test]
613    fn test_parse_simple_modulo() {
614        let ast = Parser::parse_str("7 % 3").unwrap();
615        assert_eq!(
616            ast,
617            AstNode::binary(
618                AstNode::number(7.0),
619                Operation::Modulo,
620                AstNode::number(3.0)
621            )
622        );
623    }
624
625    #[test]
626    fn test_parse_simple_power() {
627        let ast = Parser::parse_str("2 ^ 3").unwrap();
628        assert_eq!(
629            ast,
630            AstNode::binary(AstNode::number(2.0), Operation::Power, AstNode::number(3.0))
631        );
632    }
633
634    #[test]
635    fn test_parse_precedence_mul_over_add() {
636        // 2 + 3 * 4 = 2 + (3 * 4) = 14
637        let ast = Parser::parse_str("2 + 3 * 4").unwrap();
638        // Should be: Add(2, Mul(3, 4))
639        match ast {
640            AstNode::BinaryOp {
641                op: Operation::Add, ..
642            } => {}
643            _ => panic!("Expected Add at top level"),
644        }
645    }
646
647    #[test]
648    fn test_parse_precedence_power_highest() {
649        // 2 * 3 ^ 2 = 2 * (3 ^ 2) = 18
650        let ast = Parser::parse_str("2 * 3 ^ 2").unwrap();
651        match ast {
652            AstNode::BinaryOp {
653                op: Operation::Multiply,
654                ..
655            } => {}
656            _ => panic!("Expected Multiply at top level"),
657        }
658    }
659
660    #[test]
661    fn test_parse_power_right_associative() {
662        // 2 ^ 3 ^ 2 = 2 ^ (3 ^ 2) = 512
663        let ast = Parser::parse_str("2 ^ 3 ^ 2").unwrap();
664        match ast {
665            AstNode::BinaryOp {
666                left,
667                op: Operation::Power,
668                right,
669            } => {
670                assert_eq!(*left, AstNode::Number(2.0));
671                match *right {
672                    AstNode::BinaryOp {
673                        op: Operation::Power,
674                        ..
675                    } => {}
676                    _ => panic!("Expected Power on right"),
677                }
678            }
679            _ => panic!("Expected Power at top level"),
680        }
681    }
682
683    #[test]
684    fn test_parse_parentheses() {
685        // (2 + 3) * 4 = 20
686        let ast = Parser::parse_str("(2 + 3) * 4").unwrap();
687        match ast {
688            AstNode::BinaryOp {
689                op: Operation::Multiply,
690                left,
691                ..
692            } => match *left {
693                AstNode::BinaryOp {
694                    op: Operation::Add, ..
695                } => {}
696                _ => panic!("Expected Add inside parens"),
697            },
698            _ => panic!("Expected Multiply at top level"),
699        }
700    }
701
702    #[test]
703    fn test_parse_nested_parentheses() {
704        let ast = Parser::parse_str("((2 + 3))").unwrap();
705        match ast {
706            AstNode::BinaryOp {
707                op: Operation::Add, ..
708            } => {}
709            _ => panic!("Expected Add"),
710        }
711    }
712
713    #[test]
714    fn test_parse_unary_minus() {
715        let ast = Parser::parse_str("-5").unwrap();
716        match ast {
717            AstNode::Negate(inner) => {
718                assert_eq!(*inner, AstNode::Number(5.0));
719            }
720            _ => panic!("Expected Negate"),
721        }
722    }
723
724    #[test]
725    fn test_parse_unary_minus_in_expression() {
726        let ast = Parser::parse_str("3 + -2").unwrap();
727        match ast {
728            AstNode::BinaryOp {
729                op: Operation::Add,
730                right,
731                ..
732            } => match *right {
733                AstNode::Negate(_) => {}
734                _ => panic!("Expected Negate on right"),
735            },
736            _ => panic!("Expected Add"),
737        }
738    }
739
740    #[test]
741    fn test_parse_double_negative() {
742        let ast = Parser::parse_str("--5").unwrap();
743        match ast {
744            AstNode::Negate(inner) => match *inner {
745                AstNode::Negate(_) => {}
746                _ => panic!("Expected nested Negate"),
747            },
748            _ => panic!("Expected Negate"),
749        }
750    }
751
752    #[test]
753    fn test_parse_empty_expression() {
754        let result = Parser::parse_str("");
755        assert!(matches!(result, Err(CalcError::EmptyExpression)));
756    }
757
758    #[test]
759    fn test_parse_whitespace_only() {
760        let result = Parser::parse_str("   ");
761        assert!(matches!(result, Err(CalcError::EmptyExpression)));
762    }
763
764    #[test]
765    fn test_parse_unclosed_paren() {
766        let result = Parser::parse_str("(2 + 3");
767        assert!(matches!(result, Err(CalcError::ParseError(_))));
768    }
769
770    #[test]
771    fn test_parse_extra_close_paren() {
772        let result = Parser::parse_str("2 + 3)");
773        assert!(matches!(result, Err(CalcError::ParseError(_))));
774    }
775
776    #[test]
777    fn test_parse_missing_operand() {
778        let result = Parser::parse_str("2 +");
779        assert!(matches!(result, Err(CalcError::ParseError(_))));
780    }
781
782    #[test]
783    fn test_parse_consecutive_operators() {
784        // "2 + * 3" - should fail (+ followed by *)
785        let result = Parser::parse_str("2 + * 3");
786        assert!(matches!(result, Err(CalcError::ParseError(_))));
787    }
788
789    #[test]
790    fn test_parse_complex_expression() {
791        // 42 * (3 + 7)
792        let ast = Parser::parse_str("42 * (3 + 7)").unwrap();
793        match ast {
794            AstNode::BinaryOp {
795                op: Operation::Multiply,
796                left,
797                right,
798            } => {
799                assert_eq!(*left, AstNode::Number(42.0));
800                match *right {
801                    AstNode::BinaryOp {
802                        op: Operation::Add, ..
803                    } => {}
804                    _ => panic!("Expected Add in parens"),
805                }
806            }
807            _ => panic!("Expected Multiply"),
808        }
809    }
810
811    #[test]
812    fn test_parser_new() {
813        let tokens = vec![Token::Number(5.0)];
814        let parser = Parser::new(tokens);
815        assert_eq!(parser.pos, 0);
816    }
817
818    #[test]
819    fn test_parser_parse_method() {
820        let tokens = vec![Token::Number(42.0)];
821        let mut parser = Parser::new(tokens);
822        let ast = parser.parse().unwrap();
823        assert_eq!(ast, AstNode::Number(42.0));
824    }
825
826    #[test]
827    fn test_parser_parse_empty_tokens() {
828        let mut parser = Parser::new(vec![]);
829        let result = parser.parse();
830        assert!(matches!(result, Err(CalcError::EmptyExpression)));
831    }
832}