diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index 8486431..235379c 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -405,10 +405,8 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("doc") => { - if doc.is_some() { - return Err(self.duplicate_clause_err("data-def", &format!("data `{name}`"), "doc")); - } - doc = Some(self.parse_doc()?); + let subj = format!("data `{name}`"); + self.set_once(&mut doc, "data-def", &subj, "doc", |p| p.parse_doc())?; } Some("ctor") => { ctors.push(self.parse_ctor()?); @@ -559,41 +557,29 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("doc") => { - if doc.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "doc")); - } - doc = Some(self.parse_doc()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut doc, "fn-def", &subj, "doc", |p| p.parse_doc())?; } Some("export") => { - if export.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "export")); - } - export = Some(self.parse_export()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut export, "fn-def", &subj, "export", |p| p.parse_export())?; } Some("suppress") => suppress.push(self.parse_suppress_attr()?), Some("type") => { - if ty.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "type")); - } - ty = Some(self.parse_type_attr()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut ty, "fn-def", &subj, "type", |p| p.parse_type_attr())?; } Some("params") => { - if params.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "params")); - } - params = Some(self.parse_params_attr()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut params, "fn-def", &subj, "params", |p| p.parse_params_attr())?; } Some("body") => { - if body.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "body")); - } - body = Some(self.parse_body_attr()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut body, "fn-def", &subj, "body", |p| p.parse_body_attr())?; } Some("intrinsic") => { - if body.is_some() { - return Err(self.duplicate_clause_err("fn-def", &format!("fn `{name}`"), "body")); - } - body = Some(self.parse_intrinsic_attr()?); + let subj = format!("fn `{name}`"); + self.set_once(&mut body, "fn-def", &subj, "body", |p| p.parse_intrinsic_attr())?; } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); @@ -722,6 +708,29 @@ impl<'a> Parser<'a> { } } + /// Fill an optional-clause slot exactly once. If `slot` already holds + /// a value, the clause is a duplicate and this returns + /// [`Self::duplicate_clause_err`] (computed against the *current* + /// cursor position, i.e. before `parse` runs — so the diagnostic + /// points at the offending second clause). Otherwise `parse` is + /// invoked and its result stored. The parse runs only on the + /// non-duplicate path, matching the inline `is_some` / dup-err / + /// assign shape it replaces. + fn set_once( + &mut self, + slot: &mut Option, + production: &'static str, + subject: &str, + clause: &'static str, + parse: impl FnOnce(&mut Self) -> Result, + ) -> Result<(), ParseError> { + if slot.is_some() { + return Err(self.duplicate_clause_err(production, subject, clause)); + } + *slot = Some(parse(self)?); + Ok(()) + } + fn parse_type_attr(&mut self) -> Result { self.expect_lparen("type-attr")?; self.expect_keyword("type")?; @@ -772,22 +781,16 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("doc") => { - if doc.is_some() { - return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "doc")); - } - doc = Some(self.parse_doc()?); + let subj = format!("const `{name}`"); + self.set_once(&mut doc, "const-def", &subj, "doc", |p| p.parse_doc())?; } Some("type") => { - if ty.is_some() { - return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "type")); - } - ty = Some(self.parse_type_attr()?); + let subj = format!("const `{name}`"); + self.set_once(&mut ty, "const-def", &subj, "type", |p| p.parse_type_attr())?; } Some("body") => { - if value.is_some() { - return Err(self.duplicate_clause_err("const-def", &format!("const `{name}`"), "body")); - } - value = Some(self.parse_body_attr()?); + let subj = format!("const `{name}`"); + self.set_once(&mut value, "const-def", &subj, "body", |p| p.parse_body_attr())?; } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); @@ -834,25 +837,22 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("param") => { - if param.is_some() { - return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "param")); - } - self.expect_lparen("class.param")?; - self.expect_keyword("param")?; - param = Some(self.expect_ident("class param ident")?); - self.expect_rparen("class.param")?; + let subj = format!("class `{name}`"); + self.set_once(&mut param, "class-def", &subj, "param", |p| { + p.expect_lparen("class.param")?; + p.expect_keyword("param")?; + let v = p.expect_ident("class param ident")?; + p.expect_rparen("class.param")?; + Ok(v) + })?; } Some("superclass") => { - if superclass.is_some() { - return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "superclass")); - } - superclass = Some(self.parse_superclass()?); + let subj = format!("class `{name}`"); + self.set_once(&mut superclass, "class-def", &subj, "superclass", |p| p.parse_superclass())?; } Some("doc") => { - if doc.is_some() { - return Err(self.duplicate_clause_err("class-def", &format!("class `{name}`"), "doc")); - } - doc = Some(self.parse_doc()?); + let subj = format!("class `{name}`"); + self.set_once(&mut doc, "class-def", &subj, "doc", |p| p.parse_doc())?; } Some("method") => { methods.push(self.parse_class_method()?); @@ -911,16 +911,12 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("type") => { - if ty.is_some() { - return Err(self.duplicate_clause_err("class.method", &format!("method `{name}`"), "type")); - } - ty = Some(self.parse_type_attr()?); + let subj = format!("method `{name}`"); + self.set_once(&mut ty, "class.method", &subj, "type", |p| p.parse_type_attr())?; } Some("default") => { - if default.is_some() { - return Err(self.duplicate_clause_err("class.method", &format!("method `{name}`"), "default")); - } - default = Some(self.parse_default_attr()?); + let subj = format!("method `{name}`"); + self.set_once(&mut default, "class.method", &subj, "default", |p| p.parse_default_attr())?; } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); @@ -964,25 +960,19 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("class") => { - if class.is_some() { - return Err(self.duplicate_clause_err("instance-def", "instance", "class")); - } - self.expect_lparen("instance.class")?; - self.expect_keyword("class")?; - class = Some(self.expect_ident("instance class name")?); - self.expect_rparen("instance.class")?; + self.set_once(&mut class, "instance-def", "instance", "class", |p| { + p.expect_lparen("instance.class")?; + p.expect_keyword("class")?; + let v = p.expect_ident("instance class name")?; + p.expect_rparen("instance.class")?; + Ok(v) + })?; } Some("type") => { - if type_.is_some() { - return Err(self.duplicate_clause_err("instance-def", "instance", "type")); - } - type_ = Some(self.parse_type_attr()?); + self.set_once(&mut type_, "instance-def", "instance", "type", |p| p.parse_type_attr())?; } Some("doc") => { - if doc.is_some() { - return Err(self.duplicate_clause_err("instance-def", "instance", "doc")); - } - doc = Some(self.parse_doc()?); + self.set_once(&mut doc, "instance-def", "instance", "doc", |p| p.parse_doc())?; } Some("method") => { methods.push(self.parse_instance_method()?); @@ -1027,10 +1017,8 @@ impl<'a> Parser<'a> { loop { match self.peek_head_ident() { Some("body") => { - if body.is_some() { - return Err(self.duplicate_clause_err("instance.method", &format!("instance method `{name}`"), "body")); - } - body = Some(self.parse_body_attr()?); + let subj = format!("instance method `{name}`"); + self.set_once(&mut body, "instance.method", &subj, "body", |p| p.parse_body_attr())?; } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0);