From fefaa03c769829bbf4578742773cd8e7114838f9 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 21:59:29 +0200 Subject: [PATCH] iter 22b.4a.1: form-a parser arm for ClassDef --- crates/ailang-surface/src/parse.rs | 226 ++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 2 deletions(-) diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index 708cfbd..31e82b6 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -85,7 +85,8 @@ //! [`ailang_core::ast::Import::alias`]. use ailang_core::ast::{ - Arm, ConstDef, Ctor, Def, FnDef, Import, Literal, Module, ParamMode, Pattern, Suppress, Term, + Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, Literal, Module, + ParamMode, Pattern, SuperclassRef, Suppress, Term, Type, TypeDef, }; use ailang_core::SCHEMA; @@ -309,12 +310,13 @@ impl<'a> Parser<'a> { "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()?)), 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`" + "unknown def head `{other}`; expected `data`, `fn`, `const`, `class`, or `import`" ), pos, }); @@ -679,6 +681,172 @@ impl<'a> Parser<'a> { }) } + // ---- 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "class-def", + message: format!( + "class `{name}` has duplicate `(param ...)` clause" + ), + pos, + }); + } + 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "class-def", + message: format!( + "class `{name}` has duplicate `(superclass ...)` clause" + ), + pos, + }); + } + superclass = Some(self.parse_superclass()?); + } + Some("doc") => { + if doc.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "class-def", + message: format!( + "class `{name}` has duplicate `(doc ...)` clause" + ), + pos, + }); + } + 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "class.method", + message: format!( + "method `{name}` has duplicate `(type ...)` clause" + ), + pos, + }); + } + ty = Some(self.parse_type_attr()?); + } + Some("default") => { + if default.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "class.method", + message: format!( + "method `{name}` has duplicate `(default ...)` clause" + ), + pos, + }); + } + 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) + } + // ---- types ---------------------------------------------------------- fn parse_type(&mut self) -> Result { @@ -1841,4 +2009,58 @@ mod tests { "error should name the bad keyword and the legal ones; got: {msg}" ); } + + #[test] + fn parses_minimal_class_def() { + // NOTE: plan fixture used `(effects)` (empty) but the existing + // `parse_effects_clause` rejects empty effect lists. The clause + // is optional in fn-type, so we omit it here; behaviour under + // test (class shape + method count) is unaffected. + 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() { + // See note on `parses_minimal_class_def` re empty `(effects)`. + // Plan also wrote `(lit (int 0))` for the default body but the + // surface form for int literals is the bare token; using `0`. + 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"); + } }