From da1746229df97fe73b9d4946e9bcdc5ae208a048 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sat, 9 May 2026 22:05:49 +0200 Subject: [PATCH] iter 22b.4a.2: form-a parser arm for InstanceDef --- crates/ailang-surface/src/parse.rs | 176 ++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index b9bf14e..70e75f6 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -85,8 +85,8 @@ //! [`ailang_core::ast::Import::alias`]. use ailang_core::ast::{ - Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, Literal, Module, - ParamMode, Pattern, SuperclassRef, Suppress, Term, + Arm, ClassDef, ClassMethod, ConstDef, Ctor, Def, FnDef, Import, InstanceDef, + InstanceMethod, Literal, Module, ParamMode, Pattern, SuperclassRef, Suppress, Term, Type, TypeDef, }; use ailang_core::SCHEMA; @@ -311,12 +311,13 @@ impl<'a> Parser<'a> { "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`, or `import`" + "unknown def head `{other}`; expected `data`, `fn`, `const`, `class`, `instance`, or `import`" ), pos, }); @@ -847,6 +848,132 @@ impl<'a> Parser<'a> { 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "instance-def", + message: "instance has duplicate `(class ...)` clause".into(), + pos, + }); + } + 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "instance-def", + message: "instance has duplicate `(type ...)` clause".into(), + pos, + }); + } + type_ = Some(self.parse_type_attr()?); + } + Some("doc") => { + if doc.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "instance-def", + message: "instance has duplicate `(doc ...)` clause".into(), + pos, + }); + } + 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() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "instance.method", + message: format!( + "instance method `{name}` has duplicate `(body ...)` clause" + ), + pos, + }); + } + 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 { @@ -2056,4 +2183,47 @@ mod tests { 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")); + } }