Files
AILang/crates/ailang-surface/src/parse.rs
T
Brummel 8860600e37 Iter 16b.1 — local recursive let (no-capture)
Add `(let-rec NAME (params ...) (type ...) (body ...) (in ...))`
as a form-A surface and a `Term::LetRec` AST variant. The 16a
desugar pass lifts each LetRec whose body has no captures from
the enclosing scope to a synthetic top-level fn `<hint>$lr_N`
and substitutes the original name; typecheck and codegen never
see LetRec. Capture detection panics at desugar time, queued
for 16b.2.

Tests: 95 → 99 (+1 e2e local_rec_factorial_demo, +2 desugar
unit, +1 parse unit). The new fixture `examples/local_rec_demo`
runs `fact` at n=1, 3, 5 → prints 1, 6, 120.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 20:33:48 +02:00

1160 lines
41 KiB
Rust

//! Recursive-descent parser for form (A).
//!
//! The grammar (EBNF) lives next to its implementation. Each production
//! is one Rust function in this file. No look-ahead beyond a single
//! token is required; the parser is line-by-line auditable.
//!
//! ```text
//! module ::= "(" "module" ident def* ")"
//! def ::= data-def | fn-def | const-def | import-clause
//! data-def ::= "(" "data" ident vars-clause? data-attr* ")"
//! vars-clause ::= "(" "vars" ident+ ")"
//! data-attr ::= doc-attr | ctor-decl
//! ctor-decl ::= "(" "ctor" ident type* ")"
//! doc-attr ::= "(" "doc" string ")"
//!
//! fn-def ::= "(" "fn" ident fn-attr* ")"
//! fn-attr ::= doc-attr | type-attr | params-attr | body-attr
//! type-attr ::= "(" "type" type ")"
//! params-attr ::= "(" "params" ident* ")"
//! body-attr ::= "(" "body" term ")"
//!
//! const-def ::= "(" "const" ident const-attr+ ")"
//! const-attr ::= type-attr | body-attr | doc-attr
//!
//! import-clause ::= "(" "import" ident ("as" ident)? ")"
//!
//! type ::= type-var | type-con | fn-type | forall-type
//! type-var ::= ident
//! type-con ::= "(" "con" ident type* ")"
//! fn-type ::= "(" "fn-type" "(" "params" type* ")"
//! "(" "ret" type ")"
//! effects-clause? ")"
//! forall-type ::= "(" "forall" "(" "vars" ident+ ")" type ")"
//! effects-clause::= "(" "effects" ident+ ")"
//!
//! term ::= var-ref | int-lit | str-lit | bool-lit | unit-lit
//! | app-term | tail-app-term | match-term | ctor-term
//! | do-term | tail-do-term | seq-term | lam-term | if-term
//! | let-term | let-rec-term
//! var-ref ::= ident ; reserved: true/false → bool-lit
//! int-lit ::= integer ; numeric atom
//! str-lit ::= string ; string atom
//! bool-lit ::= "true" | "false"
//! unit-lit ::= "(" "lit-unit" ")"
//! app-term ::= "(" "app" term term+ ")"
//! tail-app-term ::= "(" "tail-app" term term+ ")" ; Iter 14e
//! ctor-term ::= "(" "term-ctor" ident ident term* ")"
//! match-term ::= "(" "match" term case-arm+ ")"
//! case-arm ::= "(" "case" pattern term ")"
//! do-term ::= "(" "do" ident term* ")"
//! tail-do-term ::= "(" "tail-do" ident term* ")" ; Iter 14e
//! seq-term ::= "(" "seq" term term ")"
//! lam-term ::= "(" "lam" "(" "params" typed-param* ")"
//! "(" "ret" type ")"
//! effects-clause? body-attr ")"
//! typed-param ::= "(" "typed" ident type ")"
//! if-term ::= "(" "if" term term term ")"
//! let-term ::= "(" "let" ident term term ")"
//! let-rec-term ::= "(" "let-rec" ident
//! "(" "params" ident* ")"
//! type-attr
//! body-attr
//! "(" "in" term ")" ")"
//!
//! pattern ::= pat-var | pat-ctor | pat-lit | pat-wild
//! pat-var ::= ident
//! pat-ctor ::= "(" "pat-ctor" ident pattern* ")"
//! pat-lit ::= "(" "pat-lit" lit-form ")"
//! pat-wild ::= "_"
//! lit-form ::= integer | "true" | "false" | string
//! ```
//!
//! Notes on the form (deviations from the spec in DESIGN.md Decision 6):
//!
//! - The `lam` form carries `paramTypes`, a `ret` type, and an
//! optional `effects` clause. The original DESIGN.md sketch left
//! these out; the AST stores them per-lambda and so the form must
//! round-trip them.
//! - The `import` form admits an optional `as` alias to round-trip
//! [`ailang_core::ast::Import::alias`].
use ailang_core::ast::{
Arm, ConstDef, Ctor, Def, FnDef, Import, Literal, Module, Pattern, Term, Type, TypeDef,
};
use ailang_core::SCHEMA;
use thiserror::Error;
use crate::lex::{tokenize, LexError, Tok, Token};
/// Errors raised during parsing.
#[derive(Debug, Error)]
pub enum ParseError {
#[error("lex error: {0}")]
Lex(#[from] LexError),
#[error("parse error: expected {expected}, got {got} at byte {pos}")]
Unexpected {
expected: String,
got: String,
pos: usize,
},
#[error("parse error: unexpected end of input, expected {expected}")]
UnexpectedEof { expected: String },
#[error("parse error in {production}: {message} at byte {pos}")]
Production {
production: &'static str,
message: String,
pos: usize,
},
}
/// Parse a form-(A) source string into an [`ailang_core::ast::Module`].
///
/// The schema field is set to [`ailang_core::SCHEMA`]; the form does
/// not carry it explicitly because the form itself implies the
/// version.
pub fn parse(input: &str) -> Result<Module, ParseError> {
let toks = tokenize(input)?;
let mut p = Parser::new(&toks);
let m = p.parse_module()?;
if p.cur < p.toks.len() {
return Err(ParseError::Unexpected {
expected: "end of input".into(),
got: tok_label(&p.toks[p.cur].tok),
pos: p.toks[p.cur].span.start,
});
}
Ok(m)
}
fn tok_label(t: &Tok) -> String {
match t {
Tok::LParen => "`(`".into(),
Tok::RParen => "`)`".into(),
Tok::Int(v) => format!("integer `{v}`"),
Tok::Str(s) => format!("string {s:?}"),
Tok::Ident(s) => format!("ident `{s}`"),
}
}
struct Parser<'a> {
toks: &'a [Token],
cur: usize,
}
impl<'a> Parser<'a> {
fn new(toks: &'a [Token]) -> Self {
Self { toks, cur: 0 }
}
fn peek(&self) -> Option<&Token> {
self.toks.get(self.cur)
}
fn expect_lparen(&mut self, ctx: &'static str) -> Result<(), ParseError> {
match self.peek() {
Some(Token { tok: Tok::LParen, .. }) => {
self.cur += 1;
Ok(())
}
Some(t) => Err(ParseError::Unexpected {
expected: format!("`(` (start of {ctx})"),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: format!("`(` (start of {ctx})"),
}),
}
}
fn expect_rparen(&mut self, ctx: &'static str) -> Result<(), ParseError> {
match self.peek() {
Some(Token { tok: Tok::RParen, .. }) => {
self.cur += 1;
Ok(())
}
Some(t) => Err(ParseError::Unexpected {
expected: format!("`)` (end of {ctx})"),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: format!("`)` (end of {ctx})"),
}),
}
}
/// Consume an ident atom matching `expected`. Used for keyword tags
/// like `module`, `data`, `con`, etc.
fn expect_keyword(&mut self, kw: &'static str) -> Result<(), ParseError> {
match self.peek() {
Some(Token { tok: Tok::Ident(s), span }) if s == kw => {
let _ = span;
self.cur += 1;
Ok(())
}
Some(t) => Err(ParseError::Unexpected {
expected: format!("`{kw}`"),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: format!("`{kw}`"),
}),
}
}
/// Consume any ident atom and return its text.
fn expect_ident(&mut self, ctx: &'static str) -> Result<String, ParseError> {
match self.peek().cloned() {
Some(Token { tok: Tok::Ident(s), .. }) => {
self.cur += 1;
Ok(s)
}
Some(t) => Err(ParseError::Unexpected {
expected: format!("ident ({ctx})"),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: format!("ident ({ctx})"),
}),
}
}
/// Try to consume an ident matching `kw`. On success advance and
/// return true; otherwise leave position unchanged and return
/// false.
fn try_keyword(&mut self, kw: &str) -> bool {
match self.peek() {
Some(Token { tok: Tok::Ident(s), .. }) if s == kw => {
self.cur += 1;
true
}
_ => false,
}
}
/// Look at the head ident of a parenthesised form without
/// consuming. Used to dispatch on the head keyword.
fn peek_head_ident(&self) -> Option<&str> {
if let Some(Token { tok: Tok::LParen, .. }) = self.toks.get(self.cur) {
if let Some(Token { tok: Tok::Ident(s), .. }) = self.toks.get(self.cur + 1) {
return Some(s.as_str());
}
}
None
}
// ---- module ---------------------------------------------------------
fn parse_module(&mut self) -> Result<Module, ParseError> {
self.expect_lparen("module")?;
self.expect_keyword("module")?;
let name = self.expect_ident("module name")?;
let mut imports: Vec<Import> = Vec::new();
let mut defs: Vec<Def> = Vec::new();
loop {
match self.peek() {
Some(Token { tok: Tok::RParen, .. }) => break,
None => {
return Err(ParseError::UnexpectedEof {
expected: "`)` to end module or another def".into(),
});
}
_ => {}
}
let head = self.peek_head_ident().ok_or_else(|| {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
ParseError::Production {
production: "module",
message: "expected `(` followed by a def-head keyword (\
`data`, `fn`, `const`, `import`)"
.into(),
pos,
}
})?;
match head {
"import" => imports.push(self.parse_import()?),
"data" => defs.push(Def::Type(self.parse_data()?)),
"fn" => defs.push(Def::Fn(self.parse_fn()?)),
"const" => defs.push(Def::Const(self.parse_const()?)),
other => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "module",
message: format!(
"unknown def head `{other}`; expected `data`, `fn`, `const`, or `import`"
),
pos,
});
}
}
}
self.expect_rparen("module")?;
Ok(Module {
schema: SCHEMA.to_string(),
name,
imports,
defs,
})
}
// ---- imports --------------------------------------------------------
fn parse_import(&mut self) -> Result<Import, ParseError> {
self.expect_lparen("import-clause")?;
self.expect_keyword("import")?;
let module = self.expect_ident("import module name")?;
let alias = if self.try_keyword("as") {
Some(self.expect_ident("import alias")?)
} else {
None
};
self.expect_rparen("import-clause")?;
Ok(Import { module, alias })
}
// ---- data def -------------------------------------------------------
fn parse_data(&mut self) -> Result<TypeDef, ParseError> {
self.expect_lparen("data-def")?;
self.expect_keyword("data")?;
let name = self.expect_ident("data name")?;
// Optional vars clause: `(vars a b ...)`
let mut vars: Vec<String> = Vec::new();
if let Some("vars") = self.peek_head_ident() {
self.expect_lparen("vars-clause")?;
self.expect_keyword("vars")?;
// vars+ : at least one
let first = self.expect_ident("type variable")?;
vars.push(first);
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
vars.push(self.expect_ident("type variable")?);
}
self.expect_rparen("vars-clause")?;
}
let mut doc: Option<String> = None;
let mut ctors: Vec<Ctor> = Vec::new();
loop {
match self.peek_head_ident() {
Some("doc") => {
let s = self.parse_doc()?;
doc = Some(s);
}
Some("ctor") => {
ctors.push(self.parse_ctor()?);
}
Some(other) => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "data-def",
message: format!(
"unknown data attribute `{other}`; expected `doc` or `ctor`"
),
pos,
});
}
None => break,
}
}
self.expect_rparen("data-def")?;
Ok(TypeDef {
name,
vars,
ctors,
doc,
})
}
fn parse_doc(&mut self) -> Result<String, ParseError> {
self.expect_lparen("doc-attr")?;
self.expect_keyword("doc")?;
let s = match self.peek().cloned() {
Some(Token { tok: Tok::Str(s), .. }) => {
self.cur += 1;
s
}
Some(t) => {
return Err(ParseError::Unexpected {
expected: "string literal (doc body)".into(),
got: tok_label(&t.tok),
pos: t.span.start,
});
}
None => {
return Err(ParseError::UnexpectedEof {
expected: "string literal (doc body)".into(),
});
}
};
self.expect_rparen("doc-attr")?;
Ok(s)
}
fn parse_ctor(&mut self) -> Result<Ctor, ParseError> {
self.expect_lparen("ctor-decl")?;
self.expect_keyword("ctor")?;
let name = self.expect_ident("ctor name")?;
let mut fields: Vec<Type> = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
fields.push(self.parse_type()?);
}
self.expect_rparen("ctor-decl")?;
Ok(Ctor { name, fields })
}
// ---- fn def ---------------------------------------------------------
fn parse_fn(&mut self) -> Result<FnDef, ParseError> {
self.expect_lparen("fn-def")?;
self.expect_keyword("fn")?;
let name = self.expect_ident("fn name")?;
let mut doc: Option<String> = None;
let mut ty: Option<Type> = None;
let mut params: Option<Vec<String>> = None;
let mut body: Option<Term> = None;
loop {
match self.peek_head_ident() {
Some("doc") => doc = Some(self.parse_doc()?),
Some("type") => {
let t = self.parse_type_attr()?;
ty = Some(t);
}
Some("params") => {
let p = self.parse_params_attr()?;
params = Some(p);
}
Some("body") => {
let b = self.parse_body_attr()?;
body = Some(b);
}
Some(other) => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "fn-def",
message: format!(
"unknown fn attribute `{other}`; expected `doc`, `type`, `params`, or `body`"
),
pos,
});
}
None => break,
}
}
self.expect_rparen("fn-def")?;
let ty = ty.ok_or_else(|| ParseError::Production {
production: "fn-def",
message: format!("fn `{name}` is missing required `(type ...)` attribute"),
pos: 0,
})?;
let params = params.ok_or_else(|| ParseError::Production {
production: "fn-def",
message: format!("fn `{name}` is missing required `(params ...)` attribute"),
pos: 0,
})?;
let body = body.ok_or_else(|| ParseError::Production {
production: "fn-def",
message: format!("fn `{name}` is missing required `(body ...)` attribute"),
pos: 0,
})?;
Ok(FnDef {
name,
ty,
params,
body,
doc,
})
}
fn parse_type_attr(&mut self) -> Result<Type, ParseError> {
self.expect_lparen("type-attr")?;
self.expect_keyword("type")?;
let t = self.parse_type()?;
self.expect_rparen("type-attr")?;
Ok(t)
}
fn parse_params_attr(&mut self) -> Result<Vec<String>, ParseError> {
self.expect_lparen("params-attr")?;
self.expect_keyword("params")?;
let mut out = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
out.push(self.expect_ident("param name")?);
}
self.expect_rparen("params-attr")?;
Ok(out)
}
fn parse_body_attr(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("body-attr")?;
self.expect_keyword("body")?;
let t = self.parse_term()?;
self.expect_rparen("body-attr")?;
Ok(t)
}
// ---- const def ------------------------------------------------------
fn parse_const(&mut self) -> Result<ConstDef, ParseError> {
self.expect_lparen("const-def")?;
self.expect_keyword("const")?;
let name = self.expect_ident("const name")?;
let mut doc: Option<String> = None;
let mut ty: Option<Type> = None;
let mut value: Option<Term> = None;
loop {
match self.peek_head_ident() {
Some("doc") => doc = Some(self.parse_doc()?),
Some("type") => ty = Some(self.parse_type_attr()?),
Some("body") => value = Some(self.parse_body_attr()?),
Some(other) => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "const-def",
message: format!(
"unknown const attribute `{other}`; expected `doc`, `type`, or `body`"
),
pos,
});
}
None => break,
}
}
self.expect_rparen("const-def")?;
let ty = ty.ok_or_else(|| ParseError::Production {
production: "const-def",
message: format!("const `{name}` is missing required `(type ...)`"),
pos: 0,
})?;
let value = value.ok_or_else(|| ParseError::Production {
production: "const-def",
message: format!("const `{name}` is missing required `(body ...)`"),
pos: 0,
})?;
Ok(ConstDef {
name,
ty,
value,
doc,
})
}
// ---- types ----------------------------------------------------------
fn parse_type(&mut self) -> Result<Type, ParseError> {
match self.peek() {
Some(Token { tok: Tok::LParen, .. }) => {
let head = self.peek_head_ident().ok_or_else(|| {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
ParseError::Production {
production: "type",
message: "expected `(con ...)`, `(fn-type ...)`, or `(forall ...)`"
.into(),
pos,
}
})?;
match head {
"con" => self.parse_type_con(),
"fn-type" => self.parse_fn_type(),
"forall" => self.parse_forall_type(),
other => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
Err(ParseError::Production {
production: "type",
message: format!(
"unknown type head `{other}`; expected `con`, `fn-type`, or `forall`"
),
pos,
})
}
}
}
Some(Token { tok: Tok::Ident(s), .. }) => {
let s = s.clone();
self.cur += 1;
Ok(Type::Var { name: s })
}
Some(t) => Err(ParseError::Unexpected {
expected: "type expression".into(),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: "type expression".into(),
}),
}
}
fn parse_type_con(&mut self) -> Result<Type, ParseError> {
self.expect_lparen("type-con")?;
self.expect_keyword("con")?;
let name = self.expect_ident("type constructor name")?;
let mut args: Vec<Type> = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
args.push(self.parse_type()?);
}
self.expect_rparen("type-con")?;
Ok(Type::Con { name, args })
}
fn parse_fn_type(&mut self) -> Result<Type, ParseError> {
self.expect_lparen("fn-type")?;
self.expect_keyword("fn-type")?;
// (params type*)
self.expect_lparen("fn-type params")?;
self.expect_keyword("params")?;
let mut params: Vec<Type> = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
params.push(self.parse_type()?);
}
self.expect_rparen("fn-type params")?;
// (ret type)
self.expect_lparen("fn-type ret")?;
self.expect_keyword("ret")?;
let ret = self.parse_type()?;
self.expect_rparen("fn-type ret")?;
// optional (effects ident+)
let mut effects: Vec<String> = Vec::new();
if let Some("effects") = self.peek_head_ident() {
effects = self.parse_effects_clause()?;
}
self.expect_rparen("fn-type")?;
Ok(Type::Fn {
params,
ret: Box::new(ret),
effects,
})
}
fn parse_effects_clause(&mut self) -> Result<Vec<String>, ParseError> {
self.expect_lparen("effects-clause")?;
self.expect_keyword("effects")?;
// 1+ idents per grammar; round-trip allows empty too (to support
// any future use), but the printer never emits an empty clause.
let mut out = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
out.push(self.expect_ident("effect name")?);
}
if out.is_empty() {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "effects-clause",
message: "expected at least one effect name".into(),
pos,
});
}
self.expect_rparen("effects-clause")?;
Ok(out)
}
fn parse_forall_type(&mut self) -> Result<Type, ParseError> {
self.expect_lparen("forall-type")?;
self.expect_keyword("forall")?;
// (vars ident+)
self.expect_lparen("forall vars")?;
self.expect_keyword("vars")?;
let mut vars = Vec::new();
let first = self.expect_ident("type variable")?;
vars.push(first);
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
vars.push(self.expect_ident("type variable")?);
}
self.expect_rparen("forall vars")?;
let body = self.parse_type()?;
self.expect_rparen("forall-type")?;
Ok(Type::Forall {
vars,
body: Box::new(body),
})
}
// ---- terms ----------------------------------------------------------
fn parse_term(&mut self) -> Result<Term, ParseError> {
match self.peek().cloned() {
Some(Token { tok: Tok::LParen, .. }) => {
let head = self.peek_head_ident().ok_or_else(|| {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
ParseError::Production {
production: "term",
message: "expected a term-head keyword after `(`".into(),
pos,
}
})?;
match head {
"lit-unit" => self.parse_lit_unit(),
"app" => self.parse_app(),
"tail-app" => self.parse_tail_app(),
"term-ctor" => self.parse_term_ctor(),
"match" => self.parse_match(),
"do" => self.parse_do(),
"tail-do" => self.parse_tail_do(),
"seq" => self.parse_seq(),
"lam" => self.parse_lam(),
"if" => self.parse_if(),
"let" => self.parse_let(),
"let-rec" => self.parse_let_rec(),
other => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
Err(ParseError::Production {
production: "term",
message: format!(
"unknown term head `{other}`; expected one of \
`app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, \
`tail-do`, `seq`, `term-ctor`, `lit-unit`"
),
pos,
})
}
}
}
Some(Token { tok: Tok::Ident(s), .. }) => {
self.cur += 1;
if s == "true" {
Ok(Term::Lit { lit: Literal::Bool { value: true } })
} else if s == "false" {
Ok(Term::Lit { lit: Literal::Bool { value: false } })
} else {
Ok(Term::Var { name: s })
}
}
Some(Token { tok: Tok::Int(v), .. }) => {
self.cur += 1;
Ok(Term::Lit { lit: Literal::Int { value: v } })
}
Some(Token { tok: Tok::Str(s), .. }) => {
self.cur += 1;
Ok(Term::Lit { lit: Literal::Str { value: s } })
}
Some(t) => Err(ParseError::Unexpected {
expected: "term".into(),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: "term".into(),
}),
}
}
fn parse_lit_unit(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("unit-lit")?;
self.expect_keyword("lit-unit")?;
self.expect_rparen("unit-lit")?;
Ok(Term::Lit { lit: Literal::Unit })
}
fn parse_app(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("app-term")?;
self.expect_keyword("app")?;
self.parse_app_body(false, "app-term")
}
/// Iter 14e: `(tail-app callee arg+)` — same shape as `app` but
/// constructs `Term::App { tail: true, .. }`. The typechecker's
/// tail-position pass verifies that the call really is in tail
/// position; an unmarked call in tail position is also legal.
fn parse_tail_app(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("tail-app-term")?;
self.expect_keyword("tail-app")?;
self.parse_app_body(true, "tail-app-term")
}
/// Body shared by [`Self::parse_app`] and [`Self::parse_tail_app`]:
/// callee + 1+ args + closing `)`.
fn parse_app_body(
&mut self,
tail: bool,
production: &'static str,
) -> Result<Term, ParseError> {
let callee = self.parse_term()?;
// 1+ args
if matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production,
message: "expected at least one argument".into(),
pos,
});
}
let mut args = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
args.push(self.parse_term()?);
}
self.expect_rparen(production)?;
Ok(Term::App {
callee: Box::new(callee),
args,
tail,
})
}
fn parse_term_ctor(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("ctor-term")?;
self.expect_keyword("term-ctor")?;
let type_name = self.expect_ident("ADT type name")?;
let ctor = self.expect_ident("ctor name")?;
let mut args = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
args.push(self.parse_term()?);
}
self.expect_rparen("ctor-term")?;
Ok(Term::Ctor {
type_name,
ctor,
args,
})
}
fn parse_match(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("match-term")?;
self.expect_keyword("match")?;
let scrutinee = self.parse_term()?;
let mut arms = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
arms.push(self.parse_case_arm()?);
}
if arms.is_empty() {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
return Err(ParseError::Production {
production: "match-term",
message: "expected at least one `(case ...)` arm".into(),
pos,
});
}
self.expect_rparen("match-term")?;
Ok(Term::Match {
scrutinee: Box::new(scrutinee),
arms,
})
}
fn parse_case_arm(&mut self) -> Result<Arm, ParseError> {
self.expect_lparen("case-arm")?;
self.expect_keyword("case")?;
let pat = self.parse_pattern()?;
let body = self.parse_term()?;
self.expect_rparen("case-arm")?;
Ok(Arm { pat, body })
}
fn parse_do(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("do-term")?;
self.expect_keyword("do")?;
self.parse_do_body(false, "do-term")
}
/// Iter 14e: `(tail-do op arg*)` — same shape as `do` but
/// constructs `Term::Do { tail: true, .. }`.
fn parse_tail_do(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("tail-do-term")?;
self.expect_keyword("tail-do")?;
self.parse_do_body(true, "tail-do-term")
}
/// Body shared by [`Self::parse_do`] and [`Self::parse_tail_do`].
fn parse_do_body(
&mut self,
tail: bool,
production: &'static str,
) -> Result<Term, ParseError> {
let op = self.expect_ident("effect op (e.g. `io/print_int`)")?;
let mut args = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
args.push(self.parse_term()?);
}
self.expect_rparen(production)?;
Ok(Term::Do { op, args, tail })
}
fn parse_seq(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("seq-term")?;
self.expect_keyword("seq")?;
let lhs = self.parse_term()?;
let rhs = self.parse_term()?;
self.expect_rparen("seq-term")?;
Ok(Term::Seq {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
})
}
fn parse_lam(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("lam-term")?;
self.expect_keyword("lam")?;
// (params (typed name type)*)
self.expect_lparen("lam params")?;
self.expect_keyword("params")?;
let mut params: Vec<String> = Vec::new();
let mut param_tys: Vec<Type> = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
self.expect_lparen("typed-param")?;
self.expect_keyword("typed")?;
let pname = self.expect_ident("lambda param name")?;
let pty = self.parse_type()?;
self.expect_rparen("typed-param")?;
params.push(pname);
param_tys.push(pty);
}
self.expect_rparen("lam params")?;
// (ret type)
self.expect_lparen("lam ret")?;
self.expect_keyword("ret")?;
let ret_ty = self.parse_type()?;
self.expect_rparen("lam ret")?;
// optional effects clause
let mut effects: Vec<String> = Vec::new();
if let Some("effects") = self.peek_head_ident() {
effects = self.parse_effects_clause()?;
}
// (body term)
let body = self.parse_body_attr()?;
self.expect_rparen("lam-term")?;
Ok(Term::Lam {
params,
param_tys,
ret_ty: Box::new(ret_ty),
effects,
body: Box::new(body),
})
}
fn parse_if(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("if-term")?;
self.expect_keyword("if")?;
let cond = self.parse_term()?;
let then = self.parse_term()?;
let else_ = self.parse_term()?;
self.expect_rparen("if-term")?;
Ok(Term::If {
cond: Box::new(cond),
then: Box::new(then),
else_: Box::new(else_),
})
}
fn parse_let(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("let-term")?;
self.expect_keyword("let")?;
let name = self.expect_ident("let-bound name")?;
let value = self.parse_term()?;
let body = self.parse_term()?;
self.expect_rparen("let-term")?;
Ok(Term::Let {
name,
value: Box::new(value),
body: Box::new(body),
})
}
/// Iter 16b.1: `(let-rec NAME (params PARAM*) (type T) (body TERM)
/// (in TERM))` — local recursive fn-shaped binding. Eliminated by
/// the desugar pass (lifted to a synthetic top-level fn) before
/// typecheck. The body's recursive references to `NAME` are
/// rewritten to the lifted name; the `in`-clause becomes the term
/// that replaces the LetRec.
fn parse_let_rec(&mut self) -> Result<Term, ParseError> {
self.expect_lparen("let-rec-term")?;
self.expect_keyword("let-rec")?;
let name = self.expect_ident("let-rec name")?;
let params = self.parse_params_attr()?;
let ty = self.parse_type_attr()?;
let body = self.parse_body_attr()?;
// (in TERM)
self.expect_lparen("let-rec in-clause")?;
self.expect_keyword("in")?;
let in_term = self.parse_term()?;
self.expect_rparen("let-rec in-clause")?;
self.expect_rparen("let-rec-term")?;
Ok(Term::LetRec {
name,
ty,
params,
body: Box::new(body),
in_term: Box::new(in_term),
})
}
// ---- patterns -------------------------------------------------------
fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
match self.peek().cloned() {
Some(Token { tok: Tok::LParen, .. }) => {
let head = self.peek_head_ident().ok_or_else(|| {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
ParseError::Production {
production: "pattern",
message: "expected `pat-ctor` or `pat-lit` after `(`".into(),
pos,
}
})?;
match head {
"pat-ctor" => self.parse_pat_ctor(),
"pat-lit" => self.parse_pat_lit(),
other => {
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
Err(ParseError::Production {
production: "pattern",
message: format!(
"unknown pattern head `{other}`; expected `pat-ctor` or `pat-lit`"
),
pos,
})
}
}
}
Some(Token { tok: Tok::Ident(s), .. }) => {
self.cur += 1;
if s == "_" {
Ok(Pattern::Wild)
} else {
Ok(Pattern::Var { name: s })
}
}
Some(t) => Err(ParseError::Unexpected {
expected: "pattern".into(),
got: tok_label(&t.tok),
pos: t.span.start,
}),
None => Err(ParseError::UnexpectedEof {
expected: "pattern".into(),
}),
}
}
fn parse_pat_ctor(&mut self) -> Result<Pattern, ParseError> {
self.expect_lparen("pat-ctor")?;
self.expect_keyword("pat-ctor")?;
let ctor = self.expect_ident("ctor name")?;
let mut fields = Vec::new();
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
fields.push(self.parse_pattern()?);
}
self.expect_rparen("pat-ctor")?;
Ok(Pattern::Ctor { ctor, fields })
}
fn parse_pat_lit(&mut self) -> Result<Pattern, ParseError> {
self.expect_lparen("pat-lit")?;
self.expect_keyword("pat-lit")?;
let lit = match self.peek().cloned() {
Some(Token { tok: Tok::Int(v), .. }) => {
self.cur += 1;
Literal::Int { value: v }
}
Some(Token { tok: Tok::Str(s), .. }) => {
self.cur += 1;
Literal::Str { value: s }
}
Some(Token { tok: Tok::Ident(s), span }) => {
if s == "true" {
self.cur += 1;
Literal::Bool { value: true }
} else if s == "false" {
self.cur += 1;
Literal::Bool { value: false }
} else {
return Err(ParseError::Unexpected {
expected: "literal form (integer, string, `true`, or `false`)".into(),
got: tok_label(&Tok::Ident(s)),
pos: span.start,
});
}
}
Some(t) => {
return Err(ParseError::Unexpected {
expected: "literal form (integer, string, `true`, or `false`)".into(),
got: tok_label(&t.tok),
pos: t.span.start,
});
}
None => {
return Err(ParseError::UnexpectedEof {
expected: "literal form".into(),
});
}
};
self.expect_rparen("pat-lit")?;
Ok(Pattern::Lit { lit })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_minimal_module() {
let m = parse(
r#"
(module hello
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body (do io/print_str "Hello, AILang."))))
"#,
)
.unwrap();
assert_eq!(m.name, "hello");
assert_eq!(m.defs.len(), 1);
}
#[test]
fn parses_var_and_int() {
let m = parse(
r#"
(module m
(fn id
(type (fn-type (params (con Int)) (ret (con Int))))
(params x)
(body x)))
"#,
)
.unwrap();
assert!(matches!(m.defs.len(), 1));
}
/// Iter 16b.1: minimal `(let-rec ...)` round-trips through the
/// parser into a `Term::LetRec` whose `name`, `params`, `body` and
/// `in_term` line up with the source.
#[test]
fn parses_minimal_let_rec() {
let m = parse(
r#"
(module m
(fn main
(type (fn-type (params) (ret (con Int))))
(params)
(body
(let-rec f
(params x)
(type (fn-type (params (con Int)) (ret (con Int))))
(body x)
(in (app f 1))))))
"#,
)
.unwrap();
let body = match &m.defs[0] {
Def::Fn(fd) => &fd.body,
_ => panic!("expected fn"),
};
match body {
Term::LetRec { name, params, body, in_term, .. } => {
assert_eq!(name, "f");
assert_eq!(params, &vec!["x".to_string()]);
assert!(matches!(body.as_ref(), Term::Var { name } if name == "x"));
match in_term.as_ref() {
Term::App { callee, args, .. } => {
assert!(matches!(callee.as_ref(), Term::Var { name } if name == "f"));
assert_eq!(args.len(), 1);
}
other => panic!("expected App in in-clause, got {other:?}"),
}
}
other => panic!("expected LetRec, got {other:?}"),
}
}
}