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> {
|
/// Parse a `(<kw> "<string>")` attribute clause into its string
|
||||||
self.expect_lparen("doc-attr")?;
|
/// payload. `prod` is the production tag used for the paren-balance
|
||||||
self.expect_keyword("doc")?;
|
/// diagnostics; `body_ctx` parameterises the string-literal error
|
||||||
let s = match self.peek().cloned() {
|
/// (e.g. `"doc body"`, `"export C symbol"`).
|
||||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
fn parse_string_attr(
|
||||||
self.cur += 1;
|
&mut self,
|
||||||
s
|
prod: &'static str,
|
||||||
}
|
kw: &'static str,
|
||||||
Some(t) => {
|
body_ctx: &'static str,
|
||||||
return Err(ParseError::Unexpected {
|
) -> Result<String, ParseError> {
|
||||||
expected: "string literal (doc body)".into(),
|
self.expect_lparen(prod)?;
|
||||||
got: tok_label(&t.tok),
|
self.expect_keyword(kw)?;
|
||||||
pos: t.span.start,
|
let s = self.expect_string(body_ctx)?;
|
||||||
});
|
self.expect_rparen(prod)?;
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(ParseError::UnexpectedEof {
|
|
||||||
expected: "string literal (doc body)".into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.expect_rparen("doc-attr")?;
|
|
||||||
Ok(s)
|
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> {
|
fn parse_export(&mut self) -> Result<String, ParseError> {
|
||||||
self.expect_lparen("export-attr")?;
|
self.parse_string_attr("export-attr", "export", "export C symbol")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_ctor(&mut self) -> Result<Ctor, ParseError> {
|
fn parse_ctor(&mut self) -> Result<Ctor, ParseError> {
|
||||||
@@ -713,8 +688,9 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(Suppress { code, because })
|
Ok(Suppress { code, because })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper — consume one string-literal token. Used by
|
/// helper — consume one string-literal token. Shared by
|
||||||
/// [`Self::parse_suppress_attr`].
|
/// [`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> {
|
fn expect_string(&mut self, ctx: &'static str) -> Result<String, ParseError> {
|
||||||
match self.peek().cloned() {
|
match self.peek().cloned() {
|
||||||
Some(Token { tok: Tok::Str(s), .. }) => {
|
Some(Token { tok: Tok::Str(s), .. }) => {
|
||||||
@@ -1217,27 +1193,46 @@ impl<'a> Parser<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_effects_clause(&mut self) -> Result<Vec<String>, ParseError> {
|
/// Parse a `(<kw> item+)` clause requiring at least one item. The
|
||||||
self.expect_lparen("effects-clause")?;
|
/// `item` parser is applied repeatedly until the closing paren; an
|
||||||
self.expect_keyword("effects")?;
|
/// empty body fails with `empty_msg` (a per-clause string so the
|
||||||
// 1+ idents per grammar; round-trip allows empty too (to support
|
/// diagnostic stays specific). `prod` tags the paren-balance errors.
|
||||||
// any future use), but the printer never emits an empty clause.
|
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();
|
let mut out = Vec::new();
|
||||||
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
|
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
|
||||||
out.push(self.expect_ident("effect name")?);
|
out.push(item(self)?);
|
||||||
}
|
}
|
||||||
if out.is_empty() {
|
if out.is_empty() {
|
||||||
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
|
let pos = self.peek().map(|t| t.span.start).unwrap_or(0);
|
||||||
return Err(ParseError::Production {
|
return Err(ParseError::Production {
|
||||||
production: "effects-clause",
|
production: prod,
|
||||||
message: "expected at least one effect name".into(),
|
message: empty_msg.into(),
|
||||||
pos,
|
pos,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.expect_rparen("effects-clause")?;
|
self.expect_rparen(prod)?;
|
||||||
Ok(out)
|
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> {
|
fn parse_forall_type(&mut self) -> Result<Type, ParseError> {
|
||||||
self.expect_lparen("forall-type")?;
|
self.expect_lparen("forall-type")?;
|
||||||
self.expect_keyword("forall")?;
|
self.expect_keyword("forall")?;
|
||||||
@@ -1267,22 +1262,12 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_forall_constraints_clause(&mut self) -> Result<Vec<Constraint>, ParseError> {
|
fn parse_forall_constraints_clause(&mut self) -> Result<Vec<Constraint>, ParseError> {
|
||||||
self.expect_lparen("forall.constraints")?;
|
self.parse_repeating_clause(
|
||||||
self.expect_keyword("constraints")?;
|
"forall.constraints",
|
||||||
let mut out = Vec::new();
|
"constraints",
|
||||||
while !matches!(self.peek(), Some(Token { tok: Tok::RParen, .. })) {
|
"(constraints …) clause must contain at least one (constraint …)",
|
||||||
out.push(self.parse_constraint()?);
|
|p| p.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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_constraint(&mut self) -> Result<Constraint, ParseError> {
|
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 let Some(lit) = self.try_parse_literal_token() {
|
||||||
if s == "true" {
|
return Ok(Term::Lit { lit });
|
||||||
Ok(Term::Lit { lit: Literal::Bool { value: true } })
|
}
|
||||||
} else if s == "false" {
|
match self.peek().cloned() {
|
||||||
Ok(Term::Lit { lit: Literal::Bool { value: false } })
|
// A non-boolean ident is a variable reference, not a
|
||||||
} else {
|
// literal — `try_parse_literal_token` left it for us.
|
||||||
Ok(Term::Var { name: s })
|
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 })
|
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> {
|
fn parse_pat_lit(&mut self) -> Result<Pattern, ParseError> {
|
||||||
self.expect_lparen("pat-lit")?;
|
self.expect_lparen("pat-lit")?;
|
||||||
self.expect_keyword("pat-lit")?;
|
self.expect_keyword("pat-lit")?;
|
||||||
let lit = match self.peek().cloned() {
|
let lit = match self.try_parse_literal_token() {
|
||||||
Some(Token { tok: Tok::Int(v), .. }) => {
|
Some(lit) => lit,
|
||||||
self.cur += 1;
|
None => match self.peek() {
|
||||||
Literal::Int { value: v }
|
Some(t) => {
|
||||||
}
|
|
||||||
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 {
|
|
||||||
return Err(ParseError::Unexpected {
|
return Err(ParseError::Unexpected {
|
||||||
expected: "literal form (integer, float, string, `true`, or `false`)".into(),
|
expected: "literal form (integer, float, string, `true`, or `false`)".into(),
|
||||||
got: tok_label(&Tok::Ident(s)),
|
got: tok_label(&t.tok),
|
||||||
pos: span.start,
|
pos: t.span.start,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
None => {
|
||||||
Some(t) => {
|
return Err(ParseError::UnexpectedEof {
|
||||||
return Err(ParseError::Unexpected {
|
expected: "literal form".into(),
|
||||||
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(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
self.expect_rparen("pat-lit")?;
|
self.expect_rparen("pat-lit")?;
|
||||||
Ok(Pattern::Lit { lit })
|
Ok(Pattern::Lit { lit })
|
||||||
|
|||||||
Reference in New Issue
Block a user