iter 22b.4a.1: form-a parser arm for ClassDef
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user