//! 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 { 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 { 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 { self.expect_lparen("module")?; self.expect_keyword("module")?; let name = self.expect_ident("module name")?; let mut imports: Vec = Vec::new(); let mut defs: Vec = 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 { 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 { 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 = 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 = None; let mut ctors: Vec = 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 { 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 { self.expect_lparen("ctor-decl")?; self.expect_keyword("ctor")?; let name = self.expect_ident("ctor name")?; let mut fields: Vec = 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 { self.expect_lparen("fn-def")?; self.expect_keyword("fn")?; let name = self.expect_ident("fn name")?; let mut doc: Option = None; let mut ty: Option = None; let mut params: Option> = None; let mut body: Option = 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 { 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, 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 { 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 { self.expect_lparen("const-def")?; self.expect_keyword("const")?; let name = self.expect_ident("const name")?; let mut doc: Option = None; let mut ty: Option = None; let mut value: Option = 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 { 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 { self.expect_lparen("type-con")?; self.expect_keyword("con")?; let name = self.expect_ident("type constructor name")?; let mut args: Vec = 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 { 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 = 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 = 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, 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 = Vec::new(); let mut param_tys: Vec = 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 = 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 { 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 { 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 { 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 { 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 { 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 { 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:?}"), } } }