Open-source project
sebastienros/parlot avatar
sebastienros/parlot

Parlot is a parser combinator library where the grammar is the fluent API

Fast and lightweight parser creation tools

570 stars57 forksC#BSD-3-Clause

At a glance

What is it?
Parlot gives .NET developers a fluent parser combinator layer, so a grammar is written as C# rather than as a table of production rules, and the 2.0 line adds a source generator that emits the parsing method straight into your assembly with no Parlot runtime at all. It is small, it is BSD licensed, and it splits its releases across a stable 1.x line and a preview 2.0 feed.
Who is it for?
Adopt Parlot when you are writing a parser in .NET and want a grammar that reads as C#, or when you want the 2.0 source generator so the parser becomes generated code inside your own assembly with no Parlot dependency at run time. Do not adopt it if you need a grammar language of your own or a parser generator with a wider feature set, since Parlot is a library you call rather than a tool you point at a file.
Can I use it commercially?
Yes. BSD-3-Clause 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 2 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 grammar you write in C#

Parlot is described as a fast, lightweight and simple to use .NET parser combinator, and the README's own framing of the API is that it provides a fluent API based on parser combinators that provide a more readable grammar definition.

That is the whole proposition. A parser combinator library gives you primitives such as literals, character terms and sequences, and you assemble them into a grammar with method calls. The result is a grammar that can be read and reviewed in the same place as the rest of your code, and a parser that is an ordinary object rather than something a code generator produces at build time.

The README gives the Fluent API two goals. The main goal is to provide an easy to read grammar. The other is that grammars are built at runtime, and they can be extended dynamically. That second point is what separates Parlot from a table driven or generated parser: the grammar is data your program constructs, so you can add a term at run time, select between grammars, or build a grammar from configuration.

The project is C#, licensed under BSD 3-Clause, and the repository is a solution with `src/`, `test/`, `docs/` and `tools/` directories, plus `Parlot.sln`, `Directory.Build.props`, `Directory.Packages.props`, `global.json`, a `NuGet.config` and a `.whitesource` file. The recent tags are v2.0.0 and v1.6.0, both on 2026-10-20, with v1.5.12 on 2026-10-13, so both lines are being worked on in the same week.

Two branches, two feeds, and a preview version scheme

The branches and NuGet feeds table is the first thing to read, because it decides what you install.

The `release/1.x` branch is for maintenance of stable Parlot 1.x releases, and it publishes to NuGet.org as the stable version. The `main` branch is development of Parlot 2.0, including source-generated parsers, and it publishes to a Feedz preview feed rather than to NuGet.org.

The preview packages follow a naming scheme tied to the build system. A successful build on `main` publishes preview packages to the Parlot feed on feedz.io, and the versions follow `2.0.0-preview-<run number>`, using the GitHub Actions build run number. The README says these packages contain the latest development changes and are intended for testing before release.

To use them you add the preview feed alongside NuGet.org and then install the latest prerelease version:

shell
dotnet nuget add source https://f.feedz.io/sebastienros/parlot/nuget/index.json --name parlot-preview
dotnet add package Parlot --prerelease

So the default `dotnet add package Parlot` gets you the stable 1.x line, and the 2.0 work, which is where the source generator lives, has to be opted into explicitly.

The using static import is not optional

To use the Fluent API you need to import the static `Parsers` class, which provides access to `Terms`, `Literals` and other parser combinators:

c#
using Parlot.Fluent;
using static Parlot.Fluent.Parsers;

The README marks the second line as important, and explains why: the `using static Parlot.Fluent.Parsers;` statement is required to access `Terms`, `Literals`, `ZeroOrOne`, `Between` and the other combinators used in the examples. Alternatively, if your project has `ImplicitUsings`, also known as Global Usings, enabled, this import is included automatically.

That is a small ergonomic decision with a real consequence. A static import brings a large set of names into scope, which is convenient when the grammar is the code, and slightly noisy when it is not. Projects that enable ImplicitUsings get it for free, and projects that do not will need the line in every file that defines a grammar.

The README also points at where to read more: existing parsers and usage examples in `docs/parsers.md`, best practices for custom parsers in `docs/writing.md`, a source generation guide, the Parser Explorer, parsing streams in `docs/streaming.md`, and security guidance in `docs/security.md`.

The calculator example shows the grammar idiom

The worked example in the README builds a mathematical expression tree, and the source is in `src/Samples/Calc/FluentParser.cs`. What it demonstrates is the shape of a Parlot grammar, and the grammar is written out as a comment above the code in the same notation a parsing DSL would use, with additive, multiplicative, unary and primary productions and the precedence noted in a sentence.

Two details in the code carry most of the weight.

The first is the deferred parser, which is what lets the grammar refer to itself. The comment in the code says it plainly: the Deferred helper creates a parser that can be referenced by others before it is defined, and the line that does it is:

c#
    // The Deferred helper creates a parser that can be referenced by others before it is defined
    var expression = Deferred<Expression>();

    var number = Terms.Decimal()
        .Then<Expression>(static d => new Number(d))
        ;

The second is the shape of a term. `Terms.Decimal()` matches a decimal, and `Then<Expression>` maps the matched value into a node of the tree, so the parser produces an AST rather than a string. The `static` lambda matters for a library that advertises itself as fast, since a static delegate does not capture a closure.

The rest of the example composes the grammar with the combinators the API is named after. `Between(openParen, expression, closeParen)` for the parenthesised form, `Or` for alternatives, `Unary` for the prefix case, and `LeftAssociative` for the binary operators, which is how left associativity is expressed without writing the recursion by hand.

Streams are parsed without loading the document

The streaming section is the part of the library with the most unusual design, and it is worth reading carefully because it changes what a parser costs in memory.

`Parse`, `TryParse`, `ParseAsync` and `TryParseAsync` parse a `TextReader` with the same parsers, through a compacting buffer which only retains the text the parser can still read. The consequence is stated as a property of the library: a document of any size is parsed in a single pass, with memory bounded by its largest token.

`ParseManyAsync` does the same for a sequence of values, such as NDJSON records, with memory bounded by the largest value:

csharp
using var document = File.OpenText("document.json");
var value = JsonParser.Json.Parse(document);

using var input = File.OpenText("records.ndjson");
await foreach (var item in record.ParseManyAsync(input, '\n'))
{
    Console.WriteLine(item);
}

That is a genuine difference in approach from a parser that loads the whole input into a string. A 2 GB log file can be parsed with a buffer the size of its longest token, and an NDJSON stream can be consumed record by record without the whole file resident.

The cost is that the same parsers now run over a reader rather than over a string, and the compacting buffer is what makes that possible. If your grammar assumes random access into the input, this is the part of the library to test first.

Source-generated parsers with no runtime dependency

The 2.0 direction is the analyzer, and the README states the goal plainly. Parlot's analyzer generates direct parsing methods during the normal build, and the generated code and shared internal support sources compile into your assembly, with no Parlot runtime dependency.

That is a different product from the runtime grammar. With the analyzer, the grammar is a factory method the build runs, and what lands in your assembly is a plain method that parses text.

The steps are four. Reference the `Parlot.SourceGenerator` package with `PrivateAssets="all"`. Declare a static partial `bool TryParse(string text, out T value)` method in a normal `.cs` file. Define its factory in a build-only `.parlot.cs` file with `[GenerateParser(nameof(TryParse))]`. And the analyzer executes the factory at build time and emits the method implementation and the support code.

There is a further detail for variants. You pass configuration arguments between the input and output parameters and capture them in `If`, `Select`, or other supported parse-time callbacks in the matching factory, which is how a single factory can produce a family of parsers with different settings.

The `PrivateAssets="all"` setting is the load-bearing part of step one. It is what keeps the generator package from flowing into your published dependency graph, which is consistent with the promise of no Parlot runtime dependency at all.

A local Explorer for the generated parsers

The Parser Explorer is a separate local tool, and it is aimed at the case where the source generator makes debugging awkward. Parlot Explorer, in `tools/Parlot.Explorer/README.md`, is a local .NET tool for inspecting source-generated parsers in a standalone window on Windows, macOS and Linux.

What it does is described in a list. You edit the input, replay named parser calls and backtracking, inspect the buffer at each step, and view the returned object as a tree or as JSON, and the README stresses that all of this happens on your computer.

That last point is the one that matters for a tool that parses untrusted text. The library ships a `docs/security.md` page, and the Explorer is designed so that a parser can be replayed step by step without sending anything anywhere. A generated parser that misbehaves on a particular input is otherwise a hard thing to investigate, because you are reading generated code, and the Explorer gives you the buffer contents and the call sequence instead.

So the documentation set is a coherent whole. `docs/parsers.md` for what exists, `docs/writing.md` for how to write your own, `docs/source-generation.md` for the analyzer, `docs/streaming.md` for the reader-based parsers, `docs/security.md` for the safety guidance, and `docs/explorer.md` for the tool that makes generated parsers inspectable.

Editorial conclusion

Adopt Parlot when you are writing a parser in .NET and want a grammar that reads as C#, or when you want the 2.0 source generator so the parser becomes generated code inside your own assembly with no Parlot dependency at run time. Do not adopt it if you need a grammar language of your own or a parser generator with a wider feature set, since Parlot is a library you call rather than a tool you point at a file. Verify first which branch you are on, because `release/1.x` is the stable NuGet.org line and `main` is the 2.0 development line, that the `using static Parlot.Fluent.Parsers;` import is in place, and that the version you pin matches the one you tested, with v2.0.0 tagged on 2026-10-20.

Frequently asked questions

What is Parlot?

Parlot is a fast, lightweight and simple to use .NET parser combinator library. It provides a fluent API of parser combinators so a grammar is written as C# method calls, and grammars are built at runtime, which means they can also be extended dynamically.

How do I install a preview build of Parlot 2.0?

Add the preview feed and then install the prerelease package: `dotnet nuget add source https://f.feedz.io/sebastienros/parlot/nuget/index.json --name parlot-preview` followed by `dotnet add package Parlot --prerelease`. Preview versions follow `2.0.0-preview-<run number>` using the GitHub Actions build run number.

What does the Parlot source generator produce?

The analyzer executes a factory at build time and emits a direct parsing method, so the generated code and shared internal support sources compile into your assembly with no Parlot runtime dependency. You reference Parlot.SourceGenerator with PrivateAssets="all", declare a static partial bool TryParse method, and define the factory in a build-only .parlot.cs file with [GenerateParser(nameof(TryParse))].

Can Parlot parse a file that does not fit in memory?

Yes. Parse, TryParse, ParseAsync and TryParseAsync parse a TextReader through a compacting buffer that only retains the text the parser can still read, so a document of any size is parsed in a single pass with memory bounded by its largest token, and ParseManyAsync does the same for successive values such as NDJSON records.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. README
  4. Releases
  5. sebastienros/parlot on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sebastienros-parlot.svg)](https://hysenlabs.com/projects/sebastienros-parlot)

Was this useful?