XParsec is a parser combinator library for F#, with several important differences from FParsec:
- Generalization over collection and token types
With XParsec all common contiguous collections string 'T array ResizeArray<'T> ImmutableArray<'T> and ReadOnlyMemory<'T> can be parsed with essentially the same code.
- Pure F# implementation
F# is a great .NET language, and with the Fable compiler, a powerful JavaScript language too. XParsec's pure F# implementation provides a robust, easy to use parsing library for Fable target languages.
- More Performant
XParsec uses newer F# & .NET features like [<InlineIfLambda>], Span<'T>, and struct unions to compete with imperative parsing libraries while remaining terse and easy to reason about. Parsing a single large JSON file takes roughly half the time of FParsec with ~1/6 the allocations.
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---|---|---|---|---|---|---|---|
| XParsecJson | 30.61 ms | 0.196 ms | 0.164 ms | 718.7500 | 625.0000 | - | 35.61 MB |
| FParsecJson | 61.09 ms | 0.267 ms | 0.208 ms | 4555.5556 | 777.7778 | 222.2222 | 208.37 MB |
- Simplified operator precedence parsing
- No line number tracking by default. A separate line ending parser is available for generating detailed error messages.
<|>,choice, andchoiceLalways backtrack. There is noattemptcombinator because every alternative already behaves like one. See Migrating from FParsec below.
The quick brown fox jumps over the lazy dog.
^ At index 4 (Ln 1, Col 5)
All choices failed.
├───Expected 'a'
└───All choices failed.
├───Unexpected 'q'
└───Expected 'c'
XParsec is capable of parsing extremely complex grammars, including F# itself. I'm documenting building a complete F# language parser with XParsec in an ongoing blog series, starting with the prologue.
XParsec is API-shaped to be familiar to FParsec users, but a few semantics are deliberately different. Two are worth calling out:
<|>,choice, andchoiceLalways backtrack. FParsec's alternative combinators only continue to the next branch if the previous branch failed without consuming input; you wrap branches inattemptto force backtracking after partial consumption. XParsec's alternatives save the reader position before running each branch and restore it on any failure. There is noattemptcombinator — dropattemptcalls when migrating; every alternative already behaves like one. The save is a struct copy, so the always-backtrack default is cheap.pzeroproduces a structuralEmptyerror. Aggregating combinators (<|>,choice,manyTill, …) filterEmptysiblings before constructing nested errors, sopzero <|> ppropagates onlyp's error rather than wrapping a blank stub. The default formatter renders nothing forEmpty.
If you need "fail in place when input was consumed" behaviour for a specific alternative, use notFollowedBy/<?> to gate or relabel rather than reaching for attempt.
dotnet testnpm run test