rust-lang/rust-analyzer · error · ungrammar::parser::Error
The first element in a sequence of productions or alternativ
Error message
The first element in a sequence of productions or alternatives must not have a leading pipe (`|`)
What it means
`rule` rejects a leading pipe: in ungrammar, alternatives are separators, so `A | B` is fine but the rule may not *begin* with `|`. The parser bails when the first token of a rule is `Pipe`, because `A = | B` has no valid first production.
Source
Thrown at lib/ungrammar/src/parser.rs:106
fn node(p: &mut Parser) -> Result<()> {
let token = p.bump()?;
let node = match token.kind {
TokenKind::Node(it) => p.intern_node(it),
_ => bail!(token.loc, "expected ident"),
};
p.expect(TokenKind::Eq, "=")?;
if !matches!(p.grammar[node].rule, DUMMY_RULE) {
bail!(token.loc, "duplicate rule: `{}`", p.grammar[node].name)
}
let rule = rule(p)?;
p.grammar.nodes[node.0].rule = rule;
Ok(())
}
fn rule(p: &mut Parser) -> Result<Rule> {
if let Some(lexer::Token { kind: TokenKind::Pipe, loc }) = p.peek() {
bail!(
*loc,
"The first element in a sequence of productions or alternatives \
must not have a leading pipe (`|`)"
);
}
let lhs = seq_rule(p)?;
let mut alt = vec![lhs];
while let Some(token) = p.peek() {
if token.kind != TokenKind::Pipe {
break;
}
p.bump()?;
let rule = seq_rule(p)?;
alt.push(rule)
}
let res = if alt.len() == 1 { alt.pop().unwrap() } else { Rule::Alt(alt) };
Ok(res)View on GitHub (pinned to e8f7e90aa3)
Solutions
- Delete the leading `|` at the start of the rule or alternative group.
- Ensure the first production precedes the first pipe: `Expr = A | B` not `Expr = | A | B`.
- If an alternative was accidentally removed, restore it instead of leaving the orphan pipe.
Example fix
// before (.ungram) Expr = | PathExpr | Literal // after (.ungram) Expr = PathExpr | Literal
Defensive patterns
Strategy: validation
Validate before calling
// reject leading pipes in any rule body before parsing
fn has_leading_pipe(src: &str) -> bool {
src.lines().any(|l| l.trim_start().starts_with('|'))
} Try / catch
match ungrammar::parse(src) {
Ok(g) => g,
Err(e) if e.to_string().contains("leading pipe") => {
let fixed = src.lines().map(|l| l.trim_start().strip_prefix("| ").map(|r| r.to_string()).unwrap_or_else(|| l.to_string())).collect::<Vec<_>>().join("\n");
ungrammar::parse(&fixed)
}
Err(e) => return Err(e.into()),
} Prevention
- Remember ungrammar alternatives are infix separators, never a leading prefix.
- When deleting the first alternative of a rule, also delete its trailing pipe from the next line.
- Lint .ungram files for lines starting with `|`.
When it happens
Trigger: Parsing a grammar where a rule body or a nested alternative group starts with `|`, e.g. `Node = | A | B` or a parenthesized group `(| A | B)`. Raised from `rule`, called by `node` and `opt_atom_rule`.
Common situations: Authors familiar with regex alternation or EBNF syntaxes that allow leading `|` (like PEG or some DSLs) writing `| alt1 | alt2`; copy-paste edits that drop the first production but keep its leading separator.
Related errors
- unexpected token, expected `{}`
- unexpected token
- unclosed token literal
- unexpected character: `{}`
- Undefined node: {}
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/30280ac6c3f32ef0.
Report an issue: GitHub.