Skip to main content

lambda_cat/
parser.rs

1//! Recursive-descent parser for the lambda calculus surface syntax.
2//!
3//! The grammar is:
4//!
5//! ```text
6//! expr     ::= lambda | let | fix | app_expr
7//! lambda   ::= "\" ident "." expr
8//! let      ::= "let" ident "=" expr "in" expr
9//! fix      ::= "fix" ident "." expr
10//! app_expr ::= atom atom*                  (left-associative application)
11//! atom     ::= ident | "(" expr ")"
12//! ```
13//!
14//! Application binds tighter than abstraction and `let`/`fix`, so
15//! `\x. x y` parses as `\x. (x y)` and `let id = \x. x in id id` parses as
16//! `let id = (\x. x) in (id id)`.
17
18use crate::error::Error;
19use crate::lexer::{Token, TokenKind};
20use crate::syntax::{Expr, VarName};
21
22/// Look-ahead result for the parser: either a borrowed token or end-of-input.
23enum Peek<'a> {
24    Eof,
25    Tok(&'a Token),
26}
27
28fn peek(tokens: &[Token], pos: usize) -> Peek<'_> {
29    tokens.get(pos).map_or(Peek::Eof, Peek::Tok)
30}
31
32/// Parse a full expression from a token slice.
33///
34/// # Errors
35///
36/// Returns [`Error::UnexpectedToken`] if extra tokens trail after the
37/// expression, [`Error::UnexpectedEnd`] if input is exhausted mid-production,
38/// or other parse errors propagated from sub-productions.
39///
40/// # Examples
41///
42/// ```
43/// # fn main() -> Result<(), lambda_cat::error::Error> {
44/// use lambda_cat::lexer::lex;
45/// use lambda_cat::parser::parse;
46///
47/// let tokens = lex("\\x. x")?;
48/// let expr = parse(&tokens)?;
49/// assert_eq!(format!("{expr}"), "(\\x. x)");
50/// # Ok(())
51/// # }
52/// ```
53///
54/// [`Error::UnexpectedToken`]: crate::error::Error::UnexpectedToken
55/// [`Error::UnexpectedEnd`]: crate::error::Error::UnexpectedEnd
56pub fn parse(tokens: &[Token]) -> Result<Expr, Error> {
57    let (expr, end_pos) = parse_expr(tokens, 0)?;
58    tokens.get(end_pos).map_or(Ok(expr), |tok| {
59        Err(Error::UnexpectedToken {
60            at: tok.at(),
61            expected: "end of input",
62            found: format!("{}", tok.kind()),
63        })
64    })
65}
66
67fn parse_expr(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
68    match peek(tokens, pos) {
69        Peek::Eof => Err(Error::UnexpectedEnd {
70            expected: "expression",
71        }),
72        Peek::Tok(tok) => dispatch_expr(tokens, pos, tok),
73    }
74}
75
76fn dispatch_expr(tokens: &[Token], pos: usize, tok: &Token) -> Result<(Expr, usize), Error> {
77    match tok.kind() {
78        TokenKind::Lambda => parse_lambda(tokens, pos),
79        TokenKind::KwLet => parse_let(tokens, pos),
80        TokenKind::KwFix => parse_fix(tokens, pos),
81        TokenKind::Ident(_) | TokenKind::LParen => parse_app(tokens, pos),
82        TokenKind::KwIn | TokenKind::Dot | TokenKind::Equals | TokenKind::RParen => {
83            Err(Error::UnexpectedToken {
84                at: tok.at(),
85                expected: "expression",
86                found: format!("{}", tok.kind()),
87            })
88        }
89    }
90}
91
92fn parse_lambda(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
93    let after_lambda = expect_kind(tokens, pos, &TokenKind::Lambda, "`\\`")?;
94    let (param, after_ident) = expect_ident(tokens, after_lambda)?;
95    let after_dot = expect_kind(tokens, after_ident, &TokenKind::Dot, "`.`")?;
96    let (body, after_body) = parse_expr(tokens, after_dot)?;
97    Ok((Expr::lam(param, body), after_body))
98}
99
100fn parse_let(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
101    let after_let = expect_kind(tokens, pos, &TokenKind::KwLet, "keyword `let`")?;
102    let (name, after_name) = expect_ident(tokens, after_let)?;
103    let after_eq = expect_kind(tokens, after_name, &TokenKind::Equals, "`=`")?;
104    let (value, after_value) = parse_expr(tokens, after_eq)?;
105    let after_in = expect_kind(tokens, after_value, &TokenKind::KwIn, "keyword `in`")?;
106    let (body, after_body) = parse_expr(tokens, after_in)?;
107    Ok((Expr::bind(name, value, body), after_body))
108}
109
110fn parse_fix(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
111    let after_fix = expect_kind(tokens, pos, &TokenKind::KwFix, "keyword `fix`")?;
112    let (name, after_name) = expect_ident(tokens, after_fix)?;
113    let after_dot = expect_kind(tokens, after_name, &TokenKind::Dot, "`.`")?;
114    let (body, after_body) = parse_expr(tokens, after_dot)?;
115    Ok((Expr::fix(name, body), after_body))
116}
117
118fn parse_app(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
119    let (head, after_head) = parse_atom(tokens, pos)?;
120    parse_app_tail(tokens, after_head, head)
121}
122
123fn parse_app_tail(tokens: &[Token], pos: usize, lhs: Expr) -> Result<(Expr, usize), Error> {
124    let can_apply = starts_atom(tokens, pos);
125    if can_apply {
126        let (arg, after_arg) = parse_atom(tokens, pos)?;
127        parse_app_tail(tokens, after_arg, Expr::app(lhs, arg))
128    } else {
129        Ok((lhs, pos))
130    }
131}
132
133fn starts_atom(tokens: &[Token], pos: usize) -> bool {
134    match peek(tokens, pos) {
135        Peek::Eof => false,
136        Peek::Tok(tok) => matches!(tok.kind(), TokenKind::Ident(_) | TokenKind::LParen),
137    }
138}
139
140fn parse_atom(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
141    match peek(tokens, pos) {
142        Peek::Eof => Err(Error::UnexpectedEnd {
143            expected: "atom (identifier or `(`)",
144        }),
145        Peek::Tok(tok) => dispatch_atom(tokens, pos, tok),
146    }
147}
148
149fn dispatch_atom(tokens: &[Token], pos: usize, tok: &Token) -> Result<(Expr, usize), Error> {
150    match tok.kind() {
151        TokenKind::Ident(name) => Ok((Expr::Var(name.clone()), pos + 1)),
152        TokenKind::LParen => parse_paren_inner(tokens, pos + 1),
153        TokenKind::Lambda
154        | TokenKind::KwLet
155        | TokenKind::KwFix
156        | TokenKind::KwIn
157        | TokenKind::Dot
158        | TokenKind::Equals
159        | TokenKind::RParen => Err(Error::UnexpectedToken {
160            at: tok.at(),
161            expected: "atom (identifier or `(`)",
162            found: format!("{}", tok.kind()),
163        }),
164    }
165}
166
167fn parse_paren_inner(tokens: &[Token], pos: usize) -> Result<(Expr, usize), Error> {
168    let (inner, after_inner) = parse_expr(tokens, pos)?;
169    let after_close = expect_kind(tokens, after_inner, &TokenKind::RParen, "`)`")?;
170    Ok((inner, after_close))
171}
172
173fn expect_ident(tokens: &[Token], pos: usize) -> Result<(VarName, usize), Error> {
174    match peek(tokens, pos) {
175        Peek::Eof => Err(Error::UnexpectedEnd {
176            expected: "identifier",
177        }),
178        Peek::Tok(tok) => dispatch_expect_ident(tok, pos),
179    }
180}
181
182fn dispatch_expect_ident(tok: &Token, pos: usize) -> Result<(VarName, usize), Error> {
183    match tok.kind() {
184        TokenKind::Ident(name) => Ok((name.clone(), pos + 1)),
185        TokenKind::Lambda
186        | TokenKind::KwLet
187        | TokenKind::KwIn
188        | TokenKind::KwFix
189        | TokenKind::Dot
190        | TokenKind::Equals
191        | TokenKind::LParen
192        | TokenKind::RParen => Err(Error::UnexpectedToken {
193            at: tok.at(),
194            expected: "identifier",
195            found: format!("{}", tok.kind()),
196        }),
197    }
198}
199
200fn expect_kind(
201    tokens: &[Token],
202    pos: usize,
203    expected: &TokenKind,
204    name: &'static str,
205) -> Result<usize, Error> {
206    match peek(tokens, pos) {
207        Peek::Eof => Err(Error::UnexpectedEnd { expected: name }),
208        Peek::Tok(tok) => {
209            let matches = tok.kind() == expected;
210            if matches {
211                Ok(pos + 1)
212            } else {
213                Err(Error::UnexpectedToken {
214                    at: tok.at(),
215                    expected: name,
216                    found: format!("{}", tok.kind()),
217                })
218            }
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226    use crate::lexer::lex;
227
228    fn parse_str(src: &str) -> Result<Expr, Error> {
229        let tokens = lex(src)?;
230        parse(&tokens)
231    }
232
233    #[test]
234    fn identity_function() -> Result<(), Error> {
235        let e = parse_str("\\x. x")?;
236        let expected = Expr::lam("x", Expr::var("x"));
237        (e == expected).then_some(()).ok_or(Error::UnexpectedEnd {
238            expected: "identity AST",
239        })
240    }
241
242    #[test]
243    fn left_associative_application() -> Result<(), Error> {
244        let e = parse_str("f x y")?;
245        let expected = Expr::app(Expr::app(Expr::var("f"), Expr::var("x")), Expr::var("y"));
246        (e == expected).then_some(()).ok_or(Error::UnexpectedEnd {
247            expected: "left-assoc AST",
248        })
249    }
250
251    #[test]
252    fn let_with_body() -> Result<(), Error> {
253        let e = parse_str("let id = \\x. x in id id")?;
254        let expected = Expr::bind(
255            "id",
256            Expr::lam("x", Expr::var("x")),
257            Expr::app(Expr::var("id"), Expr::var("id")),
258        );
259        (e == expected).then_some(()).ok_or(Error::UnexpectedEnd {
260            expected: "let AST",
261        })
262    }
263}