Last active
March 5, 2020 17:10
-
-
Save chgeuer/194c750548879c2f41e3107b08b5b834 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <PackageReference Include="Pidgin" Version="2.2.0" /> | |
// <PackageReference Include="Sprache" Version="2.2.0" /> | |
namespace RedisParser | |
{ | |
using System; | |
using System.Text; | |
using System.Collections.Generic; | |
using Sprache; | |
using Pidgin; | |
using static Pidgin.Parser; | |
using static Pidgin.Parser<char>; | |
// Trying to parse some Redis structures using parser/combuinator libraries Pidgin and Sprache | |
class Program | |
{ | |
static void Main() | |
{ | |
// Parser<char, long> sequencedParser = Map((l, bar, x) => l, Long(10), String("bar"), Char('x')).Between(Char('$'), CRLF); | |
// var w = Map((l, s) => (l, s), LongNum.Before(CRLF), Any.RepeatString(20)).Between(Char('$'), CRLF).Parse("$4\r\n83828282828382828282\r\n"); | |
// https://redis.io/topics/protocol | |
foreach (var pattern in new[] { "+Hallo Welt\r\n", "-Exception: That was bad... \r\n", ":5675\r\n", "$2\r\nan\r\n" }) | |
{ | |
Console.WriteLine($"Pidgin: {makePrintable(pattern)} => {pattern.ParsePidgin().ToNiceString()}"); | |
Console.WriteLine($"Sprache: {makePrintable(pattern)} => {pattern.ParseSprache().ToNiceString()}"); | |
} | |
static string makePrintable(string val) => val.Replace("\r", "\\r").Replace("\n", "\\n"); | |
} | |
} | |
public static class MyExtensions | |
{ | |
public static string ToNiceString(this IRedisCommand command) => command switch { | |
RedisSimpleString s => $"String: \"{s.Value}\"", | |
RedisError e => $"Error: \"{e.Value}\"", | |
RedisInteger i => $"Integer: {i.Value}", | |
RedisBulkString b => $"Bulk String {b.Value.Length} bytes", | |
_ => "Error" | |
}; | |
} | |
public static class SpracheParser | |
{ | |
public static IRedisCommand ParseSprache(this string input) => RedisParser.Parse(input); | |
private static readonly Sprache.Parser<string> NonCRLF = Parse.Char(c => c != '\r' && c != '\n', "no crlf").Many().Text(); | |
private static readonly Sprache.Parser<IEnumerable<char>> CRLF = Parse.String("\r\n"); | |
public static readonly Sprache.Parser<IRedisCommand> RedisSimpleStringParser = | |
from leadingPlus in Parse.Char('+') | |
from stringValue in NonCRLF | |
from cr in CRLF | |
select RedisSimpleString.From(stringValue); | |
public static readonly Sprache.Parser<IRedisCommand> RedisErrorParser = | |
from leadingMinus in Parse.Char('-') | |
from stringValue in NonCRLF | |
from cr in CRLF | |
select RedisError.From(stringValue); | |
public static readonly Sprache.Parser<IRedisCommand> RedisIntegerParser = | |
from leadingColon in Parse.Char(':') | |
from sign in Parse.Char('-').Optional() | |
from numberValue in Parse.Digit.Many().Text() | |
from cr in CRLF | |
select RedisInteger.From(long.Parse($"{(sign.IsDefined ? "-" : "")}{numberValue}")); | |
public static readonly Sprache.Parser<IRedisCommand> RedisBulkStringParser = | |
from leadingColon in Parse.Char('$') | |
from count in Parse.Decimal.Select(int.Parse) | |
from cr1 in CRLF | |
from stringValue in Parse.AnyChar.Repeat(count).Text() // THIS IS WRONG | |
from cr2 in CRLF | |
select RedisBulkString.From(Encoding.UTF8.GetBytes(stringValue)); // THIS IS WRONG, TOO | |
public static readonly Sprache.Parser<IRedisCommand> RedisParser = | |
RedisSimpleStringParser | |
.Or(RedisErrorParser) | |
.Or(RedisIntegerParser) | |
.Or(RedisBulkStringParser); | |
} | |
public static class PidginParser | |
{ | |
public static IRedisCommand ParsePidgin(this string input) => RedisParser.Parse(input) switch { | |
var x when x.Success => x.Value, | |
_ => null | |
}; | |
private static Parser<char, Unit> RedisCommandPrefix(char prefix) => Char(prefix).IgnoreResult(); | |
private static readonly Parser<char, Unit> CRLF = String("\r\n").IgnoreResult(); | |
private static readonly Parser<char, string> NonCRLF = Token(c => c != '\r' && c != '\n').ManyString(); | |
private static readonly Parser<char, IRedisCommand> RedisSimpleStringParser = NonCRLF.Between(RedisCommandPrefix('+'), CRLF).Select(RedisSimpleString.From); | |
private static readonly Parser<char, IRedisCommand> RedisErrorParser = NonCRLF.Between(RedisCommandPrefix('-'), CRLF).Select(RedisError.From); | |
private static readonly Parser<char, IRedisCommand> RedisIntegerParser = LongNum.Between(RedisCommandPrefix(':'), CRLF).Select(RedisInteger.From); | |
internal static readonly Parser<char, long> RedisBulkStringLengthParser = RedisCommandPrefix('$').Then(LongNum.Before(CRLF)); | |
// | |
// TODO Need to find a way to read the string length in one parsing step and read the data with proper length in the next step. | |
// | |
//private static readonly Parser<char, (long, string)> RedisBulkStringParser = | |
// RedisBulkStringPrefixParser.Then(LongNum.Before(CRLF)).Before(l => Any.RepeatString(l.)) | |
// Map((len, val) => (len, val), LongNum.Before(CRLF), Any.RepeatString(20)).Between(Char('$'), CRLF); | |
// Map((l, s) => (l, s), LongNum.Then(CRLF), Any.RepeatString(20)) | |
// .Between(Char('$'), CRLF); | |
// .Then(Char('s').Repeat(4)).Select(RedisInteger.From); | |
private static readonly Parser<char, IRedisCommand> RedisParser = Parser.OneOf<char, IRedisCommand>(RedisSimpleStringParser, RedisErrorParser, RedisIntegerParser); | |
} | |
public interface IRedisCommand { } | |
public class RedisSimpleString : IRedisCommand | |
{ | |
public static IRedisCommand From(string value) => new RedisSimpleString { Value = value }; | |
public string Value { get; private set; } | |
public override string ToString() => Value; | |
} | |
public class RedisBulkString : IRedisCommand | |
{ | |
public static IRedisCommand From(byte[] value) => new RedisBulkString { Value = value }; | |
public byte[] Value { get; private set; } | |
public override string ToString() => Convert.ToBase64String(this.Value); | |
} | |
public class RedisError : IRedisCommand | |
{ | |
public static IRedisCommand From(string value) => new RedisError { Value = value }; | |
public string Value { get; private set; } | |
public override string ToString() => $"Error: {Value}"; | |
} | |
public class RedisInteger : IRedisCommand | |
{ | |
public static IRedisCommand From(long value) => new RedisInteger { Value = value }; | |
public long Value { get; private set; } | |
public override string ToString() => $"integer {Value}"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment