Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
18 changes: 13 additions & 5 deletions src/Ramstack.Parsing/Parser.Fold.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ namespace Ramstack.Parsing;

partial class Parser
{
/// <summary>
/// Creates a left-associative parser. This method is equivalent to <see cref="FoldL{T, TOperator}"/>.
/// </summary>
/// <inheritdoc cref="FoldL{T, TOperator}"/>
[EditorBrowsable(EditorBrowsableState.Never)]
public static Parser<T> Fold<T, TOperator>(this Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) =>
parser.FoldL(op, reduce);

/// <summary>
/// Creates a left-associative parser.
/// </summary>
/// <remarks>
/// <code>
/// // Example: Number ([+-] Number)*
/// // 1 + 2 + 3 + 4 => (((1 + 2) + 3) + 4)
/// var sum = number.Fold(OneOf("+-"), (l, r, op) => op == '+' ? l + r : l - r);
/// var sum = number.FoldL(OneOf("+-"), (l, r, op) => op == '+' ? l + r : l - r);
/// </code>
/// If an operator-operand pair consumes no input, it is discarded and parsing stops
/// without calling the reduction function for that pair.
Expand All @@ -22,8 +30,8 @@ partial class Parser
/// <returns>
/// A parser that performs left-associative folding.
/// </returns>
public static Parser<T> Fold<T, TOperator>(this Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) =>
new FoldParser<T, TOperator>(parser, op, reduce);
public static Parser<T> FoldL<T, TOperator>(this Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) =>
new FoldLParser<T, TOperator>(parser, op, reduce);

/// <summary>
/// Creates a right-associative parser.
Expand All @@ -50,7 +58,7 @@ public static Parser<T> Fold<T, TOperator>(this Parser<T> parser, Parser<TOperat
public static Parser<T> FoldR<T, TOperator>(this Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) =>
new FoldRParser<T, TOperator>(parser, op, reduce);

#region Inner type: FoldParser
#region Inner type: FoldLParser

/// <summary>
/// Represents a left-associative parser.
Expand All @@ -60,7 +68,7 @@ public static Parser<T> FoldR<T, TOperator>(this Parser<T> parser, Parser<TOpera
/// <param name="parser">The main parser that matches a value.</param>
/// <param name="op">The operator parser that matches an operator token.</param>
/// <param name="reduce">A reduction function.</param>
private sealed class FoldParser<T, TOperator>(Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) : Parser<T>
private sealed class FoldLParser<T, TOperator>(Parser<T> parser, Parser<TOperator> op, Func<T, T, TOperator, T> reduce) : Parser<T>
{
/// <inheritdoc />
public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value)
Expand Down
2 changes: 1 addition & 1 deletion src/Ramstack.Parsing/Parser`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected Parser(string? name) =>
/// </para>
/// <para>
/// Exceptions thrown by user-provided functions, such as the callbacks passed to
/// <c>Map</c>, <c>Do</c>, or <c>Fold</c>, are not suppressed and propagate to the caller.
/// <c>Map</c>, <c>Do</c>, or <c>FoldL</c>, are not suppressed and propagate to the caller.
/// Use <see cref="Parse(ReadOnlySpan{char})"/> to capture them in
/// <see cref="ParseResult{T}.Exception"/> instead.
/// </para>
Expand Down
57 changes: 39 additions & 18 deletions tests/Ramstack.Parsing.Tests/ParsersTests.Fold.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,37 @@ partial class ParsersTests
[TestCase("1+2-", 3, 3)]
[TestCase("1+2+3+4-2", 8, 9)]
[TestCase("1+2+3+4-2$", 8, 9)]
public void FoldTest(string expr, int result, int length)
public void FoldLTest(string expr, int result, int length)
{
var number = Literal.Number<int>();
var parser = number.Fold(OneOf("+-"), (l, r, o) => o == '+' ? l + r : l - r);
var parser = number.FoldL(OneOf("+-"), (l, r, o) => o == '+' ? l + r : l - r);

Assert.That(parser.Parse(expr).Success, Is.True);
Assert.That(parser.Parse(expr).Value, Is.EqualTo(result));
Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(expr).Value, Is.EqualTo((0, length)));
}

[TestCase("8", 8, 1)]
[TestCase("8-3-1", 4, 5)]
[TestCase("8-3-", 5, 3)]
public void Fold_EquivalentToFoldL(string expr, int expected, int length)
{
var number = Literal.Number<int>();
var op = L('-');
var reduce = (int l, int r, char _) => l - r;

var parsers = new[] { number.Fold(op, reduce), number.FoldL(op, reduce) };

foreach (var parser in parsers)
{
var result = parser.Parse(expr);

Assert.That(result.Success, Is.True);
Assert.That(result.Value, Is.EqualTo(expected));
Assert.That(result.Length, Is.EqualTo(length));
}
}

[TestCase("2", "2", 1)]
[TestCase("2$", "2", 1)]
[TestCase("2**", "2", 1)]
Expand All @@ -39,7 +60,7 @@ public void FoldRTest(string expr, string result, int length)
}

[Test]
public void Fold_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative)
public void FoldL_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative)
{
var operand = new BoundedParser<int>(Return(2));
var op = Return('-');
Expand All @@ -53,7 +74,7 @@ public void Fold_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssocia

var parser = rightAssociative
? operand.FoldR(op, reduce)
: operand.Fold(op, reduce);
: operand.FoldL(op, reduce);

var context = new ParseContext("!tail");
context.Advance(1);
Expand All @@ -71,7 +92,7 @@ public void Fold_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssocia
}

[Test]
public void FoldVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative)
public void FoldLVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative)
{
var operand = new BoundedParser<int>(Return(2));
var op = Return('-');
Expand All @@ -85,7 +106,7 @@ public void FoldVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAss

var parser = rightAssociative
? operand.FoldR(op, reduce)
: operand.Fold(op, reduce);
: operand.FoldL(op, reduce);

var context = new ParseContext("!tail");
context.Advance(1);
Expand All @@ -103,7 +124,7 @@ public void FoldVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAss

[TestCase(false, 6)]
[TestCase(true, 8)]
public void Fold_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, int expected)
public void FoldL_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, int expected)
{
var operand = new BoundedParser<int>(
Literal.Number<int>().DefaultOnFail(2)
Expand All @@ -120,7 +141,7 @@ public void Fold_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, i

var parser = rightAssociative
? operand.FoldR(op, reduce)
: operand.Fold(op, reduce);
: operand.FoldL(op, reduce);

var result = parser.Parse("10-3-1!");

Expand All @@ -131,7 +152,7 @@ public void Fold_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, i
}

[Test]
public void FoldVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightAssociative)
public void FoldLVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightAssociative)
{
var operand = new BoundedParser<int>(
Literal.Number<int>().DefaultOnFail(2)
Expand All @@ -148,7 +169,7 @@ public void FoldVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightA

var parser = rightAssociative
? operand.FoldR(op, reduce)
: operand.Fold(op, reduce);
: operand.FoldL(op, reduce);

var result = parser.Void().Parse("10-3-1!");

Expand All @@ -159,14 +180,14 @@ public void FoldVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightA

[TestCase(false, -4)]
[TestCase(true, 2)]
public void Fold_OnlyOperandConsumesInput_ContinuesParsing(bool rightAssociative, int expected)
public void FoldL_OnlyOperandConsumesInput_ContinuesParsing(bool rightAssociative, int expected)
{
var operand = Set('0', '9').Do(c => c - '0');
var op = Return('-');

var parser = rightAssociative
? operand.FoldR(op, (l, r, _) => l - r)
: operand.Fold(op, (l, r, _) => l - r);
: operand.FoldL(op, (l, r, _) => l - r);

var result = parser.Parse("123!");

Expand All @@ -176,14 +197,14 @@ public void Fold_OnlyOperandConsumesInput_ContinuesParsing(bool rightAssociative
}

[Test]
public void FoldVoid_OnlyOperandConsumesInput_ContinuesParsing([Values] bool rightAssociative)
public void FoldLVoid_OnlyOperandConsumesInput_ContinuesParsing([Values] bool rightAssociative)
{
var operand = Set('0', '9').Do(c => c - '0');
var op = Return('-');

var parser = rightAssociative
? operand.FoldR(op, (l, r, _) => l - r)
: operand.Fold(op, (l, r, _) => l - r);
: operand.FoldL(op, (l, r, _) => l - r);

var result = parser.Void().Parse("123!");

Expand All @@ -193,14 +214,14 @@ public void FoldVoid_OnlyOperandConsumesInput_ContinuesParsing([Values] bool rig

[TestCase(false, -2)]
[TestCase(true, 2)]
public void Fold_OnlyOperatorConsumesInput_ContinuesParsing(bool rightAssociative, int expected)
public void FoldL_OnlyOperatorConsumesInput_ContinuesParsing(bool rightAssociative, int expected)
{
var operand = Return(2);
var op = L('-');

var parser = rightAssociative
? operand.FoldR(op, (l, r, _) => l - r)
: operand.Fold(op, (l, r, _) => l - r);
: operand.FoldL(op, (l, r, _) => l - r);

var result = parser.Parse("--!");

Expand All @@ -210,14 +231,14 @@ public void Fold_OnlyOperatorConsumesInput_ContinuesParsing(bool rightAssociativ
}

[Test]
public void FoldVoid_OnlyOperatorConsumesInput_ContinuesParsing([Values] bool rightAssociative)
public void FoldLVoid_OnlyOperatorConsumesInput_ContinuesParsing([Values] bool rightAssociative)
{
var operand = Return(2);
var op = L('-');

var parser = rightAssociative
? operand.FoldR(op, (l, r, _) => l - r)
: operand.Fold(op, (l, r, _) => l - r);
: operand.FoldL(op, (l, r, _) => l - r);

var result = parser.Void().Parse("--!");

Expand Down
Loading