refactor(surface): share the copy-pasted parser sub-productions
Round-trip-preserving — the byte-isomorphic Form-A ↔ JSON-AST tests, the hash pins, and the full fixture-corpus parse are all green; error strings passed through verbatim, not homogenised. - The Int/Float/Str/true/false literal-token match was inlined in three parsers; extracted try_parse_literal_token and routed parse_pat_lit and the parse_term fallthrough through it. - parse_doc and parse_export were identical but for the keyword; both now go through one parse_string_attr, and the previously doc/export-bypassed expect_string helper is back on the shared path. - parse_effects_clause and parse_forall_constraints_clause shared the expect-keyword / collect / reject-empty / expect-rparen shape; unified under parse_repeating_clause, with each clause's empty-error message passed in. parse_intrinsic_attr was left inline-able-but-not-inlined: it has two call sites, so inlining would re-duplicate a documented helper.
This commit is contained in:
+106
-135
@@ -505,54 +505,29 @@ impl<'a> Parser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_doc(&mut self) -> Result<String, ParseError> {
|
||||
self.expect_lparen("doc-attr")?;
|
||||
self.expect_keyword("doc")?;
|
||||
let s = match self.peek().cloned() {
|
||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||
self.cur += 1;
|
||||
s
|
||||
}
|
||||
Some(t) => {
|
||||
return Err(ParseError::Unexpected {
|
||||
expected: "string literal (doc body)".into(),
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(ParseError::UnexpectedEof {
|
||||
expected: "string literal (doc body)".into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
self.expect_rparen("doc-attr")?;
|
||||
/// Parse a `(<kw> "<string>")` attribute clause into its string
|
||||
/// payload. `prod` is the production tag used for the paren-balance
|
||||
/// diagnostics; `body_ctx` parameterises the string-literal error
|
||||
/// (e.g. `"doc body"`, `"export C symbol"`).
|
||||
fn parse_string_attr(
|
||||
&mut self,
|
||||
prod: &'static str,
|
||||
kw: &'static str,
|
||||
body_ctx: &'static str,
|
||||
) -> Result<String, ParseError> {
|
||||
self.expect_lparen(prod)?;
|
||||
self.expect_keyword(kw)?;
|
||||
let s = self.expect_string(body_ctx)?;
|
||||
self.expect_rparen(prod)?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn parse_doc(&mut self) -> Result<String, ParseError> {
|
||||
self.parse_string_attr("doc-attr", "doc", "doc body")
|
||||
}
|
||||
|
||||
fn parse_export(&mut self) -> Result<String, ParseError> {
|
||||
self.expect_lparen("export-attr")?;
|
||||
self.expect_keyword("export")?;
|
||||
let s = match self.peek().cloned() {
|
||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||
self.cur += 1;
|
||||
s
|
||||
}
|
||||
Some(t) => {
|
||||
return Err(ParseError::Unexpected {
|
||||
expected: "string literal (export C symbol)".into(),
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(ParseError::UnexpectedEof {
|
||||
expected: "string literal (export C symbol)".into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
self.expect_rparen("export-attr")?;
|
||||
Ok(s)
|
||||
self.parse_string_attr("export-attr", "export", "export C symbol")
|
||||
}
|
||||
|
||||
fn parse_ctor(&mut self) -> Result<Ctor, ParseError> {
|
||||
@@ -713,8 +688,9 @@ impl<'a> Parser<'a> {
|
||||
Ok(Suppress { code, because })
|
||||
}
|
||||
|
||||
/// helper — consume one string-literal token. Used by
|
||||
/// [`Self::parse_suppress_attr`].
|
||||
/// helper — consume one string-literal token. Shared by
|
||||
/// [`Self::parse_string_attr`] (doc / export bodies) and
|
||||
/// [`Self::parse_suppress_attr`] (code / because bodies).
|
||||
fn expect_string(&mut self, ctx: &'static str) -> Result<String, ParseError> {
|
||||
match self.peek().cloned() {
|
||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||
@@ -1217,27 +1193,46 @@ impl<'a> Parser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_effects_clause(&mut self) -> Result<Vec<String>, ParseError> {
|
||||
self.expect_lparen("effects-clause")?;
|
||||
self.expect_keyword("effects")?;
|
||||
// 1+ idents per grammar; round-trip allows empty too (to support
|
||||
// any future use), but the printer never emits an empty clause.
|
||||
/// Parse a `(<kw> item+)` clause requiring at least one item. The
|
||||
/// `item` parser is applied repeatedly until the closing paren; an
|
||||
/// empty body fails with `empty_msg` (a per-clause string so the
|
||||
/// diagnostic stays specific). `prod` tags the paren-balance errors.
|
||||
fn parse_repeating_clause<T>(
|
||||
&mut self,
|
||||
prod: &'static str,
|
||||
kw: &'static str,
|
||||
empty_msg: &'static str,
|
||||
item: fn(&mut Self) -> Result<T, ParseError>,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
self.expect_lparen(prod)?;
|
||||
self.expect_keyword(kw)?;
|
||||
let mut out = Vec::new();
|
||||
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
|
||||
out.push(self.expect_ident("effect name")?);
|
||||
out.push(item(self)?);
|
||||
}
|
||||
if out.is_empty() {
|
||||
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
|
||||
return Err(ParseError::Production {
|
||||
production: "effects-clause",
|
||||
message: "expected at least one effect name".into(),
|
||||
production: prod,
|
||||
message: empty_msg.into(),
|
||||
pos,
|
||||
});
|
||||
}
|
||||
self.expect_rparen("effects-clause")?;
|
||||
self.expect_rparen(prod)?;
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn parse_effects_clause(&mut self) -> Result<Vec<String>, ParseError> {
|
||||
// 1+ idents per grammar; round-trip allows empty too (to support
|
||||
// any future use), but the printer never emits an empty clause.
|
||||
self.parse_repeating_clause(
|
||||
"effects-clause",
|
||||
"effects",
|
||||
"expected at least one effect name",
|
||||
|p| p.expect_ident("effect name"),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_forall_type(&mut self) -> Result<Type, ParseError> {
|
||||
self.expect_lparen("forall-type")?;
|
||||
self.expect_keyword("forall")?;
|
||||
@@ -1267,22 +1262,12 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
fn parse_forall_constraints_clause(&mut self) -> Result<Vec<Constraint>, ParseError> {
|
||||
self.expect_lparen("forall.constraints")?;
|
||||
self.expect_keyword("constraints")?;
|
||||
let mut out = Vec::new();
|
||||
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
|
||||
out.push(self.parse_constraint()?);
|
||||
}
|
||||
if out.is_empty() {
|
||||
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
|
||||
return Err(ParseError::Production {
|
||||
production: "forall.constraints",
|
||||
message: "(constraints …) clause must contain at least one (constraint …)".into(),
|
||||
pos,
|
||||
});
|
||||
}
|
||||
self.expect_rparen("forall.constraints")?;
|
||||
Ok(out)
|
||||
self.parse_repeating_clause(
|
||||
"forall.constraints",
|
||||
"constraints",
|
||||
"(constraints …) clause must contain at least one (constraint …)",
|
||||
|p| p.parse_constraint(),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_constraint(&mut self) -> Result<Constraint, ParseError> {
|
||||
@@ -1340,36 +1325,27 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Token { tok: Tok::Ident(s), .. }) => {
|
||||
self.cur += 1;
|
||||
if s == "true" {
|
||||
Ok(Term::Lit { lit: Literal::Bool { value: true } })
|
||||
} else if s == "false" {
|
||||
Ok(Term::Lit { lit: Literal::Bool { value: false } })
|
||||
} else {
|
||||
Ok(Term::Var { name: s })
|
||||
_ => {
|
||||
if let Some(lit) = self.try_parse_literal_token() {
|
||||
return Ok(Term::Lit { lit });
|
||||
}
|
||||
match self.peek().cloned() {
|
||||
// A non-boolean ident is a variable reference, not a
|
||||
// literal — `try_parse_literal_token` left it for us.
|
||||
Some(Token { tok: Tok::Ident(s), .. }) => {
|
||||
self.cur += 1;
|
||||
Ok(Term::Var { name: s })
|
||||
}
|
||||
Some(t) => Err(ParseError::Unexpected {
|
||||
expected: "term".into(),
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
}),
|
||||
None => Err(ParseError::UnexpectedEof {
|
||||
expected: "term".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
Some(Token { tok: Tok::Int(v), .. }) => {
|
||||
self.cur += 1;
|
||||
Ok(Term::Lit { lit: Literal::Int { value: v } })
|
||||
}
|
||||
Some(Token { tok: Tok::Float(bits), .. }) => {
|
||||
self.cur += 1;
|
||||
Ok(Term::Lit { lit: Literal::Float { bits } })
|
||||
}
|
||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||
self.cur += 1;
|
||||
Ok(Term::Lit { lit: Literal::Str { value: s } })
|
||||
}
|
||||
Some(t) => Err(ParseError::Unexpected {
|
||||
expected: "term".into(),
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
}),
|
||||
None => Err(ParseError::UnexpectedEof {
|
||||
expected: "term".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1879,49 +1855,44 @@ impl<'a> Parser<'a> {
|
||||
Ok(Pattern::Ctor { ctor, fields })
|
||||
}
|
||||
|
||||
/// Consume one literal-form token — an `Int`, `Float`, `Str`, or the
|
||||
/// idents `true` / `false` — and return its [`Literal`]. Any other
|
||||
/// token (including a non-boolean ident) leaves the cursor untouched
|
||||
/// and returns `None`, so each caller decides how to handle the
|
||||
/// non-literal fallthrough (a bare ident is `Term::Var` in a term but
|
||||
/// an error in a `pat-lit`).
|
||||
fn try_parse_literal_token(&mut self) -> Option<Literal> {
|
||||
let lit = match &self.peek()?.tok {
|
||||
Tok::Int(v) => Literal::Int { value: *v },
|
||||
Tok::Float(bits) => Literal::Float { bits: *bits },
|
||||
Tok::Str(s) => Literal::Str { value: s.clone() },
|
||||
Tok::Ident(s) if s == "true" => Literal::Bool { value: true },
|
||||
Tok::Ident(s) if s == "false" => Literal::Bool { value: false },
|
||||
_ => return None,
|
||||
};
|
||||
self.cur += 1;
|
||||
Some(lit)
|
||||
}
|
||||
|
||||
fn parse_pat_lit(&mut self) -> Result<Pattern, ParseError> {
|
||||
self.expect_lparen("pat-lit")?;
|
||||
self.expect_keyword("pat-lit")?;
|
||||
let lit = match self.peek().cloned() {
|
||||
Some(Token { tok: Tok::Int(v), .. }) => {
|
||||
self.cur += 1;
|
||||
Literal::Int { value: v }
|
||||
}
|
||||
Some(Token { tok: Tok::Float(bits), .. }) => {
|
||||
self.cur += 1;
|
||||
Literal::Float { bits }
|
||||
}
|
||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||
self.cur += 1;
|
||||
Literal::Str { value: s }
|
||||
}
|
||||
Some(Token { tok: Tok::Ident(s), span }) => {
|
||||
if s == "true" {
|
||||
self.cur += 1;
|
||||
Literal::Bool { value: true }
|
||||
} else if s == "false" {
|
||||
self.cur += 1;
|
||||
Literal::Bool { value: false }
|
||||
} else {
|
||||
let lit = match self.try_parse_literal_token() {
|
||||
Some(lit) => lit,
|
||||
None => match self.peek() {
|
||||
Some(t) => {
|
||||
return Err(ParseError::Unexpected {
|
||||
expected: "literal form (integer, float, string, `true`, or `false`)".into(),
|
||||
got: tok_label(&Tok::Ident(s)),
|
||||
pos: span.start,
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(t) => {
|
||||
return Err(ParseError::Unexpected {
|
||||
expected: "literal form (integer, float, string, `true`, or `false`)".into(),
|
||||
got: tok_label(&t.tok),
|
||||
pos: t.span.start,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(ParseError::UnexpectedEof {
|
||||
expected: "literal form".into(),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(ParseError::UnexpectedEof {
|
||||
expected: "literal form".into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
self.expect_rparen("pat-lit")?;
|
||||
Ok(Pattern::Lit { lit })
|
||||
|
||||
Reference in New Issue
Block a user