- Notifications
You must be signed in to change notification settings - Fork3
A thin C# wrapper for FParsec.
License
bert2/FParsec.CSharp
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
FParsec.CSharp is a C# wrapper for the F# packageFParsec. FParsec is a parser combinator library with which you can implement parsers declaratively.
While using FParsec from C# is entirely possible in theory, it is very awkward in practice. Most of FParsec's elegance is lost in translation due to C#'s inferior type inference and its lack of custom operators.
FParsec.CSharp tries to alleviate that by wrapping FParsec's operators as extension functions.
FParsec.CSharp does not try to hide any types fromFParsec
orFSharp.Core
--the wrapper is thin and also avoids name collisions. That way you can always fallback to FParsec anytime you need some functionality not implemented by FParsec.CSharp.
Based on the current implementation it should be easy to extend the wrapper yourself if needed. Pull requests are always welcome!
Note that the documentation assumes prior knowledge on FParsec or other parser combinator libraries.
Import the combinators, pre-defined parsers, and helper functions:
usingFParsec.CSharp;// extension functions (combinators & helpers)usingstaticFParsec.CSharp.PrimitivesCS;// combinator functionsusingstaticFParsec.CSharp.CharParsersCS;// pre-defined parsers
Now you can write some parsers:
varparser=AnyChar.And(Digit);varreply=parser.ParseString("a1");Debug.Assert(reply.Result==('a','1'));
An FParsec parser is a function that takes aCharStream<T>
and returns aReply<T>
. In C# such parsers are represented by the typeFSharpFunction<CharStream<TUserState>, Reply<TResult>>
and can be executed with the methodReply<TResult>> Invoke(CharStream<TUserState>)
.
FParsec.CSharp comes with extensions to make things easier for you.
You have already seen one way to execute a parser in the previous section:ParseString()
.
ParseString(string)
is just a wrapper forFSharpFunc<CharStream<Unit>, Reply<T>>.Invoke()
. It constructs theCharStream
for you from the given string.
ParseFile(string)
will do the same for a given file path.
If you need maximum control thenParse(CharStream<Unit>)
is your best option. You can configure theCharStream
yourself and inspect all the details of theReply
after parsing.
UsingInvoke()
,Parse()
, or any of its variants is generally not recommended. FParsec provides a better way to execute parsers that generates nicely formatted error messages:Run()
.
The extensionParserResult<T, Unit> Run(string)
does the same asReply<T> ParseString(string)
, but also builds an error message from theReply
's errors and the parser postion:
varresult=Many1(Digit).AndR(Upper).Run("123a");// Don't be shocked: in the next section we will learn how to improve this.varmsg=((ParserResult<char,Unit>.Failure)result).Item1;Console.WriteLine(msg);
The above will print the following detailed parsing failure message:
Error in Ln: 1 Col: 4123a ^Expecting: decimal digit or uppercase letter
Run(string)
is actually just a short form forRunOnString(string)
.
Additionally, there are three moreRun...()
functions:RunOnString(string, int, int)
(parse a substring),RunOnStream()
(parse aStream
), andRunOnFile()
(parse... well you get the idea).
FParsec'sParserResult
is an F# discriminated union, which are awkward to work with in C#. FParsec.CSharp comes with extensions methods that hide those ugly details.
The easiest way to get the parser result is to useGetResult()
:
varone=Digit.Run("1").GetResult();Debug.Assert(one=='1');
GetResult()
will throw anInvalidOperationException
in case the parser failed. The exception will contain the detailed parsing failure message.
If you need more graceful error handling you can useGetResult<T>(Func<string, T>)
which delegates error handling to the caller and provides the failure message to it:
// provide fallback valuevard1=Digit.Run("a").GetResult(_=>default);Debug.Assert(d1=='\0');// throw your own exceptionvard2=Digit.Run("a").GetResult(msg=>thrownewException($"whoops...{msg}"));// do anything as long as you return a fallback value or throwvard3=Digit.Run("a").GetResult(_=>{Console.WriteLine("oof");returnGetRandomChar();});
Additionally, the extensionsGetResultOrError<T>(Func<ParserError, T>)
andGetResultOrFailure<T>(Func<ParserResult<T, Unit>.Failure, T>)
let you inspect theParserError
orFailure
objects during error handling.
Alternatively, you can can safely unwrap aParserResult<T, Unit>
into a tuple(T? result, string? message)
usingUnwrapResult()
(which will never throw):
var(res,msg)=Digit.Run("a").UnwrapResult();Console.WriteLine(msg??$"Parser succeeded:{res}");
In case parsing succeeded the left side of the tuple will hold the parser's return value and the right side will benull
.
In case parsing failed the left side of the tuple will hold the return value type'sdefault
value and the right side will hold the detailed parser error message.
Hence the safest way to check if the parser result tuple indicates failure is to check whether the right side isnull
.
With C# 8.0 you can do that quite nicely using aswitch
expression and recursive patterns:
varresponse=Digit.Run("a").UnwrapResult()switch{(varr,null)=>$"Parser succeeded:{r}",(_,varm)=>$"Parser failed:{m}"};
UnwrapWithError()
andUnwrapWithFailure()
work the same way, but return theParserError
orParserResult<TResult, Unit>.Failure
instance in the right side of the tuple.
FParsec.CSharp extends the types involved with parser results with deconstructors so you can make use of C# 8.0's recursive patterns insideswitch
statements/expressions:
varresponse=Digit.Run("1")switch{ParserResult<char,Unit>.Success(varc,_,_)=>$"Parsed '{c}'.",ParserResult<char,Unit>.Failure(_,(_,(_,_,varcol,_)),_)=>$"Some error at column{col}."};
varresponse=Digit.ParseString("a")switch{(ReplyStatus.Ok,varc,_)=>$"Parsed '{c}'.",(ReplyStatus.Error,_,(ErrorMessage.Expectederr,_))=>$"Expected a{err.Label}.", _=>"oof."};
You will need to import some of FParsec's namespaces for this to work:
using FParsec; // contains `ReplyStatus` and `ErrorMessage`using static FParsec.CharParsers; // contains `ParserResult`
FParsec.CSharp, like FParsec, supports parsing with user state. This is reflected by the type parameterU
in the signatures:
publicFSharpFunc<CharStream<U>,Reply<(T1,T2)>>And<U,T1,T2>(thisFSharpFunc<CharStream<U>,Reply<T1>>p1,FSharpFunc<CharStream<U>,Reply<T2>>p2);
If a combinator/parser supports user state then it will always haveU
as the first type parameter.
For the combinators fromFParsec.CSharp.PrimitivesCS
this will be transparent most of the time, because C# is able to infer the user state type of the combinator from the user state type of the parser argument(s).
Unfortunately C# is not able to infer the user state type retrospectively from later bindings and hence forces you to explicitly specify the user state type on parsers that have no parser parameters. In the case of the predefined parsers fromFParsec.CSharp.CharParsersCS
(which usually don't take other parsers as arguments) this restriction would be cause for much annoyance.
That's why all parsers/combinators that have no parser parameters have two variants: one assuming a user state type ofUnit
and another one expecting the explicit type argumentU
. The names of the latter ones are always suffixed with the letter "U":
var parserWithoutUserState = Digit.And(Letter);var parserWithUserState = DigitU<int>().And(LetterU<int>());
Below are example test cases to demonstrate working with user state:
[Fact]publicvoidSimpleSet(){switch(SetUserState(12).RunOnString("",0)){caseParserResult<Unit,int>.Success(_,12,_):break;default:thrownewException();}}[Fact]publicvoidCountParsedLetters(){varcountedLetter=LetterU<int>().And(UpdateUserState<int>(cnt=>cnt+1));SkipMany(countedLetter).And(GetUserState<int>()).RunOnString("abcd",0).GetResult().ShouldBe(4);}[Fact]publicvoidCheckNestingLevel(){FSharpFunc<CharStream<int>,Reply<Unit>>expr=null;varparens=Between('(',Rec(()=>expr),')');varempty=ReturnU<int,Unit>(null);expr=Choice(parens.AndR(UpdateUserState<int>(depth=>depth+1)),empty);expr.AndR(UserStateSatisfies<int>(depth=>depth<3)).RunOnString("((()))",0).IsFailure.ShouldBeTrue();}
In case you need one of FParsec's more specialized parsers you can easily import their namespace:
usingstaticFParsec.CharParsers;
In the example below we are usingFParsec.CharParsers.many1Chars2()
. As you can see it integrates seemlessly with FParsec.CSharp:
varfirst=Letter.Or(CharP('_'));varrest=Letter.Or(CharP('_')).Or(Digit);varidentifier=many1Chars2(first,rest);varp=identifier.And(Skip('=')).And(Int);varr=p.ParseString("my_1st_var=13");System.Diagnostics.Debug.Assert(r.Result==("my_1st_var",13));
Some of FParsec's parsers take anonymous functions. But since they expect curriedFSharpFunc
s they won't accept C# lambdas. FParsec.CSharp comes with a little helper to createFSharpFunc
s fromFunc
objects:
// convert lambda with factory methodvarfsfunc1=FSharpFunc.From<char,bool>(c=>c=='x'||c=='y');// convert Func object with extension methodFunc<char,bool>func= c=>c=='1'||c=='2';varfsfunc2=func.ToFSharpFunc();varp=manySatisfy<Unit>(fsfunc1).And(manySatisfy<Unit>(fsfunc2));varr=p.ParseString("xyxyyy212221212");
You can find lots of examples in thetest project. Below are some parser definitions from there.
FSharpFunc<CharStream<Unit>,Reply<JToken>>jvalue=null;varjnull=StringCI("null",(JToken)null).Lbl("null");varjnum=Float.Map(i=>(JToken)i).Lbl("number");varjbool=StringCI("true").Or(StringCI("false")).Map(b=>(JToken)bool.Parse(b)).Lbl("bool");varquotedString=Between('"',ManyChars(NoneOf("\"")),'"');varjstring=quotedString.Map(s=>(JToken)s).Lbl("string");vararrItems=Many(Rec(()=>jvalue),sep:CharP(',').And(WS));varjarray=Between(CharP('[').And(WS),arrItems,CharP(']')).Map(elems=>(JToken)newJArray(elems)).Lbl("array");varjidentifier=quotedString.Lbl("identifier");varjprop=jidentifier.And(WS).And(Skip(':')).And(WS).And(Rec(()=>jvalue)).Map((name,value)=>newJProperty(name,value));varobjProps=Many(jprop,sep:CharP(',').And(WS));varjobject=Between(CharP('{').And(WS),objProps,CharP('}')).Map(props=>(JToken)newJObject(props)).Lbl("object");jvalue=Choice(jnum,jbool,jnull,jstring,jarray,jobject).And(WS);varsimpleJsonParser=WS.And(jobject).And(WS).And(EOF).Map(o=>(JObject)o);
varnameStart=Choice(Letter,CharP('_'));varnameChar=Choice(Letter,Digit,AnyOf("-_."));varname=Many1Chars(nameStart,nameChar).And(WS);varquotedString=Between('"',ManyChars(NoneOf("\"")),'"');varattribute=name.And(Skip('=')).And(WS).And(quotedString).And(WS).Lbl_("attribute").Map((attrName,attrVal)=>newXAttribute(attrName,attrVal));varattributes=Many(attribute);FSharpFunc<CharStream<Unit>,Reply<XElement>>element=null;varelementStart=Skip('<').AndTry(name.Lbl("tag name")).And(attributes);FSharpFunc<CharStream<Unit>,Reply<string>>closingTag(stringtagName)=>Between("</",StringP(tagName).And(WS),">").Lbl_($"closing tag '</{tagName}>'");FSharpFunc<CharStream<Unit>,Reply<object>>textContent(stringleadingWS)=>NotEmpty(ManyChars(NoneOf("<")).Map(text=>leadingWS+text).Map(x=>(object)x).Lbl_("text content"));varchildElement=Rec(()=>element).Map(x=>(object)x).Lbl_("child element");objectEmptyContentToEmptyString(FSharpList<object>xs)=>xs.IsEmpty?(object)"":xs;varelementContent=Many(WS.WithSkipped().AndTry(ws=>Choice(textContent(ws),childElement))).Map(EmptyContentToEmptyString);FSharpFunc<CharStream<Unit>,Reply<XElement>>elementEnd(stringelName,FSharpList<XAttribute>elAttrs)=>Choice(Skip("/>").Return((object)null),Skip(">").And(elementContent).And(WS).AndL(closingTag(elName))).Map(elContent=>newXElement(elName,elContent,elAttrs));element=elementStart.And(elementEnd);varsimpleXmlParser=WS.And(element).And(WS).And(EOF);
varglobParser=Many(Choice(Skip('?').Map(NFA.MakeAnyChar),Skip('*').Map(NFA.MakeAnyChar).Map(NFA.MakeZeroOrMore),Between('[',AnyChar.And(Skip('-')).And(AnyChar),']').Map(NFA.MakeCharRange),Skip('\\').And(AnyOf(@"?*[]\")).Map(NFA.MakeChar),AnyChar.Map(NFA.MakeChar))).And(EOF).Map(NFA.Concat).Map(proto=>proto(newFinal()));
This example contructs a non-deterministic finite automaton (NFA) during parsing and can be used for matching:
[Fact]publicvoidCanParseAndMatchGlobPattern()=>globParser.ParseString("The * syntax is easy?").Result.Matches("The glob syntax is easy!").ShouldBe(true);
FParsec.CSharp comes with a builder to constructFParsec.OperatorPrecedenceParser
s:
varbasicExprParser=newOPPBuilder<Unit,int,Unit>().WithOperators(ops=>ops.AddInfix("+",1,(x,y)=>x+y).AddInfix("*",2,(x,y)=>x*y)).WithTerms(Natural).Build().ExpressionParser;varrecursiveExprParser=newOPPBuilder<Unit,int,Unit>().WithOperators(ops=>ops.AddInfix("+",1,(x,y)=>x+y).AddInfix("*",2,(x,y)=>x*y)).WithTerms(term=>Choice(Natural,Between('(',term,')'))).Build().ExpressionParser;
It also supports implicit operators:
varexprParser=WS.And(newOPPBuilder<Unit,int,Unit>().WithOperators(ops=>ops.AddInfix("+",10,WS,(x,y)=>x+y).AddInfix("-",10,WS,(x,y)=>x-y).AddInfix("*",20,WS,(x,y)=>x*y).AddInfix("/",20,WS,(x,y)=>x/y).AddPrefix("-",20, x=>-x).AddInfix("^",30,Associativity.Right,WS,(x,y)=>(int)Math.Pow(x,y)).AddPostfix("!",40,Factorial)).WithImplicitOperator(20,(x,y)=>x*y).WithTerms(term=>Choice(Natural.And(WS),Between(CharP('(').And(WS),term,CharP(')').And(WS)))).Build().ExpressionParser);
Armed with theOPPBuilder
and the NFA implementation used for the glob parser above we can even build a simple regex parser & matcher:
varsimpleRegexParser=Many(newOPPBuilder<Unit,NFA.ProtoState,Unit>().WithImplicitOperator(2,NFA.Connect).WithOperators(ops=>ops.AddPostfix("*",3,NFA.MakeZeroOrMore).AddPostfix("+",3,NFA.MakeOneOrMore).AddPostfix("?",3,NFA.MakeZeroOrOne).AddInfix("|",1,NFA.MakeAlternation)).WithTerms(matchExpr=>{vargroup=Between('(',Many(matchExpr),')');varwildcard=Skip('.');varcharMatch=NoneOf("*+?|()");returnChoice(group.Map(NFA.Concat),wildcard.Map(NFA.MakeAnyChar),charMatch.Map(NFA.MakeChar));}).Build().ExpressionParser).And(EOF).Map(NFA.Concat).Map(proto=>proto(newFinal()));
[Fact]publicvoidCanParseAndMatchRegex()=>simpleRegexParser.ParseString("The( simple)? .+ syntax is .*more tricky( and (complex|difficult|involved))+.").Result.Matches("The simple regex syntax is only a little bit more tricky and complex and involved!").ShouldBe(true);
This example implements a simple functional script language. It only knows one type (int
) and is super inefficient, but it has lots of functional fu (e.g. lazy evaluation, partial application, lambdas, higher order functions, and function composition).
varnumber=Natural.Lbl("number");staticStringParsernotReserved(stringid)=>id=="let"||id=="in"||id=="match"?Zero<string>():Return(id);varidentifier1=Choice(Letter,CharP('_'));varidentifierRest=Choice(Letter,CharP('_'),CharP('\''),Digit);varidentifier=Purify(Many1Chars(identifier1,identifierRest)).AndTry(notReserved).Lbl("identifier");varparameters=Many(identifier,sep:WS1,canEndWithSep:true).Lbl("parameter list");ScriptParser?expression=null;varletBinding=Skip("let").AndR(WS1).And(identifier).And(WS).And(parameters).And(Skip('=')).And(WS).And(Rec(()=>expression).Lbl("'let' definition expression")).And(Skip("in")).And(WS1).And(Rec(()=>expression).Lbl("'let' body expression")).Map(Flat).Lbl("'let' binding");varlambda=Skip('\\').And(parameters).And(Skip("->")).And(WS).And(Rec(()=>expression).Lbl("lambda body")).Lbl("lambda");vardefaultCase=Skip('_').AndRTry(NotFollowedBy(identifierRest)).AndR(WS).Return(ScriptB.AlwaysMatches);varcaseValueExpr=Rec(()=>expression).Map(ScriptB.Matches);varcaseExpr=Skip('|').AndR(WS).And(defaultCase.Or(caseValueExpr).Lbl("case value expression")).And(Skip("=>")).And(WS).And(Rec(()=>expression).Lbl("case result expression")).Lbl("match case");varmatchExpr=Skip("match").AndR(WS1).And(Rec(()=>expression).Lbl("match value expression")).And(Many1(caseExpr)).Lbl("'match' expression");expression=newOPPBuilder<Unit,Script,Unit>().WithOperators(ops=>ops.AddInfix("+",10,WS,ScriptB.Lift2((x,y)=>x+y)).AddInfix("-",10,WS,ScriptB.Lift2((x,y)=>x-y)).AddInfix("*",20,WS,ScriptB.Lift2((x,y)=>x*y)).AddInfix("/",20,WS,ScriptB.Lift2((x,y)=>x/y)).AddInfix("%",20,WS,ScriptB.Lift2((x,y)=>x%y)).AddPrefix("-",20,ScriptB.Lift(x=>-x)).AddInfix(".",30,Associativity.Right,WS,ScriptB.Compose)).WithImplicitOperator(50,ScriptB.Apply).WithTerms(term=>Choice(letBinding.Map(ScriptB.BindVar),matchExpr.Map(ScriptB.Match),Between(CharP('(').And(WS),term,CharP(')').And(WS)),number.And(WS).Map(ScriptB.Return),identifier.And(WS).Map(ScriptB.Resolve),lambda.Map(ScriptB.Lambda)).Lbl("expression")).Build().ExpressionParser;varscriptParser=WS.And(expression).And(EOF);
This parser builds a function that can be invoked (with an empty arguments list and an empty "runtime environment") to execute the script:
[Fact]publicvoidFibonacciNumber()=>scriptParser.Run(@" let fib n = match n | 0 => 0 | 1 => 1 | _ => fib (n-1) + fib (n-2) in fib 7").GetResult().Invoke(FSharpList<Script>.Empty,newDictionary<string,Script>()).ShouldBe(13);
When you need to debug into your parser chain, use theDebug()
combinator on any of your chain's parsers.
It takes twoAction
s:
Action<CharStream<Unit>> before
: is run before the parser is applied,Action<CharStream<Unit>, Reply<T>> after
: is run after the parser was applied.
For instance, you can use emptyAction
s and place break points inside them:
varp=Digit.Debug(cs=>{},(cs,r)=>{}).And(Letter.Debug(cs=>{},(cs,r)=>{})).Debug(cs=>{},(cs,r)=>{});
The signatures of FParsec.CSharp's combinators can look pretty daunting. For instance, the signature of the combinatorMany()
might appear like this in InteliSense or when hovering over its name:
Microsoft.FSharp.Core.FSharpFunc<FParsec.CharStream<Unit>,FParsec.Reply<Microsoft.FSharp.Collections.FSharpList<T>>>PrimitivesCS.Many<U,T>(Microsoft.FSharp.Core.FSharpFunc<FParsec.CharStream<Unit>,FParsec.Reply<T>>p)
Remember that Visual Studio hides namespaces in the UI that have ausing
in the current file. So if you add the followingusing
s (even though not all of them are actually needed)...
usingFParsec;usingMicrosoft.FSharp.Collections;usingMicrosoft.FSharp.Core;
...then the Visual Studio UI will simplify the above signature to:
FSharpFunc<CharStream<Unit>,Reply<FSharpList<T>>>PrimitivesCS.Many<U,T>(FSharpFunc<CharStream<Unit>,Reply<T>>p)
Still a mouthful, but a little more readable nonetheless.
You can use type aliases to further simplify signatures in the Visual Studio UI and your code:
usingChars=FParsec.CharStream<Microsoft.FSharp.Core.Unit>;
Unfortunately C# does not support type aliases with open generics. Hence if you want to simplify the type of a parser you will have to do it for each of the possibleReply<T>
s you are using:
usingStringParser=Microsoft.FSharp.Core.FSharpFunc<FParsec.CharStream<Microsoft.FSharp.Core.Unit>,FParsec.Reply<string>>;usingJsonParser=Microsoft.FSharp.Core.FSharpFunc<FParsec.CharStream<Microsoft.FSharp.Core.Unit>,FParsec.Reply<JObject>>;// ...
If you place your importusings
outside, and your aliasusing
sinside your namespace declaration then this will simplify your alias definitions:
usingSystem.Xml.Linq;usingFParsec;usingMicrosoft.FSharp.Core;namespaceTests{usingXElParser=FSharpFunc<CharStream<Unit>,Reply<XElement>>;// ...
Combining all suggestions yourusing
s could look like this for minimal noise:
#pragma warning disableIDE0065// Misplaced using directiveusingFParsec;usingFParsec.CSharp;usingMicrosoft.FSharp.Collections;usingMicrosoft.FSharp.Core;usingstaticFParsec.CSharp.PrimitivesCS;usingstaticFParsec.CSharp.CharParsersCS;namespaceMyParser{usingChars=CharStream<Unit>;usingCharParser=FSharpFunc<CharStream<Unit>,Reply<char>>;usingStringParser=FSharpFunc<CharStream<Unit>,Reply<string>>;// ...
FParsec.CSharp does not mirror all of FParsec's functions exactly. A few are not wrapped and some are just named differently.
Below is a table that maps FParsec's parser functions, combinators, and helper functions to their FParsec.CSharp equivalent.
The typeFSharpFunc<CharStream<U>, Reply<T>>
is shortened toP<T>
for brewity.
Keep in mind that many predefined parsers and some of the combinators have a variant that supports parsing with user state. Those variants always have aU
suffix in their name and are not listed in this table.
FParsec | FParsec.CSharp |
---|---|
preturn | P<T> Return(T) |
pzero | P<T> Zero<T>() |
(>>=) | P<TR> P<T1>.And(Func<T1, P<TR>>) ,P<TR> P<Unit>.And(Func<P<TR>>) if left side returnsUnit ,P<TR> P<(T1,T2)>.And(Func<T1, T2, P<TR>>) deconstructs left tuple result,P<TR> P<(T1,T2,T3)>.And(Func<T1, T2, T3, P<TR>>) deconstructs left 3-tuple result |
(>>%) | P<T2> P<T1>.Return(T2) |
(>>.) | P<T2> P<T1>.AndR(P<T2>) skips left explicitly,P<T> P<Unit>.And(P<T>) skips left implicitly when it returnsUnit |
(.>>) | P<T1> P<T1>.AndL(P<T2>) skips right explicitly,P<T> P<T>.And(P<Unit>) skips right implicitly when it returnsUnit |
(.>>.) | P<(T1,T2)> P<T1>.And(P<T2>) if neither side returnsUnit ,P<(Unit,Unit)> P<Unit>.And_(P<Unit>) if any side returnsUnit |
(|>>) | P<TR> P<T1>.Map(Func<T1, TR>) ,P<TR> P<Unit>.Map(Func<TR>) if left side returnsUnit ,P<TR> P<(T1,T2)>.Map(Func<T1, T2, TR>) deconstructs left tuple result,P<TR> P<(T1,T2,T3)>.Map(Func<T1, T2, T3, TR>) deconstructs left 3-tuple result |
between | P<T2> Between(P<T1>, P<T2>, P<T3>) (different argument order) |
pipe2 | P<TR> Pipe(P<T1>, P<T2>, Func<T1, T2, TR>) |
pipe3 | P<TR> Pipe(P<T1>, P<T2>, P<T3>, Func<T1, T2, T3, TR>) |
pipe4 | P<TR> Pipe(P<T1>, P<T2>, P<T3>, P<T4>, Func<T1, T2, T3, T4, TR>) |
pipe5 | P<TR> Pipe(P<T1>, P<T2>, P<T3>, P<T4>, P<T5>, Func<T1, T2, T3, T4, T5, TR>) |
(<|>) | P<T> P<T>.Or(P<T>) |
choice | P<T> Choice(params P<T>[]) |
choiceL | P<T> Choice(string, params P<T>[]) |
(<|>%) | P<T> P<T>.Or(T) |
opt | P<FSharpOption<T>> Opt_(P<T>) ,P<T> Opt(P<T>) unwraps theFSharpOption<T> ,P<T> Opt(P<T>, T) unwraps theFSharpOption<T> with default value |
optional | P<Unit> Optional(P<T>) |
attempt | P<T> Try(P<T>) |
(>>=?) | P<T2> P<T1>.AndTry(Func<T1, P<T2>>) |
(>>?) | P<T2> P<T1>.AndRTry(P<T2>) skips left explicitly,P<T> P<Unit>.AndTry(P<T>) skips left implicitly when it returnsUnit |
(.>>?) | P<T1> P<T1>.AndLTry(P<T2>) skips right explicitly,P<T> P<T>.AndTry(P<Unit>) skips right implicitly when it returnsUnit |
(.>>.?) | P<(T1,T2)> P<T1>.AndTry(P<T2>) if neither side returnsUnit ,P<(Unit,Unit)> P<Unit>.AndTry_(P<Unit>) if any side returnsUnit |
notEmpty | P<T> NotEmpty(P<T>) |
followedBy | P<Unit> FollowedBy(P<T>) |
followedByL | P<Unit> FollowedBy(P<T>, string) |
notFollowedBy | P<Unit> NotFollowedBy(P<T>) |
notFollowedByL | P<Unit> NotFollowedBy(P<T>, string) |
lookAhead | P<T> LookAhead(P<T>) |
(<?>) | P<T> P<T>.Label(string) |
(<??>) | P<T> P<T>.Label_(string) |
fail | P<T> Fail(string) |
failFatally | P<T> FailFatally(string) |
tuple2 | P<(T1,T2)> Tuple(P<T1>, P<T2>) |
tuple3 | P<(T1,T2,T3)> Tuple(P<T1>, P<T2>, P<T3>) |
tuple4 | P<(T1,T2,T3,T4)> Tuple(P<T1>, P<T2>, P<T3>, P<T4>) |
tuple5 | P<(T1,T2,T3,T4,T5)> Tuple(P<T1>, P<T2>, P<T3>, P<T4>, P<T5>) |
parray | P<T[]> Array(int, P<T>) |
skipArray | P<Unit> SkipArray(int, P<T>) |
many | P<FSharpList<T>> Many(P<T>) |
skipMany | P<Unit> SkipMany(P<T>) |
many1 | P<FSharpList<T>> Many1(P<T>) |
skipMany1 | P<Unit> SkipMany1(P<T>) |
sepBy | P<FSharpList<T>> Many(P<T>, P<TSep>) |
skipSepBy | P<Unit> SkipMany(P<T>, P<TSep>) |
sepBy1 | P<FSharpList<T>> Many1(P<T>, P<TSep>) |
skipSepBy1 | P<Unit> SkipMany1(P<T>, P<TSep>) |
sepEndBy | P<FSharpList<T>> Many(P<T>, P<TSep>, canEndWithSep: true) |
skipSepEndBy | P<Unit> SkipMany(P<T>, P<TSep>, canEndWithSep: true) |
sepEndBy1 | P<FSharpList<T>> Many1(P<T>, P<TSep>, canEndWithSep: true) |
skipSepEndBy1 | P<Unit> SkipMany1(P<T>, P<TSep>, canEndWithSep: true) |
manyTill | P<FSharpList<T>> ManyTill(P<T>, P<TEnd>) |
skipManyTill | P<Unit> SkipManyTill(P<T>, P<TEnd>) |
many1Till | P<FSharpList<T>> Many1Till(P<T>, P<TEnd>) |
skipMany1Till | P<Unit> SkipMany1Till(P<T>, P<TEnd>) |
chainl1 | P<T> ChainL(P<T>, P<Func<T, T, T>>) |
chainl | P<T> ChainL(P<T>, P<Func<T, T, T>>, T) |
chainr1 | P<T> ChainR(P<T>, P<Func<T, T, T>>) |
chainr | P<T> ChainR(P<T>, P<Func<T, T, T>>, T) |
runParserOnString | ParserResult<T> P<T>.RunOnString(string, string) |
runParserOnSubstring | ParserResult<T> P<T>.RunOnString(string, int, int, string) |
runParserOnStream | ParserResult<T> P<T>.RunOnStream(Stream, Encoding, string) |
runParserOnFile | ParserResult<T> P<T>.RunOnFile(string, Encoding) |
run | ParserResult<T> P<T>.Run(string) |
getPosition | P<Position> PositionP |
getUserState | P<U> GetUserState<U>() |
setUserState | P<Unit> SetUserState<U>(U) |
updateUserState | P<Unit> UpdateUserState<U>(Func<U, U>) |
userStateSatisfies | P<Unit> UserStateSatisfies<U>(Func<U, bool>) |
pchar | P<char> CharP(char) |
skipChar | P<Unit> Skip(char) |
charReturn | P<T> CharP(char, T) |
anyChar | P<char> AnyChar |
skipAnyChar | P<Unit> SkipAnyChar |
satisfy | P<char> CharP(Func<char, bool>) |
skipSatisfy | P<Unit> Skip(Func<char, bool>) |
satisfyL | P<char> CharP(Func<char, bool>, string) |
skipSatisfyL | P<Unit> Skip(Func<char, bool>, string) |
anyOf | P<char> AnyOf(IEnumerable<char>) |
skipAnyOf | P<Unit> SkipAnyOf(IEnumerable<char>) |
noneOf | P<char> NoneOf(IEnumerable<char>) |
skipNoneOf | P<Unit> SkipNoneOf(IEnumerable<char>) |
asciiUpper | P<char> AsciiUpper |
asciiLower | P<char> AsciiLower |
asciiLetter | P<char> AsciiLetter |
upper | P<char> Upper |
lower | P<char> Lower |
letter | P<char> Letter |
digit | P<char> Digit |
hex | P<char> Hex |
octal | P<char> Octal |
isAnyOf | not implemented |
isNoneOf | not implemented |
isAsciiUpper | bool IsAsciiUpper(char) |
isAsciiLower | bool IsAsciiLower(char) |
isAsciiLetter | bool IsAsciiLetter(char) |
isUpper | bool IsUpper(char) |
isLower | bool IsLower(char) |
isLetter | bool IsLetter(char) |
isDigit | bool IsDigit(char) |
isHex | bool IsHex(char) |
isOctal | bool IsOctal(char) |
tab | P<char> Tab |
newline | P<char> Newline |
skipNewline | P<Unit> SkipNewline |
newlineReturn | P<T> NewlineReturn(T) |
unicodeNewline | P<Unit> UnicodeNewline |
skipUnicodeNewline | P<Unit> SkipUnicodeNewline |
unicodeNewlineReturn | P<T> UnicodeNewlineReturn(T x) |
spaces | P<Unit> Spaces |
spaces1 | P<Unit> Spaces1 |
unicodeSpaces | P<Unit> UnicodeSpaces |
unicodeSpaces1 | P<Unit> UnicodeSpaces1 |
eof | P<Unit> EOF |
pstring | P<string> StringP(string) |
skipString | P<Unit> Skip(string) |
stringReturn | P<T> StringP(string, T) |
pstringCI | P<string> StringCI(string) |
skipStringCI | P<Unit> SkipCI(string) |
stringCIReturn | P<T> StringP(string, T) |
anyString | P<string> AnyString(int) |
skipAnyString | P<Unit> SkipAnyString(int) |
restOfLine | P<string> RestOfLine(bool) |
skipRestOfLine | P<Unit> SkipRestOfLine(bool) |
charsTillString | Reply<string> CharsTillString(string, int, bool) |
skipCharsTillString | Reply<Unit> SkipCharsTillString(string, int, bool) |
charsTillStringCI | Reply<string> CharsTillStringCI(string, int, bool) |
skipCharsTillStringCI | Reply<Unit> SkipCharsTillStringCI(string, int, bool) |
manySatisfy | P<string> ManyChars(Func<char, bool>) |
manySatisfy2 | P<string> ManyChars(Func<char, bool>, Func<char, bool>) |
skipManySatisfy | P<Unit> SkipManyChars(Func<char, bool>) |
skipManySatisfy2 | P<Unit> SkipManyChars(Func<char, bool>, Func<char, bool>) |
many1Satisfy | P<string> Many1Chars(Func<char, bool>) |
many1Satisfy2 | P<string> Many1Chars(Func<char, bool>, Func<char, bool>) |
skipMany1Satisfy | P<Unit> SkipMany1Chars(Func<char, bool>) |
skipMany1Satisfy2 | P<Unit> SkipMany1Chars(Func<char, bool>, Func<char, bool>) |
many1SatisfyL | P<string> Many1Chars(Func<char, bool>, string) |
many1Satisfy2L | P<string> Many1Chars(Func<char, bool>, Func<char, bool>, string) |
skipMany1SatisfyL | P<Unit> SkipMany1Chars(Func<char, bool>, string) |
skipMany1Satisfy2L | P<Unit> SkipMany1Chars(Func<char, bool>, Func<char, bool>, string) |
manyMinMaxSatisfy | P<string> ManyChars(Func<char, bool>, int, int) |
manyMinMaxSatisfy2 | P<string> ManyChars(Func<char, bool>, Func<char, bool>, int, int) |
skipManyMinMaxSatisfy | P<Unit> SkipManyChars(Func<char, bool>, int, int) |
skipManyMinMaxSatisfy2 | P<Unit> SkipManyChars(Func<char, bool>, Func<char, bool>, int, int) |
manyMinMaxSatisfyL | P<string> ManyChars(Func<char, bool>, int, int, string) |
manyMinMaxSatisfy2L | P<string> ManyChars(Func<char, bool>, Func<char, bool>, int, int, string) |
skipManyMinMaxSatisfyL | P<Unit> SkipManyChars(Func<char, bool>, int, int, string) |
skipManyMinMaxSatisfy2L | P<Unit> SkipManyChars(Func<char, bool>, Func<char, bool>, int, int, string) |
regex | Reply<string> Regex(string) |
regexL | Reply<string> Regex(string, string) |
identifier | not implemented |
manyChars | P<string> ManyChars(P<char>) |
manyChars2 | P<string> ManyChars(P<char>, P<char>) |
many1Chars | P<string> Many1Chars(P<char>) |
many1Chars2 | P<string> Many1Chars(P<char>, P<char>) |
manyCharsTill | P<string> ManyCharsTill(P<char>, P<TEnd>) |
manyCharsTill2 | P<string> ManyCharsTill(P<char>, P<char>, P<TEnd>) |
manyCharsTillApply | P<T> ManyCharsTill(P<char>, P<TEnd>, Func<string, TEnd, T>) |
manyCharsTillApply2 | P<T> ManyCharsTill(P<char>, P<char>, P<TEnd>, Func<string, TEnd, T>) |
many1CharsTill | P<string> Many1CharsTill(P<char>, P<TEnd>) |
many1CharsTill2 | P<string> Many1CharsTill(P<char>, P<char>, P<TEnd>) |
many1CharsTillApply | P<T> Many1CharsTill(P<char>, P<TEnd>, Func<string, TEnd, T>) |
many1CharsTillApply2 | P<T> Many1CharsTill(P<char>, P<char>, P<TEnd>, Func<string, TEnd, T>) |
manyStrings | P<string> ManyStrings(P<string>) |
manyStrings2 | not implemented |
many1Strings | P<string> Many1Strings(P<string>) |
many1Strings2 | not implemented |
stringsSepBy | ManyStrings(P<string>, P<String>) |
stringsSepBy1 | Many1Strings(P<string>, P<String>) |
skipped | P<string> P<Unit>.WithSkipped() |
withSkippedString | P<T2> P<T1>.WithSkipped(Func<string, T1, T2>) ,P<(string,T)> P<T>.WithSkipped() |
numberLiteral | P<NumberLiteral> NumberLiteral(NumberLiteralOptions, string) |
numberLiteralE | Reply<NumberLiteral> NumberLiteralE(NumberLiteralOptions, ErrorMessageList, CharStream<Unit>) |
pfloat | P<double> Float |
pint64 | P<long> Long |
pint32 | P<int> Int |
pint16 | P<short> Short |
pint8 | P<sbyte> Byte |
puint64 | P<ulong> ULong |
puint32 | P<uint> UInt |
puint16 | P<ushort> UShort |
puint8 | P<byte> UByte |
notFollowedByEof | P<Unit> NotFollowedByEOF |
followedByNewline | P<Unit> FollowedByNewline |
notFollowedByNewline | P<Unit> NotFollowedByNewline |
followedByString | P<Unit> FollowedBy(string) |
followedByStringCI | P<Unit> FollowedByCI(foo) |
notFollowedByString | P<Unit> NotFollowedBy(string) |
notFollowedByStringCI | P<Unit> NotFollowedByCI(string) |
nextCharSatisfies | P<Unit> NextCharSatisfies(Func<char, bool>) |
nextCharSatisfiesNot | P<Unit> NextCharSatisfiesNot(Func<char, bool>) |
next2CharsSatisfy | P<Unit> Next2CharsSatisfy(Func<char, char, bool>) |
next2CharsSatisfyNot | P<Unit> Next2CharsSatisfyNot(Func<char, char, bool>) |
previousCharSatisfies | P<Unit> PreviousCharSatisfies(Func<char, bool>) |
previousCharSatisfiesNot | P<Unit> PreviousCharSatisfiesNot(Func<char, bool>) |
foldCase | string FoldCase(string) |
normalizeNewlines | string NormalizeNewlines(string) |
floatToHexString | string DoubleToHexString(double) |
floatOfHexString | double DoubleOfHexString(string) |
float32ToHexString | string FloatToHexString(double) |
float32OfHexString | double FloatOfHexString(string) |
This library is based on the following works:
- ObviouslyFParsec, because FParsec.CSharp mostly justwraps FParsec. FParsec is also where I took most of the XML documentation from.
- Pidgin gave me the whole idea of thinking about a parser combinator API in C#.
- The OPP's implicit operator implementation was taken fromStackOverflow.
- The idea for the parser
Purify()
was taken fromStackOverflow. - The NFA implementation for the glob/regex parser example was inspired byRuss Cox' fantastic article on efficient regex matching.
About
A thin C# wrapper for FParsec.