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