Skip to content

Commit

Permalink
parser from template. closes #5
Browse files Browse the repository at this point in the history
  • Loading branch information
mazharenko committed Jan 6, 2025
1 parent 368c597 commit 4cd7507
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 88 deletions.
3 changes: 1 addition & 2 deletions src/ParsingExtensions/Combinators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static partial class Combinators
/// Construct a parser that applies <paramref name="first" />, applies <paramref name="second" /> and returns their results as a tuple.
/// </summary>
/// <typeparam name="T">The type of value being parsed by <paramref name="first" />.</typeparam>
/// <typeparam name="U">The type of value being parsed by <paramref name="first" />.</typeparam>
/// <typeparam name="U">The type of value being parsed by <paramref name="second" />.</typeparam>
/// <param name="first">The first parser.</param>
/// <param name="second">The second parser.</param>
/// <returns>The resulting parser.</returns>
Expand All @@ -36,7 +36,6 @@ public static TextParser<T[]> ManyDelimitedBySpaces<T>(this TextParser<T> parser
/// Construct a parser that matches one or more instances of applying <paramref name="parser"/>, delimited by space.
/// </summary>
/// <typeparam name="T">The type of value being parsed.</typeparam>
/// <typeparam name="U">The type of the resulting value.</typeparam>
/// <param name="parser">The parser.</param>
/// <returns>The resulting parser.</returns>
public static TextParser<T[]> AtLeastOnceDelimitedBySpaces<T>(this TextParser<T> parser)
Expand Down
77 changes: 77 additions & 0 deletions src/ParsingExtensions/Template.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Runtime.CompilerServices;
using Superpower;
using Superpower.Model;
using Superpower.Parsers;

namespace ParsingExtensions;

public static class Template
{
public static TextParser<T> Matching<T>(TemplateParserStringHandler<T> stringHandler)
{
if (typeof(T).IsAssignableTo(typeof(ITuple)))
return stringHandler.GetParser().Select(t => (T)t);
return stringHandler.GetParser().Select(t => (T)t[0]!);
}
public static TextParser<(T1, T2)> Matching<T1, T2>(TemplateParserStringHandler<(T1, T2)> stringHandler)
{
return stringHandler.GetParser().Select(t => ((T1, T2))t);
}
public static TextParser<(T1, T2, T3)> Matching<T1, T2, T3>(TemplateParserStringHandler<(T1, T2, T3)> stringHandler)
{
return stringHandler.GetParser().Select(t => ((T1, T2, T3))t);
}
public static TextParser<(T1, T2, T3, T4)> Matching<T1, T2, T3, T4>(TemplateParserStringHandler<(T1, T2, T3, T4)> stringHandler)
{
return stringHandler.GetParser().Select(t => ((T1, T2, T3, T4))t);
}
public static TextParser<(T1, T2, T3, T4, T5)> Matching<T1, T2, T3, T4, T5>(TemplateParserStringHandler<(T1, T2, T3, T4, T5)> stringHandler)
{
return stringHandler.GetParser().Select(t => ((T1, T2, T3, T4, T5))t);
}
}

[InterpolatedStringHandler]
public class TemplateParserStringHandler<T>
{
private TextParser<ITuple> accParser = span => Result.Value((ITuple)ValueTuple.Create(), span, span);

public TemplateParserStringHandler(int literalLength, int formattedCount)
{
if (typeof(T).IsAssignableTo(typeof(ITuple)))
{
var argsLength = typeof(T).GenericTypeArguments.Length;
if (argsLength != formattedCount)
throw new ArgumentException(
$"The template contains {formattedCount} formatted values, but type {typeof(T)} has {argsLength} arguments.");
}
else if (1 != formattedCount)
throw new ArgumentException(
$"The template contains {formattedCount} formatted values, but {typeof(T)} is not a tuple type.");
}

public void AppendLiteral(string value)
{
accParser = accParser.ThenIgnore(Span.EqualTo(value));
}

public void AppendFormatted<T1>(TextParser<T1> t) where T1 : notnull
{
accParser = accParser.Then(x => t.Select(ITuple (y) =>
{
var tupleArgs = Enumerable.Range(0, x.Length)
.Select(i => x[i]!)
.Append(y).ToArray();

var createMethod = typeof(ValueTuple).GetMethods()
.Single(m1 => m1.Name == nameof(ValueTuple.Create) && m1.GetParameters().Length == tupleArgs.Length)
.MakeGenericMethod(tupleArgs.Select(gg => gg.GetType()).ToArray());
return (ITuple)createMethod.Invoke(null, tupleArgs.ToArray())!;
}));
}

public TextParser<ITuple> GetParser()
{
return accParser;
}
}
19 changes: 6 additions & 13 deletions src/aoc/Year2024/Day13.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using aoc.Common;
using Microsoft.Z3;
using ParsingExtensions;

namespace aoc.Year2024;

Expand Down Expand Up @@ -27,23 +28,15 @@ internal partial class Day13
public (V<int> a, V<int> b, V<long>prize)[] Parse(string input)
{
var parser =
Span.EqualTo("Button A: X+")
.IgnoreThen(Numerics.IntegerInt32)
.ThenIgnore(Span.EqualTo(", Y+"))
.Then(Numerics.IntegerInt32)
Template.Matching<int, int>($"Button A: X+{Numerics.IntegerInt32}, Y+{Numerics.IntegerInt32}")
.Select(V.Create)
.ThenLine(
Span.EqualTo("Button B: X+")
.IgnoreThen(Numerics.IntegerInt32)
.ThenIgnore(Span.EqualTo(", Y+"))
.Then(Numerics.IntegerInt32)
Template.Matching<int, int>($"Button B: X+{Numerics.IntegerInt32}, Y+{Numerics.IntegerInt32}")
.Select(V.Create)
).ThenLine(
Template.Matching<long, long>($"Prize: X={Numerics.IntegerInt64}, Y={Numerics.IntegerInt64}")
.Select(V.Create)
)
.ThenLine(Span.EqualTo("Prize: X=")
.IgnoreThen(Numerics.IntegerInt64)
.ThenIgnore(Span.EqualTo(", Y="))
.Then(Numerics.IntegerInt64)
.Select(V.Create))
.Select(x =>
{
var ((a, b), prize) = x;
Expand Down
16 changes: 5 additions & 11 deletions src/aoc/Year2024/Day14.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
using aoc.Common;
using mazharenko.AoCAgent.Generator;
using MoreLinq;
using Spectre.Console;
using ParsingExtensions;

namespace aoc.Year2024;

internal partial class Day14
{
public (V<int> p, V<int> v)[] Parse(string input)
{
return Span.EqualTo("p=")
.IgnoreThen(Numerics.IntegerInt32)
.ThenIgnore(Span.EqualTo(','))
.Then(Numerics.IntegerInt32)
.Select(x => V.Create(x.Item2, x.Item1))
.Then(Span.EqualTo(" v=")
.IgnoreThen(Numerics.IntegerInt32
.ThenIgnore(Span.EqualTo(','))
.Then(Numerics.IntegerInt32)
.Select(x => V.Create(x.Item2, x.Item1))))
return Template.Matching<(int py, int px, int vy, int vx)>(
$"p={Numerics.IntegerInt32},{Numerics.IntegerInt32} v={Numerics.IntegerInt32},{Numerics.IntegerInt32}"
)
.Select(t => (V.Create(t.px, t.py), V.Create(t.vx, t.vy)))
.Lines().Parse(input);
}

Expand Down
39 changes: 18 additions & 21 deletions src/aoc/Year2024/Day17.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using aoc.Common;
using mazharenko.AoCAgent.Generator;
using MoreLinq;
using ParsingExtensions;

namespace aoc.Year2024;

Expand Down Expand Up @@ -67,27 +68,23 @@ public long Solve((Registers registers, byte[] program) input)

public (Registers registers, byte[] program) Parse(string input)
{
var registerParser =
Span.EqualTo("Register ").IgnoreThen(Character.Letter)
.IgnoreThen(Span.EqualTo(": "))
.IgnoreThen(Numerics.IntegerInt32);
return registerParser
.ThenLine(registerParser)
.ThenLine(registerParser)
.Block()
.Then(
Span.EqualTo("Program: ")
.IgnoreThen(Numerics.IntegerInt32.Select(i => (byte)i)
.ManyDelimitedBy(Character.EqualTo(',')
)

))
.Select(x =>
{
var (((a, b), c), program) = x;
return (new Registers(a, b, c), program);
})
.Parse(input);
return
Template.Matching<int>($"Register A: {Numerics.IntegerInt32}")
.ThenLine(Template.Matching<int>($"Register B: {Numerics.IntegerInt32}"))
.ThenLine(Template.Matching<int>($"Register C: {Numerics.IntegerInt32}"))
.Block()
.Then(
Span.EqualTo("Program: ")
.IgnoreThen(Numerics.IntegerInt32.Select(i => (byte)i)
.ManyDelimitedBy(Character.EqualTo(',')
)
))
.Select(x =>
{
var (((a, b), c), program) = x;
return (new Registers(a, b, c), program);
})
.Parse(input);
}

internal record Registers(long A, long B, long C);
Expand Down
50 changes: 9 additions & 41 deletions src/aoc/Year2024/Day24.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using mazharenko.AoCAgent.Generator;
using aoc.Common;
using Spectre.Console;
using ParsingExtensions;

namespace aoc.Year2024;

internal partial class Day24
{
public record InputBit(string Name, int Digit, int Value) // bool?
public record InputBit(string Name, int Digit, int Value)
{
public string FullName { get; } = $"{Name}{Digit:00}";
}
Expand Down Expand Up @@ -38,50 +36,20 @@ public record Gate(string Output, string Input1, string Input2, Operation Operat
);

var parser =
Character.Letter.Then(Numerics.IntegerInt32)
.ThenIgnore(Span.EqualTo(": "))
.Then(Numerics.IntegerInt32)
.Select(x => new InputBit(x.Item1.Item1.ToString(), x.Item1.Item2, x.Item2))
Template.Matching<(char name, int digit, int value)>($"{Character.Letter}{Numerics.IntegerInt32}: {Numerics.IntegerInt32}")
.Select(x => new InputBit(x.name.ToString(), x.digit, x.value))
.Lines()
.Block()
.ThenBlock(
wireName.ThenIgnore(SpanX.Space).Then(op).ThenIgnore(SpanX.Space).Then(wireName).ThenIgnore(Span.EqualTo(" -> ")).Then(wireName)
.Select(((tuple, s) => // todo: tuple flatten? flattening Select? flattening Then?
{
return new Gate(s, tuple.Item1.Item1, tuple.Item2, tuple.Item1.Item2);
})
).Lines());
Template.Matching<(string in1, Operation op, string in2, string @out)>($"{wireName} {op} {wireName} -> {wireName}")
.Select(x =>
new Gate(x.@out, x.in1, x.in2, x.op))
.Lines()
);

return parser.Parse(input);
}

public IDictionary<string, (string, string, string)> Parse2(string input)
{
var wire = Character.LetterOrDigit.Many().Select(chars => new string(chars));
var op = Character.Letter.Many().Select(
chars => new string(chars)
);

var dic = new Dictionary<string, (string, string, string)>();

var parser = wire.ThenIgnore(Span.EqualTo(": "))
.Then(Numerics.IntegerInt32)
.Select((s, i) => (s, ("", "", "")))
.Lines()
.Block()
.ThenBlock(
wire.ThenIgnore(SpanX.Space).Then(op).ThenIgnore(SpanX.Space).Then(wire).ThenIgnore(Span.EqualTo(" -> ")).Then(wire)
.Select(((tuple, s) => { return (s, (tuple.Item1.Item1, tuple.Item1.Item2, tuple.Item2)); })
).Lines());

var (s1, s2) = parser.Parse(input);

foreach (var (s, lazy) in s1) dic[s] = lazy;
foreach (var (s, lazy) in s2) dic[s] = lazy;

return dic;
}

internal partial class Part1
{
private readonly Example example = new(
Expand Down
53 changes: 53 additions & 0 deletions tests/ParsingExtensions.Tests/TemplateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace ParsingExtensions.Tests;

public class TemplateTests
{
[Test]
public void Should_Parse_Tuple1()
{
var parser = Template.Matching<int, int>(
$"p={Numerics.IntegerInt32},{Numerics.IntegerInt32};"
);

var result = parser.AtEnd().TryParse("p=3434,8787;");
result.HasValue.Should().BeTrue();
result.Value.Should().Be((3434, 8787));
}

[Test]
public void Should_Parse_Tuple2()
{
var parser = Template.Matching<(int, int)>(
$"p={Numerics.IntegerInt32},{Numerics.IntegerInt32};"
);

var result = parser.AtEnd().TryParse("p=3434,8787;");
result.HasValue.Should().BeTrue();
result.Value.Should().Be((3434, 8787));
}

[Test]
public void Should_Parse_SingleValue()
{
var parser = Template.Matching<int>(
$"p={Numerics.IntegerInt32};"
);

var result = parser.AtEnd().TryParse("p=3434;");
result.HasValue.Should().BeTrue();
result.Value.Should().Be(3434);
}

[Test]
public void Should_Return_Remainder()
{
var parser = Template.Matching<int>(
$"p={Numerics.IntegerInt32};"
);

var result = parser.TryParse("p=3434;v=9898");
result.HasValue.Should().BeTrue();
result.Value.Should().Be(3434);
result.Remainder.ToStringValue().Should().Be("v=9898");
}
}

0 comments on commit 4cd7507

Please sign in to comment.