Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add documentation and examples for covariance feature
Co-authored-by: sebastienros <[email protected]>
  • Loading branch information
Copilot and sebastienros committed Nov 9, 2025
commit cc6e1b20a13bbda56e4e3f9984e2c4620a16d84c
45 changes: 45 additions & 0 deletions docs/covariance-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Covariance Support Example

This example demonstrates how `IParser<out T>` enables covariance, eliminating the need for wasteful `.Then<TBase>(x => x)` conversions.

## Before (Without Covariance)

```csharp
class Animal { public string Name { get; set; } }
class Dog : Animal { public string Breed { get; set; } }
class Cat : Animal { public string Color { get; set; } }

var dogParser = Terms.Text("dog").Then(_ => new Dog { Name = "Buddy", Breed = "Golden Retriever" });
var catParser = Terms.Text("cat").Then(_ => new Cat { Name = "Whiskers", Color = "Orange" });

// Had to use .Then<Animal>(x => x) to convert each parser - creates wrapper objects
var animalParser = dogParser.Then<Animal>(x => x).Or(catParser.Then<Animal>(x => x));
```

## After (With Covariance)

```csharp
class Animal { public string Name { get; set; } }
class Dog : Animal { public string Breed { get; set; } }
class Cat : Animal { public string Color { get; set; } }

var dogParser = Terms.Text("dog").Then(_ => new Dog { Name = "Buddy", Breed = "Golden Retriever" });
var catParser = Terms.Text("cat").Then(_ => new Cat { Name = "Whiskers", Color = "Orange" });

// Can use OneOf directly with IParser<T> - no wrapper objects needed!
var animalParser = OneOf<Animal>(dogParser, catParser);
```

## Benefits

1. **No wrapper objects**: The original parser instances are reused without creating `Then` wrappers
2. **Cleaner syntax**: More readable code without explicit type conversions
3. **Better performance**: Eliminates the overhead of wrapper parser objects
4. **Type safety**: Still maintains full type safety through the covariant interface

## How It Works

- `IParser<out T>` is a covariant interface (note the `out` keyword)
- This means `IParser<Dog>` can be used where `IParser<Animal>` is expected
- The `OneOf<T>` method now accepts `params IParser<T>[]` instead of just `Parser<T>[]`
- Under the hood, parsers are adapted as needed while preserving the original parser behavior
89 changes: 89 additions & 0 deletions docs/covariance-usage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Parlot.Fluent;
using static Parlot.Fluent.Parsers;

namespace CovarianceExample;

// Example domain model for an expression parser
abstract class Expression
{
public abstract int Evaluate();
}

class NumberExpression : Expression
{
public int Value { get; set; }
public override int Evaluate() => Value;
}

class AddExpression : Expression
{
public Expression Left { get; set; } = null!;
public Expression Right { get; set; } = null!;
public override int Evaluate() => Left.Evaluate() + Right.Evaluate();
}

class MultiplyExpression : Expression
{
public Expression Left { get; set; } = null!;
public Expression Right { get; set; } = null!;
public override int Evaluate() => Left.Evaluate() * Right.Evaluate();
}

class Program
{
static void Main()
{
// Define parsers for specific expression types
var numberParser = Terms.Integer().Then(n => new NumberExpression { Value = n });

// BEFORE: Would need explicit conversions like this:
// var expressionParser = numberParser.Then<Expression>(x => x);

// AFTER: Can use covariance directly!
// The OneOf method now accepts IParser<T>[] where T is covariant
Parser<Expression> expressionParser = OneOf<Expression>(
numberParser
// Could add more expression types here without .Then<Expression>(x => x)
);

var result = expressionParser.Parse("42");
if (result != null)
{
Console.WriteLine($"Parsed: {result.Evaluate()}"); // Output: Parsed: 42
}

// More complex example with multiple types
var addParser = Terms.Text("add").SkipAnd(Terms.Integer())
.And(Terms.Integer())
.Then(tuple => new AddExpression
{
Left = new NumberExpression { Value = tuple.Item1 },
Right = new NumberExpression { Value = tuple.Item2 }
});

var multiplyParser = Terms.Text("mul").SkipAnd(Terms.Integer())
.And(Terms.Integer())
.Then(tuple => new MultiplyExpression
{
Left = new NumberExpression { Value = tuple.Item1 },
Right = new NumberExpression { Value = tuple.Item2 }
});

// BEFORE: Would need .Then<Expression>(x => x) on each parser
// var complexParser = numberParser.Then<Expression>(x => x)
// .Or(addParser.Then<Expression>(x => x))
// .Or(multiplyParser.Then<Expression>(x => x));

// AFTER: Clean and simple with covariance
var complexParser = OneOf<Expression>(numberParser, addParser, multiplyParser);

var result1 = complexParser.Parse("42");
Console.WriteLine($"Number: {result1?.Evaluate()}"); // Output: Number: 42

var result2 = complexParser.Parse("add 10 20");
Console.WriteLine($"Add: {result2?.Evaluate()}"); // Output: Add: 30

var result3 = complexParser.Parse("mul 5 6");
Console.WriteLine($"Multiply: {result3?.Evaluate()}"); // Output: Multiply: 30
}
}