Skip to content

Instantly share code, notes, and snippets.

@mrange
Created June 9, 2021 06:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrange/78ef8c6f8539b7500bf4bf89467b5c56 to your computer and use it in GitHub Desktop.
Save mrange/78ef8c6f8539b7500bf4bf89467b5c56 to your computer and use it in GitHub Desktop.
Parser Combinator C#
namespace CsParserCombinator
{
using System;
using static CsParserCombinator.Parsers;
class Program
{
const string Input = @"
x = 3
y=4
xyz=620
";
static void Main(string[] args)
{
var ident = Letter.Many(atLeast: 1).Map(cs => new string(cs));
var line = WhiteSpaces
.Right(ident)
.Left(WhiteSpaces)
.Left(Token("="))
.Left(WhiteSpaces)
.Pair(Int)
.Left(WhiteSpaces)
;
var lines = line.Many();
var full = lines.Left(EOF);
var pr = full(Input.AsSpan());
if (!pr.HasValue)
{
Console.WriteLine("Something went wrong");
}
foreach (var (k,v) in pr.Value)
{
Console.WriteLine($"{k} = {v}");
}
}
}
}
namespace CsParserCombinator
{
using System;
using System.Collections.Generic;
using System.Globalization;
partial class Unit
{
private Unit() {}
public static readonly Unit Value = new Unit();
}
ref partial struct ParserResult<T>
{
public ParserResult(ReadOnlySpan<char> rest)
{
HasValue = false ;
Value = default ;
Rest = rest ;
}
public ParserResult(ReadOnlySpan<char> rest, T value)
{
HasValue = true ;
Value = value ;
Rest = rest ;
}
public ReadOnlySpan<char> Rest ;
public bool HasValue;
public T Value ;
}
delegate ParserResult<T> Parser<T>(ReadOnlySpan<char> input);
static partial class Parsers
{
public static ParserResult<T> Bad<T>(ReadOnlySpan<char> input) =>
new ParserResult<T>(input);
public static ParserResult<T> Good<T>(ReadOnlySpan<char> input, T v) =>
new ParserResult<T>(input, v);
public static Parser<T> Fail<T>() => input => Bad<T>(input);
public static Parser<T> Return<T>(T v) => input => Good(input, v);
public static Parser<U> Bind<T, U>(this Parser<T> t, Func<T, Parser<U>> uf) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return Bad<U>(input);
}
var u = uf(tr.Value);
return u(tr.Rest);
};
public static Parser<U> Map<T, U>(this Parser<T> t, Func<T, U> m) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return Bad<U>(input);
}
return Good(tr.Rest, m(tr.Value));
};
public static Parser<(T,U)> Pair<T, U>(this Parser<T> t, Parser<U> u) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return Bad<(T, U)>(input);
}
var ur = u(tr.Rest);
if (!ur.HasValue)
{
return Bad<(T, U)>(input);
}
return Good(ur.Rest, (tr.Value, ur.Value));
};
public static Parser<T> Left<T, U>(this Parser<T> t, Parser<U> u) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return Bad<T>(input);
}
var ur = u(tr.Rest);
if (!ur.HasValue)
{
return Bad<T>(input);
}
return Good(ur.Rest, tr.Value);
};
public static Parser<U> Right<T, U>(this Parser<T> t, Parser<U> u) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return Bad<U>(input);
}
var ur = u(tr.Rest);
if (!ur.HasValue)
{
return Bad<U>(input);
}
return Good(ur.Rest, ur.Value);
};
public static Parser<T> Or<T, U>(this Parser<T> t, Parser<T> u) =>
input =>
{
var tr = t(input);
if (!tr.HasValue)
{
return u(input);
}
return Good(tr.Rest, tr.Value);
};
public static Parser<T> Debug<T, U>(this Parser<T> t, string name) =>
input =>
{
var tr = t(input);
if (tr.HasValue)
{
Console.WriteLine($"PARSER - {name} - GOOD: {tr.Value}");
}
else
{
Console.WriteLine($"PARSER - {name} - BAD : {tr.Value}");
}
return tr;
};
public static Parser<T[]> Many<T>(this Parser<T> t, int atLeast = 0, int atMost = int.MaxValue) =>
input =>
{
var result = new List<T>(16);
var tr = t(input);
while(tr.HasValue)
{
result.Add(tr.Value);
tr = t(tr.Rest);
}
if (atLeast > result.Count)
{
return Bad<T[]>(input);
}
if (atMost < result.Count)
{
return Bad<T[]>(input);
}
return Good(tr.Rest, result.ToArray());
};
public static Parser<Unit> SkipMany<T>(this Parser<T> t, int atLeast = 0, int atMost = int.MaxValue) =>
input =>
{
var result = 0;
var tr = t(input);
while(tr.HasValue)
{
++result;
tr = t(tr.Rest);
}
if (atLeast > result)
{
return Bad<Unit>(input);
}
if (atMost < result)
{
return Bad<Unit>(input);
}
return Good(tr.Rest, Unit.Value);
};
public static readonly Parser<char> Char =
input =>
{
if (input.Length < 1)
{
return Bad<char>(input);
}
return Good(input.Slice(1), input[0]);
};
public static readonly Parser<Unit> EOF =
input =>
{
if (input.Length > 0)
{
return Bad<Unit>(input);
}
return Good(input, Unit.Value);
};
public static Parser<char> Satisfy(Func<char, bool> s) =>
input =>
{
if (input.Length < 1)
{
return Bad<char>(input);
}
var ch = input[0];
if (!s(ch))
{
return Bad<char>(input);
}
return Good(input.Slice(1), ch);
};
public static readonly Parser<char> Digit = Satisfy(char.IsDigit);
public static readonly Parser<char> Letter = Satisfy(char.IsLetter);
public static readonly Parser<char> LetterOrDigit = Satisfy(char.IsLetterOrDigit);
public static readonly Parser<char> WhiteSpace = Satisfy(char.IsWhiteSpace);
public static readonly Parser<Unit> WhiteSpaces = WhiteSpace.SkipMany();
public static readonly Parser<Unit> WhiteSpaces1 = WhiteSpace.SkipMany(atLeast: 1);
public static readonly Parser<int> Int = Digit
.Many(atLeast: 1)
.Map(cs => int.Parse(cs.AsSpan(), NumberStyles.Integer, CultureInfo.InvariantCulture))
;
public static Parser<Unit> Token(string token) =>
input =>
{
if (input.Length < token.Length)
{
return Bad<Unit>(input);
}
if (!input.Slice(0, token.Length).Equals(token.AsSpan(), StringComparison.Ordinal))
{
return Bad<Unit>(input);
}
return Good(input.Slice(token.Length), Unit.Value);
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment