//! 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 | suppress-attr | type-attr | params-attr | body-attr //! suppress-attr ::= "(" "suppress" "(" "code" string ")" //! "(" "because" string ")" ")" //! 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-param ::= type | "(" "borrow" type ")" | "(" "own" type ")" //! fn-type ::= "(" "fn-type" "(" "params" fn-type-param* ")" //! "(" "ret" fn-type-param ")" //! 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 | clone-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 ")" ")" //! clone-term ::= "(" "clone" term ")" ; Iter 18c.1 //! reuse-as-term ::= "(" "reuse-as" term term ")" ; Iter 18d.1 //! //! 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, ClassDef, ClassMethod, Constraint, ConstDef, Ctor, Def, FnDef, Import, InstanceDef, InstanceMethod, Literal, Module, ParamMode, Pattern, SuperclassRef, Suppress, 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) } /// Parse a single form-A term — the concrete-syntax counterpart of /// [`Term`]. This is the dual of [`crate::print::term_to_form_a`] and is /// used by callers that produce form-A snippets out-of-band, e.g. the /// `suggested_rewrites` payload of `ail check --json` (Iter 18c.2). The /// input must consume to EOF after the term — extra trailing tokens /// produce [`ParseError::Unexpected`]. /// /// Round-trip: `parse_term(term_to_form_a(t)) ≡ t` for every term `t` /// the surface can express. pub fn parse_term(input: &str) -> Result { let toks = tokenize(input)?; let mut p = Parser::new(&toks); let t = p.parse_term()?; 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(t) } fn tok_label(t: &Tok) -> String { match t { Tok::LParen => "`(`".into(), Tok::RParen => "`)`".into(), Tok::Int(v) => format!("integer `{v}`"), Tok::Float(bits) => format!("float `0x{bits:016x}`"), 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()?)), "class" => defs.push(Def::Class(self.parse_class()?)), "instance" => defs.push(Def::Instance(self.parse_instance()?)), 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`, `class`, `instance`, 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(); let mut drop_iterative = false; loop { match self.peek_head_ident() { Some("doc") => { if doc.is_some() { return Err(self.duplicate_clause_err("data-def", &format!("data `{name}`"), "doc")); } doc = Some(self.parse_doc()?); } Some("ctor") => { ctors.push(self.parse_ctor()?); } Some("drop-iterative") => { // Iter 18e: `(drop-iterative)` opt-in annotation. // Takes no arguments — it is a flag. A second // `(drop-iterative)` clause is a parse error // (rejected here so that the JSON schema's // `drop_iterative: bool` round-trips unambiguously). self.expect_lparen("drop-iterative-attr")?; self.expect_keyword("drop-iterative")?; 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: "data-def", message: "drop-iterative takes no arguments; expected `)`" .into(), pos, }); } self.expect_rparen("drop-iterative-attr")?; if drop_iterative { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "data-def", message: "duplicate `drop-iterative` attribute" .into(), pos, }); } drop_iterative = true; } 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`, `ctor`, or `drop-iterative`" ), pos, }); } None => break, } } self.expect_rparen("data-def")?; Ok(TypeDef { name, vars, ctors, doc, drop_iterative, }) } 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; // Iter 19b: every `(suppress ...)` clause appends one entry. Order // is preserved (matches the on-disk JSON-AST order). let mut suppress: Vec = Vec::new(); loop { match self.peek_head_ident() { Some("doc") => { if doc.is_some() { return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "doc")); } doc = Some(self.parse_doc()?); } Some("suppress") => suppress.push(self.parse_suppress_attr()?), Some("type") => { if ty.is_some() { return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "type")); } ty = Some(self.parse_type_attr()?); } Some("params") => { if params.is_some() { return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "params")); } params = Some(self.parse_params_attr()?); } Some("body") => { if body.is_some() { return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "body")); } body = Some(self.parse_body_attr()?); } 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`, `suppress`, `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, suppress, }) } /// Iter 19b: parse one `(suppress (code "") (because ""))` /// clause, returning a [`Suppress`] entry. Unknown sub-keywords /// inside the clause are rejected with [`ParseError::Production`]. /// The `because` text is allowed to be empty here (the typechecker /// emits `empty-suppress-reason` instead of the parser, so the /// invalid form round-trips through the surface for diagnostic /// purposes). fn parse_suppress_attr(&mut self) -> Result { self.expect_lparen("suppress-attr")?; self.expect_keyword("suppress")?; let mut code: Option = None; let mut because: Option = None; loop { match self.peek_head_ident() { Some("code") => { self.expect_lparen("suppress.code")?; self.expect_keyword("code")?; code = Some(self.expect_string("code body")?); self.expect_rparen("suppress.code")?; } Some("because") => { self.expect_lparen("suppress.because")?; self.expect_keyword("because")?; because = Some(self.expect_string("because body")?); self.expect_rparen("suppress.because")?; } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "suppress-attr", message: format!( "unknown suppress sub-attribute `{other}`; expected `code` or `because`" ), pos, }); } None => break, } } self.expect_rparen("suppress-attr")?; let code = code.ok_or_else(|| ParseError::Production { production: "suppress-attr", message: "suppress is missing required `(code ...)`".into(), pos: 0, })?; let because = because.ok_or_else(|| ParseError::Production { production: "suppress-attr", message: "suppress is missing required `(because ...)`".into(), pos: 0, })?; Ok(Suppress { code, because }) } /// Iter 19b: helper — consume one string-literal token. Used by /// [`Self::parse_suppress_attr`]. fn expect_string(&mut self, ctx: &'static str) -> Result { match self.peek().cloned() { Some(Token { tok: Tok::Str(s), .. }) => { self.cur += 1; Ok(s) } Some(t) => Err(ParseError::Unexpected { expected: format!("string literal ({ctx})"), got: tok_label(&t.tok), pos: t.span.start, }), None => Err(ParseError::UnexpectedEof { expected: format!("string literal ({ctx})"), }), } } fn duplicate_clause_err( &self, production: &'static str, subject: &str, clause: &'static str, ) -> ParseError { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); ParseError::Production { production, message: format!("{subject} has duplicate `({clause} ...)` clause"), pos, } } 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") => { if doc.is_some() { return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "doc")); } doc = Some(self.parse_doc()?); } Some("type") => { if ty.is_some() { return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "type")); } ty = Some(self.parse_type_attr()?); } Some("body") => { if value.is_some() { return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "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, }) } // ---- class def ----------------------------------------------------- fn parse_class(&mut self) -> Result { self.expect_lparen("class-def")?; self.expect_keyword("class")?; let name = self.expect_ident("class name")?; let mut param: Option = None; let mut superclass: Option = None; let mut doc: Option = None; let mut methods: Vec = Vec::new(); loop { match self.peek_head_ident() { Some("param") => { if param.is_some() { return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "param")); } self.expect_lparen("class.param")?; self.expect_keyword("param")?; param = Some(self.expect_ident("class param ident")?); self.expect_rparen("class.param")?; } Some("superclass") => { if superclass.is_some() { return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "superclass")); } superclass = Some(self.parse_superclass()?); } Some("doc") => { if doc.is_some() { return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "doc")); } doc = Some(self.parse_doc()?); } Some("method") => { methods.push(self.parse_class_method()?); } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "class-def", message: format!( "unknown class attribute `{other}`; expected `param`, `superclass`, `doc`, or `method`" ), pos, }); } None => break, } } self.expect_rparen("class-def")?; let param = param.ok_or_else(|| ParseError::Production { production: "class-def", message: format!("class `{name}` is missing required `(param ...)` clause"), pos: 0, })?; Ok(ClassDef { name, param, superclass, methods, doc, }) } fn parse_superclass(&mut self) -> Result { self.expect_lparen("superclass-clause")?; self.expect_keyword("superclass")?; // (class Name) self.expect_lparen("superclass.class")?; self.expect_keyword("class")?; let class = self.expect_ident("superclass name")?; self.expect_rparen("superclass.class")?; // (type ident) self.expect_lparen("superclass.type")?; self.expect_keyword("type")?; let type_ = self.expect_ident("superclass param ident")?; self.expect_rparen("superclass.type")?; self.expect_rparen("superclass-clause")?; Ok(SuperclassRef { class, type_ }) } fn parse_class_method(&mut self) -> Result { self.expect_lparen("class.method")?; self.expect_keyword("method")?; let name = self.expect_ident("class method name")?; let mut ty: Option = None; let mut default: Option = None; loop { match self.peek_head_ident() { Some("type") => { if ty.is_some() { return Err(self.duplicate_clause_err("class.method", &format!("method `{name}`"), "type")); } ty = Some(self.parse_type_attr()?); } Some("default") => { if default.is_some() { return Err(self.duplicate_clause_err("class.method", &format!("method `{name}`"), "default")); } default = Some(self.parse_default_attr()?); } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "class.method", message: format!( "unknown class.method attribute `{other}`; expected `type` or `default`" ), pos, }); } None => break, } } self.expect_rparen("class.method")?; let ty = ty.ok_or_else(|| ParseError::Production { production: "class.method", message: format!("method `{name}` is missing required `(type ...)` clause"), pos: 0, })?; Ok(ClassMethod { name, ty, default }) } fn parse_default_attr(&mut self) -> Result { self.expect_lparen("default-attr")?; self.expect_keyword("default")?; let t = self.parse_term()?; self.expect_rparen("default-attr")?; Ok(t) } // ---- instance def -------------------------------------------------- fn parse_instance(&mut self) -> Result { self.expect_lparen("instance-def")?; self.expect_keyword("instance")?; let mut class: Option = None; let mut type_: Option = None; let mut doc: Option = None; let mut methods: Vec = Vec::new(); loop { match self.peek_head_ident() { Some("class") => { if class.is_some() { return Err(self.duplicate_clause_err("instance-def", "instance", "class")); } self.expect_lparen("instance.class")?; self.expect_keyword("class")?; class = Some(self.expect_ident("instance class name")?); self.expect_rparen("instance.class")?; } Some("type") => { if type_.is_some() { return Err(self.duplicate_clause_err("instance-def", "instance", "type")); } type_ = Some(self.parse_type_attr()?); } Some("doc") => { if doc.is_some() { return Err(self.duplicate_clause_err("instance-def", "instance", "doc")); } doc = Some(self.parse_doc()?); } Some("method") => { methods.push(self.parse_instance_method()?); } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "instance-def", message: format!( "unknown instance attribute `{other}`; expected `class`, `type`, `doc`, or `method`" ), pos, }); } None => break, } } self.expect_rparen("instance-def")?; let class = class.ok_or_else(|| ParseError::Production { production: "instance-def", message: "instance is missing required `(class ...)` clause".into(), pos: 0, })?; let type_ = type_.ok_or_else(|| ParseError::Production { production: "instance-def", message: "instance is missing required `(type ...)` clause".into(), pos: 0, })?; Ok(InstanceDef { class, type_, methods, doc, }) } fn parse_instance_method(&mut self) -> Result { self.expect_lparen("instance.method")?; self.expect_keyword("method")?; let name = self.expect_ident("instance method name")?; let mut body: Option = None; loop { match self.peek_head_ident() { Some("body") => { if body.is_some() { return Err(self.duplicate_clause_err("instance.method", &format!("instance method `{name}`"), "body")); } body = Some(self.parse_body_attr()?); } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "instance.method", message: format!( "unknown instance.method attribute `{other}`; expected `body`" ), pos, }); } None => break, } } self.expect_rparen("instance.method")?; let body = body.ok_or_else(|| ParseError::Production { production: "instance.method", message: format!( "instance method `{name}` is missing required `(body ...)` clause" ), pos: 0, })?; Ok(InstanceMethod { name, body }) } // ---- 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(), // Iter 18a: `borrow` / `own` are valid only as // wrappers around `fn-type` params or `ret`. At // top-level type position they are a parse error // with a clear message. "borrow" | "own" => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); Err(ParseError::Production { production: "type", message: format!( "`{head}` may only appear inside fn-type params or ret" ), pos, }) } 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 fn-type-param*) self.expect_lparen("fn-type params")?; self.expect_keyword("params")?; let mut params: Vec = Vec::new(); let mut param_modes: Vec = Vec::new(); while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) { let (t, m) = self.parse_param_with_mode()?; params.push(t); param_modes.push(m); } self.expect_rparen("fn-type params")?; // (ret fn-type-param) self.expect_lparen("fn-type ret")?; self.expect_keyword("ret")?; let (ret, ret_mode) = self.parse_param_with_mode()?; 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")?; // If every entry is Implicit, store as `vec![]` so canonical // JSON serialisation omits the field — preserves pre-18a // hashes for any fixture that still uses bare types. let stored_modes = if param_modes.iter().all(|m| matches!(m, ParamMode::Implicit)) { Vec::new() } else { param_modes }; Ok(Type::Fn { params, ret: Box::new(ret), effects, param_modes: stored_modes, ret_mode, }) } /// Iter 18a: parse one fn-type slot — a type, optionally wrapped /// in `(borrow T)` or `(own T)`. The mode is `Implicit` for a /// bare type, `Borrow` for `(borrow T)`, `Own` for `(own T)`. fn parse_param_with_mode(&mut self) -> Result<(Type, ParamMode), ParseError> { if let Some(head) = self.peek_head_ident() { match head { "borrow" => { self.expect_lparen("borrow")?; self.expect_keyword("borrow")?; let inner = self.parse_type()?; self.expect_rparen("borrow")?; return Ok((inner, ParamMode::Borrow)); } "own" => { self.expect_lparen("own")?; self.expect_keyword("own")?; let inner = self.parse_type()?; self.expect_rparen("own")?; return Ok((inner, ParamMode::Own)); } _ => {} } } let t = self.parse_type()?; Ok((t, ParamMode::Implicit)) } 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")?; // optional (constraints (constraint )+ ) let constraints = if let Some("constraints") = self.peek_head_ident() { self.parse_forall_constraints_clause()? } else { Vec::new() }; let body = self.parse_type()?; self.expect_rparen("forall-type")?; Ok(Type::Forall { vars, constraints, body: Box::new(body), }) } fn parse_forall_constraints_clause(&mut self) -> Result, ParseError> { self.expect_lparen("forall.constraints")?; self.expect_keyword("constraints")?; let mut out = Vec::new(); while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) { out.push(self.parse_constraint()?); } if out.is_empty() { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { production: "forall.constraints", message: "(constraints …) clause must contain at least one (constraint …)".into(), pos, }); } self.expect_rparen("forall.constraints")?; Ok(out) } fn parse_constraint(&mut self) -> Result { self.expect_lparen("constraint")?; self.expect_keyword("constraint")?; let class = self.expect_ident("constraint class name")?; let type_ = self.parse_type()?; self.expect_rparen("constraint")?; Ok(Constraint { class, type_ }) } // ---- 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(), "clone" => self.parse_clone(), "reuse-as" => self.parse_reuse_as(), 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`, `clone`, `reuse-as`, `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::Float(bits), .. }) => { self.cur += 1; Ok(Term::Lit { lit: Literal::Float { bits } }) } 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), }) } /// Iter 18c.1: `(clone TERM)` — explicit RC clone wrapper. In 18c.1 /// the wrapper is identity for typechecker and codegen; in 18c.3 /// the codegen will emit `call void @ailang_rc_inc` before yielding /// the inner value's SSA reg under `--alloc=rc`. fn parse_clone(&mut self) -> Result { self.expect_lparen("clone-term")?; self.expect_keyword("clone")?; // Exactly one inner term, then `)`. 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: "clone-term", message: "clone expects exactly one term argument".into(), pos, }); } let inner = self.parse_term()?; 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: "clone-term", message: "clone expects exactly one term argument".into(), pos, }); } self.expect_rparen("clone-term")?; Ok(Term::Clone { value: Box::new(inner), }) } /// Iter 18d.1: `(reuse-as SOURCE BODY)` — explicit reuse-as wrapper. /// In 18d.1 the wrapper is identity for codegen (lowers `body` and /// drops `source`); 18d.2 will lower this as in-place rewrite under /// `--alloc=rc`. The parser accepts any term in either slot — the /// "source must be a bare Var" rule and the "body must be allocating" /// rule are enforced at typecheck/linearity time, not the parser. fn parse_reuse_as(&mut self) -> Result { self.expect_lparen("reuse-as-term")?; self.expect_keyword("reuse-as")?; // Exactly two inner terms, then `)`. 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: "reuse-as-term", message: "reuse-as expects exactly two term arguments".into(), pos, }); } let source = self.parse_term()?; 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: "reuse-as-term", message: "reuse-as expects exactly two term arguments".into(), pos, }); } let body = self.parse_term()?; 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: "reuse-as-term", message: "reuse-as expects exactly two term arguments".into(), pos, }); } self.expect_rparen("reuse-as-term")?; Ok(Term::ReuseAs { source: Box::new(source), body: Box::new(body), }) } // ---- 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::Float(bits), .. }) => { self.cur += 1; Literal::Float { bits } } 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, float, string, `true`, or `false`)".into(), got: tok_label(&Tok::Ident(s)), pos: span.start, }); } } Some(t) => { return Err(ParseError::Unexpected { expected: "literal form (integer, float, 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 18a: `(borrow T)` and `(own T)` wrappers in fn-type /// param/ret slots round-trip into [`ParamMode::Borrow`] / /// [`ParamMode::Own`] on `Type::Fn`. A bare type stays /// [`ParamMode::Implicit`] (and its mode is elided from the /// canonical form). #[test] fn parses_borrow_and_own_modes_on_fn_type_slots() { let m = parse( r#" (module m (fn f (type (fn-type (params (borrow (con Int)) (con Bool)) (ret (own (con Int))))) (params x y) (body x))) "#, ) .unwrap(); let ty = match &m.defs[0] { Def::Fn(fd) => &fd.ty, _ => panic!("expected fn"), }; match ty { Type::Fn { param_modes, ret_mode, .. } => { assert_eq!( param_modes, &vec![ParamMode::Borrow, ParamMode::Implicit], "first param parsed as `(borrow ...)`, second as bare" ); assert_eq!(*ret_mode, ParamMode::Own, "ret parsed as `(own ...)`"); } other => panic!("expected Type::Fn, got {other:?}"), } } /// Iter 18a: `(borrow T)` outside an `fn-type` param/ret slot is /// a parse error. Specifically, a `const` whose declared type is /// `(borrow ...)` must be rejected with a clear message — modes /// are *not* a top-level type production. #[test] fn rejects_borrow_at_top_level_type_position() { let err = parse( r#" (module m (const c (type (borrow (con Int))) (body 0))) "#, ) .err() .expect("parse should fail"); let msg = format!("{err}"); assert!( msg.contains("borrow"), "diagnostic should mention `borrow`, got: {msg}" ); } /// Iter 18c.1: `(clone X)` parses to `Term::Clone { value: Var "x" }`. #[test] fn parses_clone_wraps_inner_term() { let m = parse( r#" (module m (fn id (type (fn-type (params (con Int)) (ret (con Int)))) (params x) (body (clone x)))) "#, ) .unwrap(); let body = match &m.defs[0] { Def::Fn(fd) => &fd.body, _ => panic!("expected fn"), }; match body { Term::Clone { value } => match value.as_ref() { Term::Var { name } => assert_eq!(name, "x"), other => panic!("expected Var inside Clone, got {other:?}"), }, other => panic!("expected Clone, got {other:?}"), } } /// Iter 18c.1: `(clone)` with no inner term is rejected with a /// clear message. #[test] fn rejects_clone_without_argument() { let err = parse( r#" (module m (fn id (type (fn-type (params (con Int)) (ret (con Int)))) (params x) (body (clone)))) "#, ) .err() .expect("parse should fail"); let msg = format!("{err}"); assert!( msg.contains("clone expects exactly one term argument"), "diagnostic should explain clone's arity, got: {msg}" ); } /// Iter 18d.1: `(reuse-as xs (term-ctor List Cons (...)))` parses to /// `Term::ReuseAs { source: Var "xs", body: Ctor "Cons" ... }`. #[test] fn parses_reuse_as_wraps_source_and_body() { let m = parse( r#" (module m (data List (vars a) (ctor Nil) (ctor Cons a (con List a))) (fn f (type (fn-type (params (own (con List (con Int)))) (ret (own (con List (con Int)))))) (params xs) (body (reuse-as xs (term-ctor List Cons 1 xs))))) "#, ) .unwrap(); let body = match &m.defs[1] { Def::Fn(fd) => &fd.body, _ => panic!("expected fn"), }; match body { Term::ReuseAs { source, body } => { assert!(matches!(source.as_ref(), Term::Var { name } if name == "xs")); match body.as_ref() { Term::Ctor { type_name, ctor, args } => { assert_eq!(type_name, "List"); assert_eq!(ctor, "Cons"); assert_eq!(args.len(), 2); } other => panic!("expected Ctor inside ReuseAs body, got {other:?}"), } } other => panic!("expected ReuseAs, got {other:?}"), } } /// Iter 18d.1: `(reuse-as)` with no args is rejected. #[test] fn rejects_reuse_as_with_no_arguments() { let err = parse( r#" (module m (fn f (type (fn-type (params (con Int)) (ret (con Int)))) (params x) (body (reuse-as)))) "#, ) .err() .expect("parse should fail"); let msg = format!("{err}"); assert!( msg.contains("reuse-as expects exactly two term arguments"), "diagnostic should explain reuse-as's arity, got: {msg}" ); } /// Iter 18d.1: `(reuse-as x)` with a single arg is rejected. #[test] fn rejects_reuse_as_with_one_argument() { let err = parse( r#" (module m (fn f (type (fn-type (params (con Int)) (ret (con Int)))) (params x) (body (reuse-as x)))) "#, ) .err() .expect("parse should fail"); let msg = format!("{err}"); assert!( msg.contains("reuse-as expects exactly two term arguments"), "diagnostic should explain reuse-as's arity, got: {msg}" ); } /// Iter 18d.1: round-trip via `parse_term` / `term_to_form_a` — /// `(reuse-as xs (term-ctor List Cons 1 xs))` survives a print/parse /// cycle as the same `Term::ReuseAs`. #[test] fn parse_term_round_trip_reuse_as() { use crate::print::term_to_form_a; let original = Term::ReuseAs { source: Box::new(Term::Var { name: "xs".into() }), body: Box::new(Term::Ctor { type_name: "List".into(), ctor: "Cons".into(), args: vec![ Term::Lit { lit: Literal::Int { value: 1 } }, Term::Var { name: "xs".into() }, ], }), }; let printed = term_to_form_a(&original); let parsed = parse_term(&printed).expect("parse_term should succeed"); // Compare by canonical bytes (Term has no PartialEq) — easiest // structural equality is via the printer. assert_eq!(term_to_form_a(&parsed), printed); } /// Iter 18e: `(data T (drop-iterative))` parses with /// `drop_iterative = true` AND round-trips through the printer /// back to the same canonical surface form. #[test] fn parses_drop_iterative_annotation_on_data_decl() { use crate::print::print; let src = r#" (module m (data Tree (vars a) (ctor Leaf) (ctor Node a (con Tree a) (con Tree a)) (drop-iterative))) "#; let m = parse(src).expect("parse should succeed"); match &m.defs[0] { Def::Type(td) => { assert_eq!(td.name, "Tree"); assert!(td.drop_iterative, "drop_iterative flag must be set"); } _ => panic!("expected Def::Type"), } // Round-trip through the printer. let printed = print(&m); let m2 = parse(&printed).expect("re-parse should succeed"); match &m2.defs[0] { Def::Type(td) => { assert!(td.drop_iterative, "drop_iterative must survive print/parse"); } _ => panic!("expected Def::Type"), } } /// Iter 18e: `(data T)` with no `(drop-iterative)` clause parses /// with `drop_iterative = false`. Default state must be the /// pre-18e shape so legacy fixtures' canonical bytes are stable. #[test] fn parses_data_without_drop_iterative_defaults_to_false() { let m = parse( r#" (module m (data Tree (vars a) (ctor Leaf) (ctor Node a (con Tree a) (con Tree a)))) "#, ) .expect("parse should succeed"); match &m.defs[0] { Def::Type(td) => { assert!(!td.drop_iterative, "drop_iterative must default to false"); } _ => panic!("expected Def::Type"), } } /// Iter 18e: `(drop-iterative ...arg...)` is rejected — the /// annotation is a flag with no payload. #[test] fn rejects_drop_iterative_with_arguments() { let err = parse( r#" (module m (data T (ctor MkT) (drop-iterative oops))) "#, ) .err() .expect("parse should fail"); let msg = format!("{err}"); assert!( msg.contains("drop-iterative takes no arguments"), "diagnostic should explain drop-iterative's shape, got: {msg}" ); } /// 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:?}"), } } /// Iter 19b: a `(fn ...)` carrying a single /// `(suppress (code "...") (because "..."))` clause parses into /// [`FnDef::suppress`] with the corresponding [`Suppress`] entry. #[test] fn parses_single_suppress_clause_on_fn_def() { let m = crate::parse::parse( r#" (module t (fn f (suppress (code "over-strict-mode") (because "test reason")) (type (fn-type (params) (ret (con Int)))) (params) (body 0))) "#, ) .unwrap(); match &m.defs[0] { Def::Fn(fd) => { assert_eq!(fd.suppress.len(), 1); assert_eq!(fd.suppress[0].code, "over-strict-mode"); assert_eq!(fd.suppress[0].because, "test reason"); } _ => panic!("expected fn"), } } /// Iter 19b: multiple `(suppress ...)` clauses accumulate into /// `FnDef::suppress` in declaration order. A second clause does /// NOT overwrite the first. #[test] fn parses_multiple_suppress_clauses_in_order() { let m = crate::parse::parse( r#" (module t (fn f (suppress (code "over-strict-mode") (because "first")) (suppress (code "other-code") (because "second")) (type (fn-type (params) (ret (con Int)))) (params) (body 0))) "#, ) .unwrap(); match &m.defs[0] { Def::Fn(fd) => { assert_eq!(fd.suppress.len(), 2); assert_eq!(fd.suppress[0].code, "over-strict-mode"); assert_eq!(fd.suppress[0].because, "first"); assert_eq!(fd.suppress[1].code, "other-code"); assert_eq!(fd.suppress[1].because, "second"); } _ => panic!("expected fn"), } } /// Iter 19b: a `(fn ...)` without a `(suppress ...)` clause has /// `FnDef::suppress` empty (the default — round-trip identity /// with pre-19b fixtures). #[test] fn parses_fn_def_without_suppress_has_empty_vec() { let m = crate::parse::parse( r#" (module t (fn f (type (fn-type (params) (ret (con Int)))) (params) (body 0))) "#, ) .unwrap(); match &m.defs[0] { Def::Fn(fd) => { assert!(fd.suppress.is_empty()); } _ => panic!("expected fn"), } } /// Iter 19b: a `(suppress ...)` with `(because "")` (empty /// reason) still parses cleanly — the parser is intentionally /// permissive; the typechecker is what emits /// `empty-suppress-reason`. This keeps the surface symmetric /// (a malformed input round-trips through the printer for /// diagnostic display). #[test] fn parses_suppress_with_empty_because_string() { let m = crate::parse::parse( r#" (module t (fn f (suppress (code "over-strict-mode") (because "")) (type (fn-type (params) (ret (con Int)))) (params) (body 0))) "#, ) .unwrap(); match &m.defs[0] { Def::Fn(fd) => { assert_eq!(fd.suppress.len(), 1); assert_eq!(fd.suppress[0].because, ""); } _ => panic!("expected fn"), } } /// Iter 19b: an unknown sub-attribute inside `(suppress ...)` /// produces a `ParseError::Production` naming the bad keyword and /// listing the legal ones (`code` / `because`). #[test] fn rejects_suppress_with_unknown_subattribute() { let err = crate::parse::parse( r#" (module t (fn f (suppress (code "over-strict-mode") (because "ok") (oops "x")) (type (fn-type (params) (ret (con Int)))) (params) (body 0))) "#, ) .unwrap_err(); let msg = format!("{err}"); assert!( msg.contains("oops") && msg.contains("code") && msg.contains("because"), "error should name the bad keyword and the legal ones; got: {msg}" ); } #[test] fn parses_minimal_class_def() { let src = r#"(module M (class Foo (param a) (method m (type (fn-type (params (con a)) (ret (con Int)))))))"#; let m = parse(src).expect("parse ok"); assert_eq!(m.defs.len(), 1, "one def"); match &m.defs[0] { ailang_core::ast::Def::Class(c) => { assert_eq!(c.name, "Foo"); assert_eq!(c.param, "a"); assert!(c.superclass.is_none(), "no superclass"); assert!(c.doc.is_none(), "no doc"); assert_eq!(c.methods.len(), 1, "one method"); assert_eq!(c.methods[0].name, "m"); assert!(c.methods[0].default.is_none(), "no default"); } other => panic!("expected Def::Class, got {other:?}"), } } #[test] fn parses_class_def_with_superclass_and_default() { let src = r#"(module M (class Bar (param a) (superclass (class Foo) (type a)) (doc "bar extends foo") (method m (type (fn-type (params (con a)) (ret (con Int)))) (default 0))))"#; let m = parse(src).expect("parse ok"); let c = match &m.defs[0] { ailang_core::ast::Def::Class(c) => c, _ => panic!("expected Class"), }; let sc = c.superclass.as_ref().expect("has superclass"); assert_eq!(sc.class, "Foo"); assert_eq!(sc.type_, "a"); assert_eq!(c.doc.as_deref(), Some("bar extends foo")); assert!(c.methods[0].default.is_some(), "method default present"); } #[test] fn parses_minimal_instance_def() { let src = r#"(module M (instance (class Foo) (type (con Int)) (method m (body 5))))"#; let m = parse(src).expect("parse ok"); match &m.defs[0] { ailang_core::ast::Def::Instance(i) => { assert_eq!(i.class, "Foo"); match &i.type_ { ailang_core::ast::Type::Con { name, args } => { assert_eq!(name, "Int"); assert!(args.is_empty(), "Int has no args"); } other => panic!("expected Type::Con, got {other:?}"), } assert!(i.doc.is_none()); assert_eq!(i.methods.len(), 1); assert_eq!(i.methods[0].name, "m"); } other => panic!("expected Def::Instance, got {other:?}"), } } #[test] fn parses_instance_def_with_doc() { let src = r#"(module M (instance (class Foo) (type (con Int)) (doc "instance for ints") (method m (body 7))))"#; let m = parse(src).expect("parse ok"); let i = match &m.defs[0] { ailang_core::ast::Def::Instance(i) => i, _ => panic!("expected Instance"), }; assert_eq!(i.doc.as_deref(), Some("instance for ints")); } #[test] fn parse_fn_rejects_duplicate_doc_clause() { let err = parse( r#" (module m (fn f (doc "first") (doc "second") (type (fn-type (params) (ret (con Unit)))) (params) (body (do io/print_str "x")))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("fn `f` has duplicate `(doc ...)` clause"), "expected duplicate-doc diagnostic, got: {msg}" ); } #[test] fn parse_fn_rejects_duplicate_type_clause() { let err = parse( r#" (module m (fn f (type (fn-type (params) (ret (con Unit)))) (type (fn-type (params) (ret (con Unit)))) (params) (body (do io/print_str "x")))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("fn `f` has duplicate `(type ...)` clause"), "expected duplicate-type diagnostic, got: {msg}" ); } #[test] fn parse_fn_rejects_duplicate_params_clause() { let err = parse( r#" (module m (fn f (type (fn-type (params) (ret (con Unit)))) (params) (params) (body (do io/print_str "x")))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("fn `f` has duplicate `(params ...)` clause"), "expected duplicate-params diagnostic, got: {msg}" ); } #[test] fn parse_fn_rejects_duplicate_body_clause() { let err = parse( r#" (module m (fn f (type (fn-type (params) (ret (con Unit)))) (params) (body (do io/print_str "x")) (body (do io/print_str "y")))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("fn `f` has duplicate `(body ...)` clause"), "expected duplicate-body diagnostic, got: {msg}" ); } #[test] fn parse_const_rejects_duplicate_doc_clause() { let err = parse( r#" (module m (const c (doc "first") (doc "second") (type (con Int)) (body 42))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("const `c` has duplicate `(doc ...)` clause"), "expected duplicate-doc diagnostic, got: {msg}" ); } #[test] fn parse_const_rejects_duplicate_type_clause() { let err = parse( r#" (module m (const c (type (con Int)) (type (con Int)) (body 42))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("const `c` has duplicate `(type ...)` clause"), "expected duplicate-type diagnostic, got: {msg}" ); } #[test] fn parse_const_rejects_duplicate_body_clause() { let err = parse( r#" (module m (const c (type (con Int)) (body 1) (body 2))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("const `c` has duplicate `(body ...)` clause"), "expected duplicate-body diagnostic, got: {msg}" ); } #[test] fn parse_data_rejects_duplicate_doc_clause() { let err = parse( r#" (module m (data D (doc "first") (doc "second") (ctor C))) "#, ) .unwrap_err(); let msg = format!("{err:?}"); assert!( msg.contains("data `D` has duplicate `(doc ...)` clause"), "expected duplicate-doc diagnostic, got: {msg}" ); } /// Floats milestone iter 2.2 RED: `1.5` parses as /// `Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000 } }`. /// Property protected: a `Tok::Float` produced by the lexer flows through /// `parse_term` into a `Literal::Float` AST node carrying the same bit /// pattern, so the surface→AST translation is bit-exact. #[test] fn parses_float_atom_term() { let t = parse_term("1.5").expect("parse term"); match t { Term::Lit { lit: Literal::Float { bits } } => { assert_eq!(bits, 0x3ff8_0000_0000_0000u64); } other => panic!("expected Term::Lit::Float, got {other:?}"), } } }