Skip to main content

rill_lang/
parser.rs

1//! Recursive-descent + Pratt (operator-precedence) parser.
2
3use crate::ast::{BinOp, Def, Expr, Program};
4use crate::error::{CompileError, Span};
5use crate::lexer::{Tok, Token};
6
7struct Parser<'a> {
8    toks: &'a [Token],
9    pos: usize,
10}
11
12/// Binding powers. Higher = binds tighter. Returns (op, left_bp, right_bp).
13/// Left-associative ops use right_bp = left_bp + 1.
14fn infix_binding_power(t: &Tok) -> Option<(BinOp, u8, u8)> {
15    Some(match t {
16        Tok::Tilde => (BinOp::Feedback, 1, 2),
17        Tok::Colon => (BinOp::Seq, 3, 4),
18        Tok::Merge => (BinOp::Merge, 5, 6),
19        Tok::Split => (BinOp::Split, 7, 8),
20        Tok::Comma => (BinOp::Par, 9, 10),
21        Tok::Plus => (BinOp::Add, 11, 12),
22        Tok::Minus => (BinOp::Sub, 11, 12),
23        Tok::Star => (BinOp::Mul, 13, 14),
24        Tok::Slash => (BinOp::Div, 13, 14),
25        Tok::Percent => (BinOp::Rem, 13, 14),
26        Tok::At => (BinOp::Delay, 15, 16),
27        _ => return None,
28    })
29}
30
31impl<'a> Parser<'a> {
32    fn new(toks: &'a [Token]) -> Self {
33        Self { toks, pos: 0 }
34    }
35    fn peek(&self) -> &Token {
36        &self.toks[self.pos]
37    }
38    fn bump(&mut self) -> Token {
39        let t = self.toks[self.pos].clone();
40        if self.pos + 1 < self.toks.len() {
41            self.pos += 1;
42        }
43        t
44    }
45    fn eat(&mut self, want: &Tok) -> Result<Token, CompileError> {
46        if &self.peek().tok == want {
47            Ok(self.bump())
48        } else {
49            let p = self.peek();
50            Err(CompileError::Parse {
51                msg: format!("expected {want:?}, found {:?}", p.tok),
52                span: p.span,
53            })
54        }
55    }
56
57    fn parse_program(&mut self) -> Result<Program, CompileError> {
58        let mut defs = Vec::new();
59        while self.peek().tok != Tok::Eof {
60            defs.push(self.parse_def()?);
61        }
62        if defs.is_empty() {
63            return Err(CompileError::Parse {
64                msg: "empty program (expected at least `process = ...;`)".into(),
65                span: Span::new(0, 0),
66            });
67        }
68        Ok(Program { defs })
69    }
70
71    fn parse_def(&mut self) -> Result<Def, CompileError> {
72        let name_tok = self.peek().clone();
73        let (name, start) = match &name_tok.tok {
74            Tok::Ident(n) => (n.clone(), name_tok.span.start),
75            _ => {
76                return Err(CompileError::Parse {
77                    msg: format!("expected definition name, found {:?}", name_tok.tok),
78                    span: name_tok.span,
79                })
80            }
81        };
82        self.bump();
83        let mut params = Vec::new();
84        if self.peek().tok == Tok::LParen {
85            self.bump();
86            loop {
87                match &self.peek().tok {
88                    Tok::Ident(p) => {
89                        params.push(p.clone());
90                        self.bump();
91                    }
92                    other => {
93                        return Err(CompileError::Parse {
94                            msg: format!("expected parameter name, found {other:?}"),
95                            span: self.peek().span,
96                        })
97                    }
98                }
99                match self.peek().tok {
100                    Tok::Comma => {
101                        self.bump();
102                    }
103                    Tok::RParen => break,
104                    _ => {
105                        return Err(CompileError::Parse {
106                            msg: "expected `,` or `)` in parameter list".into(),
107                            span: self.peek().span,
108                        })
109                    }
110                }
111            }
112            self.eat(&Tok::RParen)?;
113        }
114        self.eat(&Tok::Eq)?;
115        let body = self.parse_expr(0, false)?;
116        let semi = self.eat(&Tok::Semi)?;
117        Ok(Def {
118            name,
119            params,
120            body,
121            span: Span::new(start, semi.span.end),
122        })
123    }
124
125    /// Pratt loop. When `no_comma` is set, a top-level `,` terminates the
126    /// expression instead of being parsed as the `Par` combinator — used inside
127    /// an application's argument list where `,` is a separator. Grouping parens
128    /// reset this so `,` means `Par` again.
129    fn parse_expr(&mut self, min_bp: u8, no_comma: bool) -> Result<Expr, CompileError> {
130        let mut lhs = self.parse_prefix(no_comma)?;
131        while let Some((op, l_bp, r_bp)) = infix_binding_power(&self.peek().tok) {
132            if no_comma && op == BinOp::Par {
133                break;
134            }
135            if l_bp < min_bp {
136                break;
137            }
138            self.bump();
139            let rhs = self.parse_expr(r_bp, no_comma)?;
140            let span = lhs.span().merge(rhs.span());
141            lhs = Expr::Bin {
142                op,
143                lhs: Box::new(lhs),
144                rhs: Box::new(rhs),
145                span,
146            };
147        }
148        Ok(lhs)
149    }
150
151    fn parse_prefix(&mut self, no_comma: bool) -> Result<Expr, CompileError> {
152        let t = self.peek().clone();
153        match t.tok {
154            Tok::Minus => {
155                self.bump();
156                let inner = self.parse_expr(15, no_comma)?;
157                let span = t.span.merge(inner.span());
158                Ok(Expr::Neg(Box::new(inner), span))
159            }
160            _ => self.parse_atom(),
161        }
162    }
163
164    fn parse_atom(&mut self) -> Result<Expr, CompileError> {
165        let t = self.bump();
166        match t.tok {
167            Tok::Int(v) => Ok(Expr::Int(v, t.span)),
168            Tok::Float(v) => Ok(Expr::Float(v, t.span)),
169            Tok::Wire => Ok(Expr::Wire(t.span)),
170            Tok::Cut => Ok(Expr::Cut(t.span)),
171            Tok::Str(s) => Ok(Expr::Str(s, t.span)),
172            Tok::Plus => Ok(Expr::Ref("+".into(), t.span)),
173            Tok::Minus => Ok(Expr::Ref("-".into(), t.span)),
174            Tok::Star => Ok(Expr::Ref("*".into(), t.span)),
175            Tok::Slash => Ok(Expr::Ref("/".into(), t.span)),
176            Tok::Percent => Ok(Expr::Ref("%".into(), t.span)),
177            Tok::Ident(name) => {
178                if self.peek().tok == Tok::LParen {
179                    self.bump();
180                    let mut args = Vec::new();
181                    if self.peek().tok != Tok::RParen {
182                        loop {
183                            args.push(self.parse_expr(0, true)?);
184                            match self.peek().tok {
185                                Tok::Comma => {
186                                    self.bump();
187                                }
188                                _ => break,
189                            }
190                        }
191                    }
192                    let rp = self.eat(&Tok::RParen)?;
193                    Ok(Expr::Apply {
194                        name,
195                        args,
196                        span: t.span.merge(rp.span),
197                    })
198                } else {
199                    Ok(Expr::Ref(name, t.span))
200                }
201            }
202            Tok::LParen => {
203                let inner = self.parse_expr(0, false)?;
204                self.eat(&Tok::RParen)?;
205                Ok(inner)
206            }
207            other => Err(CompileError::Parse {
208                msg: format!("unexpected token {other:?}"),
209                span: t.span,
210            }),
211        }
212    }
213}
214
215/// Parse a complete program (`name = expr; ...`).
216pub fn parse(tokens: &[Token]) -> Result<Program, CompileError> {
217    Parser::new(tokens).parse_program()
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::lexer::tokenize;
224
225    fn prog(src: &str) -> Program {
226        parse(&tokenize(src).unwrap()).unwrap()
227    }
228    fn body(src: &str) -> Expr {
229        let p = prog(src);
230        p.defs
231            .into_iter()
232            .find(|d| d.name == "process")
233            .unwrap()
234            .body
235    }
236
237    #[test]
238    fn parses_single_def() {
239        let p = prog("process = _;");
240        assert_eq!(p.defs.len(), 1);
241        assert_eq!(p.defs[0].name, "process");
242        assert!(matches!(p.defs[0].body, Expr::Wire(_)));
243    }
244
245    #[test]
246    fn arithmetic_binds_tighter_than_par() {
247        match body("process = _ * 2 , _;") {
248            Expr::Bin {
249                op: BinOp::Par,
250                lhs,
251                ..
252            } => {
253                assert!(matches!(*lhs, Expr::Bin { op: BinOp::Mul, .. }));
254            }
255            other => panic!("expected top Par, got {other:?}"),
256        }
257    }
258
259    #[test]
260    fn feedback_binds_loosest() {
261        match body("process = + ~ _;") {
262            Expr::Bin {
263                op: BinOp::Feedback,
264                lhs,
265                rhs,
266                ..
267            } => {
268                assert!(matches!(*lhs, Expr::Ref(_, _)) || matches!(*lhs, Expr::Bin { .. }));
269                assert!(matches!(*rhs, Expr::Wire(_)));
270            }
271            other => panic!("expected Feedback at top, got {other:?}"),
272        }
273    }
274
275    #[test]
276    fn seq_is_left_associative() {
277        match body("process = _ : _ : _;") {
278            Expr::Bin {
279                op: BinOp::Seq,
280                lhs,
281                ..
282            } => {
283                assert!(matches!(*lhs, Expr::Bin { op: BinOp::Seq, .. }));
284            }
285            other => panic!("expected Seq at top, got {other:?}"),
286        }
287    }
288
289    #[test]
290    fn application_uses_comma_as_arg_separator() {
291        let p = prog("gain(x, y) = x; process = gain(_, 2);");
292        let call = p.defs.iter().find(|d| d.name == "process").unwrap();
293        match &call.body {
294            Expr::Apply { name, args, .. } => {
295                assert_eq!(name, "gain");
296                assert_eq!(args.len(), 2);
297            }
298            other => panic!("expected Apply, got {other:?}"),
299        }
300    }
301
302    #[test]
303    fn grouping_paren_is_parallel_inside() {
304        match body("process = (_ , _) :> _;") {
305            Expr::Bin {
306                op: BinOp::Merge,
307                lhs,
308                ..
309            } => {
310                assert!(matches!(*lhs, Expr::Bin { op: BinOp::Par, .. }));
311            }
312            other => panic!("expected Merge, got {other:?}"),
313        }
314    }
315
316    #[test]
317    fn application_arg_may_be_a_composed_expression() {
318        let p = prog("f(a, b) = a; process = f(_ : _, 2);");
319        let call = p.defs.iter().find(|d| d.name == "process").unwrap();
320        match &call.body {
321            Expr::Apply { name, args, .. } => {
322                assert_eq!(name, "f");
323                assert_eq!(args.len(), 2);
324                assert!(matches!(args[0], Expr::Bin { op: BinOp::Seq, .. }));
325            }
326            other => panic!("expected Apply, got {other:?}"),
327        }
328    }
329
330    #[test]
331    fn rejects_missing_semicolon() {
332        assert!(parse(&tokenize("process = _").unwrap()).is_err());
333    }
334
335    #[test]
336    fn parses_string_arg() {
337        let p = parse(&tokenize(r#"process = f("x");"#).unwrap()).unwrap();
338        let call = p.defs.iter().find(|d| d.name == "process").unwrap();
339        match &call.body {
340            Expr::Apply { name, args, .. } => {
341                assert_eq!(name, "f");
342                assert_eq!(args.len(), 1);
343                assert!(matches!(&args[0], Expr::Str(s, _) if s == "x"));
344            }
345            other => panic!("expected Apply, got {other:?}"),
346        }
347    }
348}