Skip to content

Commit

Permalink
tolerance to trailing newlines
Browse files Browse the repository at this point in the history
  • Loading branch information
mazharenko committed Dec 2, 2024
1 parent e775680 commit 00a4807
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 6 deletions.
44 changes: 39 additions & 5 deletions src/ParsingExtensions/Combinators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ public static TextParser<T[]> AtLeastOnceDelimitedBySpaces<T>(this TextParser<T>
{
return parser.AtLeastOnceDelimitedBy(SpanX.Space);
}

[GeneratedRegex("^.*?(?=(\r\n|\n){2})", RegexOptions.Singleline)]
private static partial Regex TextUntilBlockSeparatorRegex();

public static TextParser<T> Block<T>(this TextParser<T> parser)
{
TextParser<TextSpan> untilSeparatorParser = input =>
TextParser<TextSpan> untilSeparatorParser = input =>
{
if (input.Length == 0)
return Result.Empty<TextSpan>(input, ["some text until block separator"]);
Expand All @@ -72,12 +73,45 @@ public static TextParser<U> ThenBlock<T, U>(this TextParser<T> first, Func<T, Te
{
return first.Then(t => second(t).Block());
}


/// <summary>
/// Construct a parser that matches <paramref name="parser" /> againts the trimmed input - without one trailing new line (\n or \r\n)
/// </summary>
/// <param name="parser">The parser.</param>
/// <typeparam name="T">The type of value being parsed.</typeparam>
/// <returns>The resulting parser.</returns>
public static TextParser<T> TrimTrailingNewLine<T>(this TextParser<T> parser)
{
// It's a bit tricky to ignore the trailing newline because when the Lines parser encounters it, it expects another line.
// This can be resolved with tokenization, but we want to avoid that to keep things simple.
return input =>
{
if (input.IsAtEnd)
return parser(input);
if (input.Source![input.Position.Absolute + input.Length - 1] == '\n')
{
var trimmedInput = new TextSpan(input.Source, input.Position, input.Length - 1);
if (!trimmedInput.IsAtEnd &&
trimmedInput.Source![trimmedInput.Position.Absolute + trimmedInput.Length - 1] == '\r')
trimmedInput = new TextSpan(trimmedInput.Source, trimmedInput.Position, trimmedInput.Length - 1);
return parser(trimmedInput);
}

return parser(input);
};
}

/// <summary>
/// Construct a parser that matches <paramref name="parser" /> zero or more times, delimited by new lines and an optional trailing new line.
/// </summary>
/// <typeparam name="T">The type of value being parsed.</typeparam>
/// <param name="parser">The parser.</param>
/// <returns>The resulting parser.</returns>
public static TextParser<T[]> Lines<T>(this TextParser<T> parser)
{
return parser.ManyDelimitedBy(SpanX.NewLine);
return parser.ManyDelimitedBy(SpanX.NewLine).TrimTrailingNewLine();
}

public static TextParser<T> ThenIgnore<T, U>(this TextParser<T> first, TextParser<U> second)
{
return first.Then(x => second.Select(_ => x));
Expand Down
1 change: 0 additions & 1 deletion src/aoc/Year2024/Day02/Day02.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public int Solve(long[][] input)
|| Enumerable.Range(0, levels.Length).Any(i =>
Safe([..levels[..i], ..levels[(i + 1)..]])
);

});
}

Expand Down
48 changes: 48 additions & 0 deletions tests/ParsingExtensions.Tests/CombinatorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ public void Lines()
result.HasValue.Should().BeTrue();
result.Value.Should().BeEquivalentTo(expected);
}

[Test]
public void Lines_Empty()
{
const string source = "";
int[] expected = [];
var parser = Numerics.IntegerInt32.Lines();
var result = parser.TryParse(source);

result.HasValue.Should().BeTrue();
result.Value.Should().BeEquivalentTo(expected);
}

[Test]
public void Lines_TrailingNewLine()
{
Expand All @@ -26,6 +38,42 @@ public void Lines_TrailingNewLine()
result.Value.Should().BeEquivalentTo(expected);
}

[Test]
public void Lines_Empty_TrailingNewLine()
{
const string source = "\n";
int[] expected = [];
var parser = Numerics.IntegerInt32.Lines();
var result = parser.TryParse(source);

result.HasValue.Should().BeTrue();
result.Value.Should().BeEquivalentTo(expected);
}

[Test]
public void Lines_TrailingNewLine2()
{
const string source = "2\n3\n4\r\n1\r\n";
int[] expected = [2, 3, 4, 1];
var parser = Numerics.IntegerInt32.Lines();
var result = parser.TryParse(source);

result.HasValue.Should().BeTrue();
result.Value.Should().BeEquivalentTo(expected);
}


[Test]
public void Lines_Empty_TrailingNewLine2()
{
const string source = "\r\n";
int[] expected = [];
var parser = Numerics.IntegerInt32.Lines();
var result = parser.TryParse(source);

result.HasValue.Should().BeTrue();
result.Value.Should().BeEquivalentTo(expected);
}
[Test]
public void Blocks()
{
Expand Down

0 comments on commit 00a4807

Please sign in to comment.