iter 22b.4a.2: form-a parser arm for InstanceDef
This commit is contained in:
@@ -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<InstanceDef, ParseError> {
|
||||
self.expect_lparen("instance-def")?;
|
||||
self.expect_keyword("instance")?;
|
||||
let mut class: Option<String> = None;
|
||||
let mut type_: Option<Type> = None;
|
||||
let mut doc: Option<String> = None;
|
||||
let mut methods: Vec<InstanceMethod> = 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<InstanceMethod, ParseError> {
|
||||
self.expect_lparen("instance.method")?;
|
||||
self.expect_keyword("method")?;
|
||||
let name = self.expect_ident("instance method name")?;
|
||||
let mut body: Option<Term> = 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<Type, ParseError> {
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user