{"record":{"id":"30280ac6c3f32ef0","repo":"rust-lang/rust-analyzer","slug":"the-first-element-in-a-sequence-of-productions-or","errorCode":null,"errorMessage":"The first element in a sequence of productions or alternatives must not have a leading pipe (`|`)","messagePattern":"The first element in a sequence of productions or alternatives must not have a leading pipe \\(`\\|`\\)","errorType":"error_code","errorClass":"ungrammar::parser::Error","httpStatus":null,"severity":"error","filePath":"lib/ungrammar/src/parser.rs","lineNumber":106,"sourceCode":"fn node(p: &mut Parser) -> Result<()> {\n    let token = p.bump()?;\n    let node = match token.kind {\n        TokenKind::Node(it) => p.intern_node(it),\n        _ => bail!(token.loc, \"expected ident\"),\n    };\n    p.expect(TokenKind::Eq, \"=\")?;\n    if !matches!(p.grammar[node].rule, DUMMY_RULE) {\n        bail!(token.loc, \"duplicate rule: `{}`\", p.grammar[node].name)\n    }\n\n    let rule = rule(p)?;\n    p.grammar.nodes[node.0].rule = rule;\n    Ok(())\n}\n\nfn rule(p: &mut Parser) -> Result<Rule> {\n    if let Some(lexer::Token { kind: TokenKind::Pipe, loc }) = p.peek() {\n        bail!(\n            *loc,\n            \"The first element in a sequence of productions or alternatives \\\n            must not have a leading pipe (`|`)\"\n        );\n    }\n\n    let lhs = seq_rule(p)?;\n    let mut alt = vec![lhs];\n    while let Some(token) = p.peek() {\n        if token.kind != TokenKind::Pipe {\n            break;\n        }\n        p.bump()?;\n        let rule = seq_rule(p)?;\n        alt.push(rule)\n    }\n    let res = if alt.len() == 1 { alt.pop().unwrap() } else { Rule::Alt(alt) };\n    Ok(res)","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/lib/ungrammar/src/parser.rs#L88-L124","documentation":"`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.","triggerScenarios":"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`.","commonSituations":"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.","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."],"exampleFix":"// before (.ungram)\nExpr = | PathExpr | Literal\n\n// after (.ungram)\nExpr = PathExpr | Literal","handlingStrategy":"validation","validationCode":"// reject leading pipes in any rule body before parsing\nfn has_leading_pipe(src: &str) -> bool {\n    src.lines().any(|l| l.trim_start().starts_with('|'))\n}","typeGuard":null,"tryCatchPattern":"match ungrammar::parse(src) {\n    Ok(g) => g,\n    Err(e) if e.to_string().contains(\"leading pipe\") => {\n        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\");\n        ungrammar::parse(&fixed)\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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 `|`."],"tags":["parser","ungrammar","syntax"],"backgroundTag":"grammar-parse-error","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}