Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active July 1, 2018 06:50
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/164aa798178c8ba969d9605967ed7758 to your computer and use it in GitHub Desktop.
Save mrange/164aa798178c8ba969d9605967ed7758 to your computer and use it in GitHub Desktop.
CsParser
namespace CsParsec
{
using System;
using System.Collections.Generic;
using System.Text;
public class Unit
{
private Unit()
{
}
public static Unit Value = new Unit();
}
public class ParserContext
{
readonly int m_position ;
readonly List<string> m_expected = new List<string>(16);
readonly List<string> m_unexpected = new List<string>(16);
public ParserContext(int position)
{
m_position = position;
}
public void Expected(string expected, int position)
{
if (m_position == position)
{
m_expected.Add(expected);
}
}
public void Unexpected(string unexpected, int position)
{
if (m_position == position)
{
m_expected.Add(unexpected);
}
}
public void Unexpected_EOS(int position)
{
Unexpected("EOS", position);
}
public string AggregatedErrorMessage
{
get
{
var sb = new StringBuilder(16);
sb.AppendLine($"Parse failure @{m_position}");
if (m_expected.Count > 0)
{
sb.AppendLine("Expected");
for (var iter = 0; iter < m_expected.Count; ++iter)
{
sb.Append(" ").AppendLine(m_expected[iter]);
}
}
if (m_unexpected.Count > 0)
{
sb.AppendLine("Unexpected");
for (var iter = 0; iter < m_unexpected.Count; ++iter)
{
sb.Append(" ").AppendLine(m_unexpected[iter]);
}
}
return sb.ToString();
}
}
}
public struct ParserResult<T>
{
bool m_hasValue ;
T m_value ;
int m_position ;
public bool HasValue => m_hasValue;
public T Value => m_hasValue ? m_value : throw new Exception("ParserResult has no value");
public int Position => m_position;
public ParserResult(T value, int position)
{
m_hasValue = true ;
m_value = value ;
m_position = position ;
}
public ParserResult(int position)
{
m_hasValue = false ;
m_value = default ;
m_position = position;
}
}
public delegate ParserResult<T> Parser<T>(ParserContext context, string input, int position);
public static class Parser
{
public static Parser<T> Success<T>(T v) =>
(context, input, position) =>
new ParserResult<T>(v, position);
public static Parser<T> Failure<T>(T v) =>
(context, input, position) =>
new ParserResult<T>(position);
public static Parser<U> Bind<T, U>(this Parser<T> t, Func<T, Parser<U>> uf) =>
(context, input, position) =>
{
var tr = t(context, input, position);
if (tr.HasValue)
{
var u = uf(tr.Value);
return u(context, input, tr.Position);
}
else
{
return new ParserResult<U>(tr.Position);
}
};
public static Parser<U> Map<T, U>(this Parser<T> t, Func<T, U> m) =>
(context, input, position) =>
{
var tr = t(context, input, position);
if (tr.HasValue)
{
return new ParserResult<U>(m(tr.Value), tr.Position);
}
else
{
return new ParserResult<U>(tr.Position);
}
};
public static Parser<T> Debug<T>(this Parser<T> t, string name) =>
(context, input, position) =>
{
Console.WriteLine($"DEBUG - BEFORE - {name} - {position}");
var tr = t(context, input, position);
if (tr.HasValue)
{
Console.WriteLine($"DEBUG - SUCCESS - {name} - {position} - {tr.Value}");
}
else
{
Console.WriteLine($"DEBUG - FAILURE - {name} - {position}");
}
return tr;
};
public static Parser<(T Left, U Right)> AndAlso<T, U>(this Parser<T> l, Parser<U> r) =>
(context, input, position) =>
{
var lr = l(context, input, position);
if (lr.HasValue)
{
var rr = r(context, input, lr.Position);
if (lr.HasValue && rr.HasValue)
{
return new ParserResult<(T Left, U Right)>((lr.Value, rr.Value), rr.Position);
}
else
{
return new ParserResult<(T Left, U Right)>(rr.Position);
}
}
else
{
return new ParserResult<(T Left, U Right)>(lr.Position);
}
};
public static Parser<T> KeepLeft<T, U>(this Parser<T> l, Parser<U> r) =>
(context, input, position) =>
{
var lr = l(context, input, position);
if (lr.HasValue)
{
var rr = r(context, input, lr.Position);
if (lr.HasValue && rr.HasValue)
{
return new ParserResult<T>(lr.Value, rr.Position);
}
else
{
return new ParserResult<T>(rr.Position);
}
}
else
{
return new ParserResult<T>(lr.Position);
}
};
public static Parser<U> KeepRight<T, U>(this Parser<T> l, Parser<U> r) =>
(context, input, position) =>
{
var lr = l(context, input, position);
if (lr.HasValue)
{
var rr = r(context, input, lr.Position);
if (lr.HasValue && rr.HasValue)
{
return new ParserResult<U>(rr.Value, rr.Position);
}
else
{
return new ParserResult<U>(rr.Position);
}
}
else
{
return new ParserResult<U>(lr.Position);
}
};
public static Parser<T> OrElse<T>(this Parser<T> l, Parser<T> r) =>
(context, input, position) =>
{
var lr = l(context, input, position);
if (lr.HasValue)
{
return lr; // TODO: Run r as well to collect errors?
}
else
{
return r(context, input, position);
}
};
public static Parser<T[]> Many<T>(this Parser<T> t, int minMatch = 0, int maxMatch = Int32.MaxValue) =>
(context, input, position) =>
{
var r = new List<T>(16);
var current = position;
for (var i = 0; i < maxMatch; ++i)
{
var tr = t(context, input, current);
if (tr.HasValue)
{
r.Add(tr.Value);
}
else if (i < minMatch)
{
return new ParserResult<T []>(tr.Position);
}
else
{
return new ParserResult<T[]>(r.ToArray(), tr.Position);
}
current = tr.Position;
}
return new ParserResult<T[]>(r.ToArray(), current);
};
public static Parser<char> Satisfy(string label, Func<char, bool> s) =>
(context, input, position) =>
{
if (position < input.Length)
{
var c = input[position];
if (s(c))
{
return new ParserResult<char>(c, position + 1);
}
else
{
context.Expected(label, position);
return new ParserResult<char>(position);
}
}
else
{
context.Unexpected_EOS(input.Length);
return new ParserResult<char>(input.Length);
}
};
public static U Parse<T, U>(
this Parser<T> t
, string input
, Func<T, U> onSuccess
, Func<string, U> onFailure
)
{
var context = new ParserContext(-1);
var tr = t(context, input, 0);
if (tr.HasValue)
{
return onSuccess(tr.Value);
}
else
{
var errorContext = new ParserContext(tr.Position);
var etr = t(errorContext, input, 0);
return onFailure(errorContext.AggregatedErrorMessage);
}
}
}
class Program
{
static void Main(string[] args)
{
var pident = Parser.Satisfy("Letter", Char.IsLetter)
.Many(1)
.Map(cs => new string(cs))
.Debug("pletter")
;
Parser<char> pchar(char c) => Parser.Satisfy($"'{c}'", cc => c == cc)
.Debug("pchar")
;
var pint = Parser.Satisfy("Digit", Char.IsDigit)
.Many(1)
.Map(cs => int.Parse(new string(cs)))
.Debug("pint")
;
var p = pident.KeepLeft(pchar('=')).AndAlso(pint);
bool Success(string msg)
{
Console.WriteLine(msg);
return true;
}
bool Failure(string msg)
{
Console.WriteLine(msg);
return false;
}
p.Parse("Hello=123", v => Success($"{v}"), m => Failure($"Failure: {m}"));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment