Skip to content

Instantly share code, notes, and snippets.

@LukaHorvat
Created July 5, 2018 08:06
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 LukaHorvat/36cb6d504bd4198c4c8f8c86e011ae40 to your computer and use it in GitHub Desktop.
Save LukaHorvat/36cb6d504bd4198c4c8f8c86e011ae40 to your computer and use it in GitHub Desktop.
"Parser combinators" in C#
class FailedParse : Exception {}
class Parser
{
string text;
int state;
private Parser(string text)
{
this.text = text;
}
public char AnyChar()
{
if (state < text.Length)
{
state++;
return text[state - 1];
}
throw new FailedParse();
}
public char Satisfies(Func<char, bool> pred)
{
var c = AnyChar();
if (pred(c)) return c;
else
{
state--;
throw new FailedParse();
}
}
public T Alternate<T>(Func<T> left, Func<T> right)
{
int oldState = state;
try
{
return left.Invoke();
}
catch (FailedParse)
{
if (oldState == state) return right();
else throw;
}
}
public T Alternate<T>(params Func<T>[] alts)
{
Func<T> acc = () => throw new FailedParse();
foreach (var alt in alts.Reverse())
{
acc = () => Alternate(alt, acc);
}
return acc();
}
public T Try<T>(Func<T> pars)
{
int oldState = state;
try
{
return pars();
}
catch (FailedParse)
{
state = oldState;
throw;
}
}
public bool Optional<T>(Func<T> pars, out T res)
{
int oldState = state;
try {
res = pars();
return true;
}
catch (FailedParse)
{
if (state == oldState)
{
res = default(T);
return false;
}
throw;
}
}
public Stack<T> Some<T>(Func<T> pars)
{
var fst = pars();
var rest = Many(pars);
rest.Push(fst);
return rest;
}
public Stack<T> Many<T>(Func<T> pars)
{
return Alternate<Stack<T>>(() => Some(pars), () => new Stack<T>());
}
public static T Parse<T>(string text, Func<Parser, T> parser) => parser(new Parser(text));
}
static class ParserExt
{
public static char Digit(this Parser pars) => pars.Satisfies(System.Char.IsDigit);
public static char Char(this Parser pars, char tgt) => pars.Satisfies(c => c == tgt);
public static void Whitespace(this Parser pars) => pars.Many(() => pars.Char(' '));
public static int Expression(this Parser pars)
{
return pars.Alternate(
() => pars.Try(() => {
var prod = pars.Product();
pars.Char('+');
pars.Whitespace();
var rest = pars.Expression();
return prod + rest;
}),
pars.Product
);
}
public static int Product(this Parser pars)
{
return pars.Alternate(
() => pars.Try(() => {
var term = pars.Term();
pars.Char('*');
pars.Whitespace();
var rest = pars.Product();
return term * rest;
}),
pars.Term
);
}
public static int Term(this Parser pars)
{
return pars.Alternate(
() => {
var res = int.Parse(new string(pars.Some(pars.Digit).ToArray()));
pars.Whitespace();
return res;
},
() => {
pars.Char('(');
pars.Whitespace();
var s = pars.Expression();
pars.Char(')');
pars.Whitespace();
return s;
}
);
}
}
void Main()
{
var digs = Parser.Parse("(12+ 2) * 13", ParserExt.Expression);
Console.WriteLine(digs);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment