iter 22b.4a.1: form-a parser arm for ClassDef

This commit is contained in:
2026-05-09 21:59:29 +02:00
parent fdf0de5de7
commit fefaa03c76
+224 -2
View File
@@ -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<ClassDef, ParseError> {
self.expect_lparen("class-def")?;
self.expect_keyword("class")?;
let name = self.expect_ident("class name")?;
let mut param: Option<String> = None;
let mut superclass: Option<SuperclassRef> = None;
let mut doc: Option<String> = None;
let mut methods: Vec<ClassMethod> = 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<SuperclassRef, ParseError> {
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<ClassMethod, ParseError> {
self.expect_lparen("class.method")?;
self.expect_keyword("method")?;
let name = self.expect_ident("class method name")?;
let mut ty: Option<Type> = None;
let mut default: Option<Term> = 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<Term, ParseError> {
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<Type, ParseError> {
@@ -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");
}
}