Skip to main content

bubbles/compiler/
expr.rs

1//! Recursive-descent expression parser — turns a source string into an [`Expr`] tree.
2
3pub use crate::compiler::ast::{BinOp, Expr, UnOp};
4
5use crate::compiler::lexer::{Token, tokenise};
6use crate::error::{DialogueError, Result};
7
8// ── public entry point ────────────────────────────────────────────────────────
9
10/// Parses `source` as an expression.
11///
12/// # Errors
13/// Returns [`DialogueError::Parse`] on a syntax error.
14pub fn parse_expr(source: &str) -> Result<Expr> {
15    let tokens: Vec<Token> = tokenise(source).into_iter().map(|(t, _)| t).collect();
16    let mut p = ExprParser {
17        tokens: &tokens,
18        pos: 0,
19    };
20    let expr = p.parse_or()?;
21    if p.pos < p.tokens.len() {
22        return Err(DialogueError::Parse {
23            file: "<expr>".into(),
24            line: 0,
25            message: format!("unexpected token after expression: {:?}", p.tokens[p.pos]),
26        });
27    }
28    Ok(expr)
29}
30
31// ── parser ────────────────────────────────────────────────────────────────────
32
33struct ExprParser<'a> {
34    tokens: &'a [Token],
35    pos: usize,
36}
37
38impl ExprParser<'_> {
39    fn peek(&self) -> Option<&Token> {
40        self.tokens.get(self.pos)
41    }
42
43    fn advance(&mut self) -> Option<&Token> {
44        let t = self.tokens.get(self.pos);
45        self.pos += 1;
46        t
47    }
48
49    fn err(msg: &str) -> DialogueError {
50        DialogueError::Parse {
51            file: "<expr>".into(),
52            line: 0,
53            message: msg.into(),
54        }
55    }
56
57    // Precedence (lowest → highest):
58    // or → and → eq/neq → cmp → add/sub → mul/div/rem → unary → primary
59
60    fn parse_or(&mut self) -> Result<Expr> {
61        let mut left = self.parse_and()?;
62        while self.peek() == Some(&Token::OrOr) {
63            self.advance();
64            let right = self.parse_and()?;
65            left = Expr::Binary {
66                left: Box::new(left),
67                op: BinOp::Or,
68                right: Box::new(right),
69            };
70        }
71        Ok(left)
72    }
73
74    fn parse_and(&mut self) -> Result<Expr> {
75        let mut left = self.parse_equality()?;
76        while self.peek() == Some(&Token::AndAnd) {
77            self.advance();
78            let right = self.parse_equality()?;
79            left = Expr::Binary {
80                left: Box::new(left),
81                op: BinOp::And,
82                right: Box::new(right),
83            };
84        }
85        Ok(left)
86    }
87
88    fn parse_equality(&mut self) -> Result<Expr> {
89        let mut left = self.parse_comparison()?;
90        loop {
91            let op = match self.peek() {
92                Some(Token::EqEq) => BinOp::Eq,
93                Some(Token::Neq) => BinOp::Neq,
94                _ => break,
95            };
96            self.advance();
97            let right = self.parse_comparison()?;
98            left = Expr::Binary {
99                left: Box::new(left),
100                op,
101                right: Box::new(right),
102            };
103        }
104        Ok(left)
105    }
106
107    fn parse_comparison(&mut self) -> Result<Expr> {
108        let mut left = self.parse_additive()?;
109        loop {
110            let op = match self.peek() {
111                Some(Token::Lt) => BinOp::Lt,
112                Some(Token::Lte) => BinOp::Lte,
113                Some(Token::Gt) => BinOp::Gt,
114                Some(Token::Gte) => BinOp::Gte,
115                _ => break,
116            };
117            self.advance();
118            let right = self.parse_additive()?;
119            left = Expr::Binary {
120                left: Box::new(left),
121                op,
122                right: Box::new(right),
123            };
124        }
125        Ok(left)
126    }
127
128    fn parse_additive(&mut self) -> Result<Expr> {
129        let mut left = self.parse_multiplicative()?;
130        loop {
131            let op = match self.peek() {
132                Some(Token::Plus) => BinOp::Add,
133                Some(Token::Minus) => BinOp::Sub,
134                _ => break,
135            };
136            self.advance();
137            let right = self.parse_multiplicative()?;
138            left = Expr::Binary {
139                left: Box::new(left),
140                op,
141                right: Box::new(right),
142            };
143        }
144        Ok(left)
145    }
146
147    fn parse_multiplicative(&mut self) -> Result<Expr> {
148        let mut left = self.parse_unary()?;
149        loop {
150            let op = match self.peek() {
151                Some(Token::Star) => BinOp::Mul,
152                Some(Token::Slash) => BinOp::Div,
153                Some(Token::Percent) => BinOp::Rem,
154                _ => break,
155            };
156            self.advance();
157            let right = self.parse_unary()?;
158            left = Expr::Binary {
159                left: Box::new(left),
160                op,
161                right: Box::new(right),
162            };
163        }
164        Ok(left)
165    }
166
167    fn parse_unary(&mut self) -> Result<Expr> {
168        match self.peek() {
169            Some(Token::Minus) => {
170                self.advance();
171                let expr = self.parse_unary()?;
172                Ok(Expr::Unary {
173                    op: UnOp::Neg,
174                    expr: Box::new(expr),
175                })
176            }
177            Some(Token::Bang) => {
178                self.advance();
179                let expr = self.parse_unary()?;
180                Ok(Expr::Unary {
181                    op: UnOp::Not,
182                    expr: Box::new(expr),
183                })
184            }
185            _ => self.parse_primary(),
186        }
187    }
188
189    fn parse_primary(&mut self) -> Result<Expr> {
190        match self.advance().cloned() {
191            Some(Token::Number(n)) => Ok(Expr::Number(n)),
192            Some(Token::Str(s)) => Ok(Expr::Text(s)),
193            Some(Token::Var(v)) => Ok(Expr::Var(v)),
194            Some(Token::Ident(ref s)) if s == "true" => Ok(Expr::Bool(true)),
195            Some(Token::Ident(ref s)) if s == "false" => Ok(Expr::Bool(false)),
196            Some(Token::Ident(name)) => {
197                if self.peek() == Some(&Token::LParen) {
198                    self.advance();
199                    let mut args = Vec::new();
200                    if self.peek() != Some(&Token::RParen) {
201                        args.push(self.parse_or()?);
202                        while self.peek() == Some(&Token::Comma) {
203                            self.advance();
204                            args.push(self.parse_or()?);
205                        }
206                    }
207                    if self.advance() != Some(&Token::RParen) {
208                        return Err(Self::err("expected `)` after function arguments"));
209                    }
210                    Ok(Expr::Call { name, args })
211                } else {
212                    Err(Self::err(&format!(
213                        "unknown identifier `{name}`; variables need a `$` prefix"
214                    )))
215                }
216            }
217            Some(Token::LParen) => {
218                let expr = self.parse_or()?;
219                if self.advance() != Some(&Token::RParen) {
220                    return Err(Self::err("expected closing `)`"));
221                }
222                Ok(expr)
223            }
224            Some(t) => Err(Self::err(&format!("unexpected token `{t:?}`"))),
225            None => Err(Self::err("unexpected end of expression")),
226        }
227    }
228}
229
230#[cfg(test)]
231#[path = "expr_tests.rs"]
232mod tests;