Farkle is a library written in F#, but supporting the much more used C# is a valuable feature. Because these two languages are quite different, there is a more idiomatic API for C# users. In this tutorial, we will assume that you have read the F# quickstart guide.
The API described here is available in F# as well.
F# programs using Farkle use an operator-laden API to compose designtime Farkles. Because C# does not support custom operators, we can instead use a different API based on extension methods.
Regular expressions are created using members of the Regex
class, which is well documented. Predefined sets are in the PredefinedSets
class. Let's see a comparison:
open Farkle.Builder
open Farkle.Builder.Regex
let regex1 = regexString "Hello+' '?World!*"
let regex2 = concat [
string "Hell"
char 'o' |> plus
char ' ' |> optional
string "World"
char '!' |> star
]
using Farkle.Builder;
using static Farkle.Builder.Regex;
Regex regex1 = FromRegexString("Hello+' '?World!*");
Regex regex2 = Join(
Literal("Hell"),
Literal('o').AtLeast(1),
Literal(' ').Optional(),
Literal("World"),
Literal('!').ZeroOrMore()
);
|
The following table highlights the differences between the F# and C# designtime Farkle API.
terminal "X" (T (fun _ x -> x.ToString())) r
|
Terminal.Create("X", (_, x) => x.ToString(), r)
|
"S" ||= [p1; p2]
|
Nonterminal.Create("S", p1, p2)
|
!@ x
|
x.Extended()
|
!% x
|
x.Appended()
|
!& "literal"
|
"literal".Appended()
|
empty
|
ProductionBuilder.Empty
|
newline
|
Terminal.NewLine
|
x .>> y
|
x.Append(y)
|
x .>>. y
|
x.Extend(y)
|
x |> asIs
|
x.AsIs()
|
x => (fun x -> MyFunc x)
|
x.Finish(x => MyFunc(x))
|
x =% 0
|
x.FinishConstant(0)
|
RuntimeFarkle.build x
|
x.Build()
|
RuntimeFarkle.buildUntyped x
|
x.BuildUntyped()
|
If you want to define productions with a single member, you can omit the calls to Appended()
or Extended()
as follows:
!& str => fun () -> v
|
str.Appended().Finish(() => v)
|
str.Finish(() => v) *
|
!% x => fun () -> v
|
x.Appended().Finish(() => v)
|
x.Finish(() => v) *
|
!@ x => f
|
x.Extended().Finish(f)
|
x.Finish(f)
|
!& str =% v
|
str.Appended().FinishConstant(v)
|
str.FinishConstant(v)
|
!% x =% v
|
x.Appended().FinishConstant(v)
|
x.FinishConstant(v) *
|
!@ x |> asIs
|
x.Extended().AsIs()
|
x.AsIs()
|
Methods with an asterisk (*) were introduced in Farkle 6.5.0.
The Build
and BuildUntyped
extension methods accept an optional CancellationToken
and will throw an OperationCanceledException
if it gets triggered.
Let's take a look at the calculator we made at the quick start guide written in C#:
using System;
using Farkle.Builder;
using Farkle.Builder.OperatorPrecedence;
public static class SimpleMaths
{
public static readonly DesigntimeFarkle<double> Designtime;
public static readonly RuntimeFarkle<double> Runtime;
static SimpleMaths()
{
DesigntimeFarkle<double> number = Terminals.Double("Number");
DesigntimeFarkle<double> expression = Nonterminal.Create<double>("Expression");
expression.SetProductions(
number.AsIs(),
expression.Extended().Append("+").Extend(expression).Finish((x1, x2) => x1 + x2),
expression.Extended().Append("-").Extend(expression).Finish((x1, x2) => x1 - x2),
expression.Extended().Append("*").Extend(expression).Finish((x1, x2) => x1 * x2),
expression.Extended().Append("/").Extend(expression).Finish((x1, x2) => x1 / x2),
"-".Appended().Extend(expression).WithPrecedence(out object NEG).Finish(x => -x),
expression.Extended().Append("^").Extend(expression).Finish(Math.Pow),
"(".Appended().Extend(expression).Append(")").AsIs());
OperatorScope opScope = new OperatorScope(
new LeftAssociative("+", "-"),
new LeftAssociative("*", "/"),
new PrecedenceOnly(NEG),
new RightAssociative("^"));
Designtime = expression.WithOperatorScope(opScope);
Runtime = Designtime.Build();
}
}
|
Notice how we called the WithPrecedence
method. In F# we were passing an object to the prec
function. In C# we let the method create and return that object to us, taking advantage of C# 7.0's out
variable declarations. We can still pass an object if we want.
To customize things like the case-sensitivity of designtime Farkles, there are some extension methods for them that reside in the Farkle.Builder
namespace. Let's take a look at an example:
RuntimeFarkle<double> customized =
SimpleMaths.Designtime
.AddBlockComment("/*", "*/")
.AddLineComment("//")
.AutoWhitespace(false)
.CaseSensitive(false)
.MarkForPrecompile()
.Build();
|
To parse text, there are some extension methods for runtime Farkles that reside in the Farkle
namespace. These functions return an F# result type that can nevertheless be used from C# like this:
using Microsoft.FSharp.Core;
DesigntimeFarkle<double> designtime = /*...*/;
RuntimeFarkle<double> runtime = designtime.Build();
// Parsing strings.
FSharpResult<double, FarkleError> result = runtime.Parse("foobar");
if (result.IsOk)
Console.WriteLine("Success. Result: {0}", result.OkValue);
else
Console.WriteLine("Failure. Error message: {0}", result.ErrorValue);
// Parsing ReadOnlyMemories
runtime.Parse("foobar".AsMemory());
// Parsing TextReaders
using (TextReader f = File.OpenText("foobar.txt"))
{
runtime.Parse(f);
}
// Parsing files
runtime.ParseFile("foobar.txt");
|
So, I hope you enjoyed this little guide. If you did, don't forget to give Farkle a try, and maybe you feel especially sharp today, and want to hit the star button as well. I hope that all of you have a wonderful day, and to see you soon. Goodbye!
val regex1: obj
val regex2: obj
Multiple items
val string: value: 'T -> string
--------------------
type string = System.String
Multiple items
val char: value: 'T -> char (requires member op_Explicit)
--------------------
type char = System.Char