|
public static class Combinators |
|
{ |
|
/// Non-deterministic choice -- The (++) operator |
|
/// is a (non-deterministic) choice operator for parsers. The parser p ++ q applies |
|
/// both parsers p and q to the argument string, and appends their list of results. |
|
public static Parser<TInput, TOutput> Opp<TInput, TOutput>(Parser<TInput, TOutput> p, Parser<TInput, TOutput> q) |
|
{ |
|
Operation<TInput, TOutput> bound = cs => |
|
{ |
|
return p.Op(cs).Concat(q.Op(cs)).ToList(); |
|
}; |
|
return bound.ToParser(); |
|
} |
|
|
|
/// a deterministic)choice operator (+++) that has |
|
/// the same behaviour as (++), except that at most one result is returned: |
|
public static Parser<TInput, TOutput> Oppp<TInput, TOutput>(Parser<TInput, TOutput> p, Parser<TInput, TOutput> q) |
|
{ |
|
Operation<TInput, TOutput> bound = cs => |
|
{ |
|
var sum = Opp(p, q).Op(cs); |
|
if (sum.Any()) |
|
{ |
|
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>> { sum.First() }; |
|
} |
|
|
|
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>(); |
|
}; |
|
|
|
return bound.ToParser(); |
|
} |
|
|
|
/// Matches 1 or more applications of parser p |
|
/// f# prototype is parser['a, 'b] -> parser['a list, 'b] |
|
public static Parser<TInput, List<TOutput>> Many1<TInput, TOutput>(Parser<TInput, TOutput> p) |
|
{ |
|
var parser = p.Bind(x => |
|
{ |
|
var manyp = Many(p); |
|
var outer = manyp.Bind(xs => |
|
{ |
|
var tmp = new List<TOutput> { x }; |
|
var result = tmp.Concat(xs).ToList(); |
|
return ParserMonad.Return<TInput, List<TOutput>>(result); |
|
}); |
|
return outer; |
|
}); |
|
|
|
return parser; |
|
} |
|
|
|
/// Matches 0 or more applications of parser p |
|
/// f# prototype is parser['a, 'b] -> parser['a list, 'b] |
|
public static Parser<TInput, List<TOutput>> Many<TInput, TOutput>(Parser<TInput, TOutput> p) |
|
{ |
|
var arg1 = Many1(p); |
|
var arg2 = ParserMonad.Return<TInput, List<TOutput>>(new List<TOutput>()); |
|
return Combinators.Oppp(arg1, arg2); |
|
} |
|
|
|
/// Apply a parser |
|
public static Parser<TInput, TOutput> Apply<TInput, TOutput>(Parser<TInput, TOutput> p) |
|
{ |
|
var parser = p.Bind(x => ParserMonad.Return<TInput, TOutput>(x)); |
|
return parser; |
|
} |
|
|
|
/// A parser which successfully consumes the first element |
|
/// of the input stream if it is is non-empty, and fails otherwise. |
|
/// f# prototype is (unit ->) parser[char, string] |
|
public static Parser<TInput, TOutput> Item<TInput, TOutput>(Func<TInput, TOutput> select) |
|
{ |
|
Operation<TInput, TOutput> bound = |
|
cs => |
|
{ |
|
if (cs.Any()) |
|
{ |
|
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>> |
|
{ |
|
new KeyValuePair<TOutput, IEnumerable<TInput>>(select(cs.First()), cs.Skip(1).ToList()) |
|
}; |
|
} |
|
|
|
return new List<KeyValuePair<TOutput, IEnumerable<TInput>>>(); |
|
}; |
|
|
|
return bound.ToParser(); |
|
} |
|
|
|
/// A parser that consumes a single token if it matches the predicate |
|
/// or fails otherwise |
|
/// f# prototype is (char->bool) -> parser[char, string] |
|
public static Parser<TInput, TOutput> Sat<TInput, TOutput>(Predicate<TInput> p, Func<TInput, TInput, TOutput> select) |
|
{ |
|
var parser = Item<TInput, TInput>(x => x).SelectMany(c => |
|
{ |
|
if (p(c)) |
|
{ |
|
return ParserMonad.Return<TInput, TInput>(c); |
|
} |
|
else |
|
{ |
|
return ParserMonad.Zero<TInput, TInput>(); |
|
} |
|
}, select); |
|
|
|
return parser; |
|
} |
|
|
|
/// A parser for specific characters |
|
/// f# prototype is char -> parser[char, string] |
|
public static Parser<char, TOutput> Char<TOutput>(char c, Func<char, char, TOutput> select) |
|
{ |
|
return Sat(s => s == c, select); |
|
} |
|
|
|
/// Parse a specific string |
|
/// f# prototype is string -> parser[char list, string] |
|
public static Parser<char, List<TOutput>> StringP<TOutput>(string input, Func<char, char, TOutput> select) |
|
{ |
|
if (input.Any()) |
|
{ |
|
var parser = Char<TOutput>(input.ToCharArray()[0], select).Bind(c => |
|
{ |
|
var p2 = StringP(input.Substring(1), select); |
|
var inner = p2.Bind(cs => |
|
{ |
|
var tmp = new List<TOutput> { c }; |
|
var result = tmp.Concat(cs).ToList(); |
|
return ParserMonad.Return<char, List<TOutput>>(result); |
|
}); |
|
return inner; |
|
}); |
|
|
|
return parser; |
|
} |
|
else |
|
{ |
|
return ParserMonad.Return<char, List<TOutput>>(new List<TOutput>()); |
|
} |
|
} |
|
|
|
/// Parse a string of spaces. |
|
/// f# prototype is (unit ->) parser[char list, string] |
|
public static Parser<char, List<TOutput>> Space<TOutput>(Func<char, char, TOutput> select) |
|
{ |
|
return Many(Sat(System.Char.IsWhiteSpace, select)); |
|
} |
|
|
|
/// Parse a token using a parser p, throwing away any trailing space. |
|
/// f# prototype is parser['a, string] -> parser['a, string] |
|
public static Parser<char, TOutput> Token<TOutput>(Parser<char, TOutput> p, Func<char, char, TOutput> select) |
|
{ |
|
var parser = p.Bind(x => |
|
Space(select).Bind(xs => |
|
{ |
|
return ParserMonad.Return<char, TOutput>(x); |
|
} |
|
)); |
|
return parser; |
|
} |
|
|
|
/// Parse a symbolic token: |
|
/// f# prototype is string -> parser[char list, string] |
|
public static Parser<char, List<TOutput>> Symbol<TOutput>(string symbol, Func<char, char, TOutput> select) |
|
{ |
|
Parser<char, List<TOutput>> inner = StringP<TOutput>(symbol, select); |
|
return Token<List<TOutput>>(inner, (x, y) => new List<TOutput> { select(x,y) } ); |
|
} |
|
|
|
/// Parse repeated applications of a parser p, separated by applications of a parser |
|
/// op whose result value is an operator that is assumed to associate to the left, |
|
/// and which is used to combine the results from the p parsers. |
|
public static Parser<TInput, TOutput> Chain1<TInput, TOutput> ( |
|
Parser<TInput, TOutput> p, |
|
Parser<TInput, Func<TOutput, TOutput, TOutput>> op) |
|
{ |
|
return p.Bind(x => Chain1Rest(x, p, op)); |
|
} |
|
|
|
/// Helper method for Chain1 |
|
public static Parser<TInput, TOutput> Chain1Rest<TInput, TOutput> ( |
|
TOutput a, |
|
Parser<TInput, TOutput> p, |
|
Parser<TInput, Func<TOutput, TOutput, TOutput>> op) |
|
{ |
|
var parser = op.Bind(f => |
|
p.Bind(b => |
|
Chain1Rest(f(a, b), p, op) |
|
)); |
|
var stop = ParserMonad.Return<TInput, TOutput>(a); |
|
|
|
return Oppp(parser, stop); |
|
} |
|
|
|
//F# Monadic Parser - Calculator Example :))) |
|
|
|
// Grammar |
|
//expr ::= expr addop term | term |
|
//term ::= term mulop factor | factor |
|
//factor ::= digit | ( expr ) |
|
//digit ::= 0 | 1 | : : : | 9 |
|
//addop ::= + | - |
|
//mulop ::= * | / |
|
|
|
public static Parser<char, Func<double, double, double>> AddOp() |
|
{ |
|
var plus = Symbol<char>("+", (x, y) => x).Bind(x => |
|
ParserMonad.Return<char, Func<double, double, double>>( |
|
(a, b) => a + b)); |
|
var minus = Symbol<char>("-", (x, y) => x).Bind(x => |
|
ParserMonad.Return<char, Func<double, double, double>>( |
|
(a, b) => a - b)); |
|
return Oppp(plus, minus); |
|
} |
|
|
|
public static Parser<char, Func<double, double, double>> MulOp() |
|
{ |
|
var plus = Symbol<char>("*", (x, y) => x).Bind(x => |
|
ParserMonad.Return<char, Func<double, double, double>>( |
|
(a, b) => a * b)); |
|
var minus = Symbol<char>("/", (x, y) => x).Bind(x => |
|
ParserMonad.Return<char, Func<double, double, double>>( |
|
(a, b) => a / b)); |
|
return Oppp(plus, minus); |
|
} |
|
|
|
public static Parser<char, double> Digit() |
|
{ |
|
var sat = Sat<char, char>(System.Char.IsDigit, (x, y) => x); |
|
return Token<char> (sat, (x,y) => x).Bind<char, char, double> |
|
(x => ParserMonad.Return<char, double>((double)(x - '0'))); |
|
} |
|
|
|
public static Parser<char, double> Expr() |
|
{ |
|
return Chain1<char, double>(Term(), AddOp()); |
|
} |
|
|
|
public static Parser<char, double> Term() |
|
{ |
|
return Chain1<char, double>(Factor(), MulOp()); |
|
} |
|
|
|
public static Parser<char, double> Factor() |
|
{ |
|
var paren = Symbol("(", (x, y) => x). |
|
Bind(x => Expr(). |
|
Bind(n => Symbol(")", (x2, y2) => x2). |
|
Bind(z => ParserMonad.Return<char, double>(n)))); |
|
return Oppp(Digit(), paren); |
|
} |
|
} |