Skip to main content

jia_parse/jia_lang/parser/
expr.rs

1//! Expression parser for the CP language using Pratt parsing.
2//!
3//! Precedence levels (lowest to highest):
4//! 1. Additive: `+`, `-`
5//! 2. Multiplicative: `*`
6//! 3. Atoms: `Number`, `Ident`, `start_of(name)`, `end_of(name)`, `duration_of(name)`, `(expr)`, unary `-`
7
8use super::Parser;
9use crate::error::ParseError;
10use crate::jia_lang::ast::{ArithOp, Expr};
11use crate::jia_lang::lexer::TokenKind;
12
13#[derive(Clone, Copy)]
14enum UnaryExprFn {
15    StartOf,
16    EndOf,
17    DurationOf,
18    PresentOf,
19}
20
21impl UnaryExprFn {
22    fn from_name(name: &str) -> Option<Self> {
23        match name {
24            "start_of" => Some(Self::StartOf),
25            "end_of" => Some(Self::EndOf),
26            "duration_of" => Some(Self::DurationOf),
27            "present_of" => Some(Self::PresentOf),
28            _ => None,
29        }
30    }
31
32    fn apply(self, arg: String) -> Expr {
33        match self {
34            Self::StartOf => Expr::StartOf(arg),
35            Self::EndOf => Expr::EndOf(arg),
36            Self::DurationOf => Expr::DurationOf(arg),
37            Self::PresentOf => Expr::PresentOf(arg),
38        }
39    }
40}
41
42impl<'a> Parser<'a> {
43    /// Parse an expression.
44    pub fn parse_expr(&mut self) -> Result<Expr, ParseError> {
45        self.parse_additive()
46    }
47
48    /// Parse additive expressions: `term (('+' | '-') term)*`
49    fn parse_additive(&mut self) -> Result<Expr, ParseError> {
50        let mut left = self.parse_multiplicative()?;
51        loop {
52            let op = match self.peek_kind() {
53                Some(TokenKind::Plus) => ArithOp::Add,
54                Some(TokenKind::Minus) => ArithOp::Sub,
55                _ => break,
56            };
57            self.advance();
58            let right = self.parse_multiplicative()?;
59            left = Expr::BinaryOp {
60                op,
61                left: Box::new(left),
62                right: Box::new(right),
63            };
64        }
65        Ok(left)
66    }
67
68    /// Parse multiplicative expressions: `atom (('*' | '/') atom)*`
69    fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
70        let mut left = self.parse_atom()?;
71        loop {
72            let op = match self.peek_kind() {
73                Some(TokenKind::Star) => ArithOp::Mul,
74                Some(TokenKind::Slash) => ArithOp::Div,
75                _ => break,
76            };
77            self.advance();
78            let right = self.parse_atom()?;
79            left = Expr::BinaryOp {
80                op,
81                left: Box::new(left),
82                right: Box::new(right),
83            };
84        }
85        Ok(left)
86    }
87
88    /// Parse an atom: number, identifier, function call, parenthesized expr, or unary negation.
89    fn parse_atom(&mut self) -> Result<Expr, ParseError> {
90        match self.peek_kind() {
91            Some(TokenKind::Number(n)) => {
92                self.advance();
93                Ok(Expr::Number(n))
94            }
95            Some(TokenKind::Float(f)) => {
96                self.advance();
97                Ok(Expr::Float(f))
98            }
99            Some(TokenKind::Minus) => {
100                self.advance();
101                let inner = self.parse_atom()?;
102                Ok(Expr::Negate(Box::new(inner)))
103            }
104            Some(TokenKind::LParen) => {
105                self.advance();
106                let expr = self.parse_expr()?;
107                self.expect_token(TokenKind::RParen)?;
108                Ok(expr)
109            }
110            Some(TokenKind::Ident(name)) => {
111                // Check if it's a function call: start_of, end_of, duration_of, present_of
112                let name = name.clone();
113                if let Some(function) = UnaryExprFn::from_name(&name) {
114                    self.advance();
115                    self.expect_token(TokenKind::LParen)?;
116                    let arg = self.expect_ident()?;
117                    self.expect_token(TokenKind::RParen)?;
118                    Ok(function.apply(arg))
119                } else {
120                    self.advance();
121                    Ok(Expr::Var(name))
122                }
123            }
124            _ => {
125                let span = self.current_span();
126                Err(ParseError::new("expected expression", span))
127            }
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use crate::jia_lang::ast::{ArithOp, Expr};
135    use crate::jia_lang::lexer::tokenize;
136    use crate::jia_lang::parser::Parser;
137
138    fn parse_expr(input: &str) -> Expr {
139        let tokens = tokenize(input).unwrap();
140        let mut parser = Parser::new(&tokens);
141        parser.parse_expr().unwrap()
142    }
143
144    #[test]
145    fn test_number() {
146        assert_eq!(parse_expr("42"), Expr::Number(42));
147    }
148
149    #[test]
150    fn test_variable() {
151        assert_eq!(parse_expr("makespan"), Expr::Var("makespan".to_string()));
152    }
153
154    #[test]
155    fn test_start_of() {
156        assert_eq!(
157            parse_expr("start_of(task_a)"),
158            Expr::StartOf("task_a".to_string())
159        );
160    }
161
162    #[test]
163    fn test_end_of() {
164        assert_eq!(
165            parse_expr("end_of(task_b)"),
166            Expr::EndOf("task_b".to_string())
167        );
168    }
169
170    #[test]
171    fn test_duration_of() {
172        assert_eq!(
173            parse_expr("duration_of(task_a)"),
174            Expr::DurationOf("task_a".to_string())
175        );
176    }
177
178    #[test]
179    fn test_addition() {
180        assert_eq!(
181            parse_expr("end_of(a) + 5"),
182            Expr::BinaryOp {
183                op: ArithOp::Add,
184                left: Box::new(Expr::EndOf("a".to_string())),
185                right: Box::new(Expr::Number(5)),
186            }
187        );
188    }
189
190    #[test]
191    fn test_precedence_mul_before_add() {
192        // 1 + 2 * 3 should be 1 + (2 * 3)
193        let expr = parse_expr("1 + 2 * 3");
194        assert_eq!(
195            expr,
196            Expr::BinaryOp {
197                op: ArithOp::Add,
198                left: Box::new(Expr::Number(1)),
199                right: Box::new(Expr::BinaryOp {
200                    op: ArithOp::Mul,
201                    left: Box::new(Expr::Number(2)),
202                    right: Box::new(Expr::Number(3)),
203                }),
204            }
205        );
206    }
207
208    #[test]
209    fn test_parenthesized() {
210        let expr = parse_expr("(1 + 2) * 3");
211        assert_eq!(
212            expr,
213            Expr::BinaryOp {
214                op: ArithOp::Mul,
215                left: Box::new(Expr::BinaryOp {
216                    op: ArithOp::Add,
217                    left: Box::new(Expr::Number(1)),
218                    right: Box::new(Expr::Number(2)),
219                }),
220                right: Box::new(Expr::Number(3)),
221            }
222        );
223    }
224
225    #[test]
226    fn test_negation() {
227        assert_eq!(parse_expr("-5"), Expr::Negate(Box::new(Expr::Number(5))));
228    }
229
230    #[test]
231    fn test_present_of_and_expression_error() {
232        assert_eq!(
233            parse_expr("present_of(task_a)"),
234            Expr::PresentOf("task_a".to_string())
235        );
236
237        let tokens = tokenize(")").unwrap();
238        let mut parser = Parser::new(&tokens);
239        assert!(parser.parse_expr().is_err());
240    }
241}