Pidgin builds C# parsers out of combinators instead of grammar files
A lightweight and fast parsing library for C#.
At a glance
- What is it?
- Pidgin is a parser combinator library for C# where a parser is a function from a token stream to a value or a parse error, and grammars are written as ordinary C# code. The catch is that the combinators do not backtrack by default, which surprises people the first time an alternative fails.
- Who is it for?
- Adopt Pidgin when you need a hand written parser in C# for a language small enough to describe in a few hundred lines of code, and you want error messages and control flow in ordinary C# rather than in a generated artifact. Do not adopt it expecting regular expression semantics: it parses a larger class of languages, but it will not backtrack unless you ask it to, and the README is explicit about that trap.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 116 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A parser is a function, not a grammar object
The core type is `Parser<TToken, T>`, and the README gives its shape as a delegate over an enumerator of tokens that returns either a result or nothing:
delegate T? Parser<TToken, T>(IEnumerator<TToken> input);That single signature explains the whole design. A parser consumes from an input stream, may fail with a parsing error, and otherwise produces a `T`. Because parsers are values, you compose them with ordinary method calls and LINQ, and because they are functions, there is no separate grammar file, no code generation step, and no external tool to run. The README positions the library between regular expressions and parser generators such as ANTLR: more capable than a regex, since a regex cannot match nested structures, but without the tooling overhead of a generator. The token type is a parameter, so the same combinators parse characters in a string or bytes from a binary file.
Two static classes carry all the factory methods
Starting a parser means importing two classes, `Parser` and `Parser<TToken>`, the second one for token specific factories. The tutorial's own import block makes the intent clear:
using Pidgin;
using static Pidgin.Parser;
using static Pidgin.Parser<char>; // we'll be parsing strings - sequences of characters. For other applications (eg parsing binary file formats) TToken may be some other type (eg byte).The `static` import is what lets the rest of the examples read as `Any`, `Digit` and `String` without a prefix. That is a deliberate readability choice, and it also means two names can collide if you import both classes in the same file, so a larger project tends to qualify calls instead. The primitive parsers cover the obvious ground: `Any` consumes a single character and returns it, `Char` is an alias for `Token` and matches one particular character, `Digit` matches a digit, and `String` matches a fixed string.
ParseOrThrow turns a parser into a one liner
Every example in the tutorial drives the parser the same way, with `ParseOrThrow`, and asserts on the result or on the exception. The pattern is worth recognising because it is the whole calling convention:
Parser<char, char> parser = Char('a');
Assert.AreEqual('a', parser.ParseOrThrow("a"));
Assert.Throws<ParseException>(() => parser.ParseOrThrow("b"));A failed parse raises `ParseException` rather than returning null, and the primitive parsers are usable directly as values, so `Digit.ParseOrThrow("3")` works without constructing anything. `Return` never consumes input and produces a fixed value, with `FromResult` as a synonym, and `Fail` always fails without consuming input. `Return` is the piece people reach for when a parser needs to succeed and yield a constant, for example a literal keyword. The exception-based surface is convenient in tests, and in production code you may want to catch `ParseException` and render the message, since the library gives you a structured error rather than a bool.
Then, Before and Map decide which results survive
Sequencing is where composition earns its keep, and the three combinators differ in a way that matters more than it first appears. `Then` runs two parsers in order and discards the first result, `Before` discards the second instead, and `Map` keeps both and applies a function to them:
Parser<char, string> parser1 = String("foo");
Parser<char, string> parser2 = String("bar");
Parser<char, string> sequencedParser = Map((foo, bar) => bar + foo, parser1, parser2);
Assert.AreEqual("barfoo", sequencedParser.ParseOrThrow("foobar"));
Assert.Throws<ParseException>(() => sequencedParser.ParseOrThrow("food"));`Map` has overloads taking between one and eight parsers, and the single parser version also has the postfix synonym `Select`. That is what makes the LINQ form work:
Parser<char, char> parser =
from c in Any
from c2 in Char(c)
select c2;The README describes this form as reading like an imperative script, and it is the closest thing in the library to a declarative grammar. `Bind` is the one that unlocks context-sensitive parsing, since it uses the first result to choose the second parser, which is how the example that parses any character repeated twice is written.
Or does not backtrack, and that is the trap
Alternatives are where a reader is most likely to lose an afternoon. `Or` runs the left parser first and falls back to the right one if the left fails, and `OneOf` is the variadic equivalent. The catch is stated plainly in the README: if a component parser fails after consuming input, the whole parser fails, because Pidgin performs no lookahead or backtracking by default. The example makes the failure concrete:
Parser<char, string> parser = String("food").Or(String("foul"));
Assert.Throws<ParseException>(() => parser.ParseOrThrow("foul")); // why didn't it choose the second option?The first alternative consumes `f`, `o` and `o` and then fails, so the second is never tried. The fix is to wrap the first branch in `Try`, which restores the ability to return to the beginning of the input. This is a design choice rather than an oversight: avoiding backtracking keeps parsers linear in the input, but it means every alternative that shares a prefix has to be written with that in mind, or the grammar has to be restructured with `Bind` and lookahead of your own.
What the repository layout shows about the project
The tree gives a sense of the scope. The library itself is in `Pidgin/`, tests in `Pidgin.Tests/`, and there are directories for documentation generation (`Pidgin.Docs/`), examples (`Pidgin.Examples/`), a code generator (`Pidgin.CodeGen/`) and benchmarks, including a folder named `Pidgin.Bench.FParsec/` alongside `Pidgin.Bench/`. `Pidgin.slnx` and a `global.json` sit at the root next to `Directory.Build.props` and `Directory.Packages.props`, which is central package management, so dependency versions are pinned across the solution rather than per project. A `Pidgin.snk` file indicates strong-name signing. The README points at NuGet for installation and at the author's website for API documentation, and there is a separate tutorial there on parsing a subset of Prolog with Pidgin. The licence is MIT, with the licence file in the repository root. The most recent release is v3.5.1 from 2025-10-31, and the last push to the main branch was on 2026-06-29, so the code moves but the releases come at a measured pace.
Editorial conclusion
Adopt Pidgin when you need a hand written parser in C# for a language small enough to describe in a few hundred lines of code, and you want error messages and control flow in ordinary C# rather than in a generated artifact. Do not adopt it expecting regular expression semantics: it parses a larger class of languages, but it will not backtrack unless you ask it to, and the README is explicit about that trap. Verify first that your input needs a real token stream rather than a regex, since the type parameter `TToken` decides whether you are parsing characters or bytes, and read the `Try` documentation before you rely on alternatives.
Frequently asked questions
How do I install Pidgin for a C# project?
The README states that Pidgin is available on NuGet, and the API documentation is hosted on the author's website. It does not give a command line install step, so the package is added through your usual NuGet or dotnet tooling.
What is the core type in Pidgin?
It is `Parser<TToken, T>`, described in the README as a procedure which consumes an input stream of tokens and either fails with a parsing error or produces a `T`. The equivalent delegate is `T? Parser<TToken, T>(IEnumerator<TToken> input)`.
Does Pidgin backtrack when an alternative fails?
No. The README states that Pidgin does not perform any lookahead or backtracking by default, so if a component parser fails after consuming input the whole parser fails. Backtracking is enabled by wrapping the branch in `Try`.
What is the difference between Then, Before and Map in Pidgin?
`Then` runs two parsers in sequence and discards the first result, `Before` discards the second result, and `Map` keeps both and applies a transformation function to them. `Map` has overloads for between one and eight parsers.
Can Pidgin parse binary formats?
Yes, by changing the token type. The README notes that `TToken` may be some other type such as byte for other applications such as parsing binary file formats, and the static import in the examples is `Pidgin.Parser<char>` only because the tutorial parses strings.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/benjamin-hodgson-pidgin)Was this useful?