Skip to content

Commit b323824

Browse files
committed
Add documentation for the precompiler.
1 parent da1ef64 commit b323824

File tree

8 files changed

+141
-6
lines changed

8 files changed

+141
-6
lines changed

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Farkle
66

77
<!--"Modern" is a marketing catchphrase, but keep in mind that FsLexYacc is definitely not "modern"-->
8-
Farkle is a modern and easy-to-use parser library for F# and C#, that creates [LALR parsers][lalr] from composable [parser combinator][combinator]-like objects. Moreover, it can read grammars created by [GOLD Parser][gold] (the project that inspired Farkle) and provides an API to post-process them into arbitrary types.
8+
Farkle is a modern and easy-to-use parser library for F# and C#, that creates [LALR parsers][lalr] from composable [parser combinator][combinator]-like objects.
99

1010
## Documentation
1111

Diff for: RELEASE_NOTES.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#### 6.0.0
1+
#### 6.0.0-alpha.1
22
* __Breaking change:__ Removed the legacy API for creating runtime Farkles from EGT files (the API with the transformers and fusers). EGT files are still supported (for now), but users are strongly urged to rewrite their grammars using `Farkle.Builder`, or implement the `PostProcessor` interface themselves (not recommended).
33
* __Breaking change:__ The `PostProcessor` type was moved to the root `Farkle` namespace. Some reusable post-processors were moved to the new `Farkle.PostProcessors` module.
4+
* Farkle can now build grammars at compile-time. See more in https://teo-tsirpanis.github.io/Farkle/the-precompiler.html.
45
* Added a function to rename designtime Farkles; it might be useful for better diagnostic messages.
56

67
#### 5.4.1 - 23-03-2020

Diff for: docsrc/content/index.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ The library is available under the MIT license, which allows modification and
3333
redistribution for both commercial and non-commercial purposes. For more information see the
3434
[License file][license] in the GitHub repository.
3535

36-
[content]: https://github.com/teo-tsirpanis/Farkle/tree/master/docs/content
36+
[content]: https://github.com/teo-tsirpanis/Farkle/tree/master/docsrc/content
3737
[gh]: https://github.com/teo-tsirpanis/Farkle
3838
[issues]: https://github.com/teo-tsirpanis/Farkle/issues
3939
[license]: https://github.com/teo-tsirpanis/Farkle/blob/master/LICENSE.txt
40-
[gold]: http://www.goldparser.org/

Diff for: docsrc/content/the-precompiler.md

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Farkle's precompiler
2+
3+
Every time an app using Farkle starts, it generates the parser tables. This process takes some time, and it take even more, the app does not reuse the runtime Farkles it creates.
4+
5+
Most apps need to parse a static grammar whose specifications never change between program executions. For example, a compiler or a JSON parser will parse text from the same language every time you use it. Farkle would spend time generating the parsing tables that do not depend on user input and will always be the same. It wouldn't hurt a program like a REST server parsing hundreds of input strings, but a compiler that parses only one file, building the grammar every time it is run would take some time, maybe more than the time spent for the parser, if the grammar is big.
6+
7+
What is more, Farkle does not report any grammar error (such as an LALR conflict) until it's too late: text was attempted to be parsed with a faulty grammar. Wouldn't it be better if these errors were caught earlier in the app's life cycle?
8+
9+
One of Farkle's new features that came with version 6 is called _the precompiler_. The precompiler addresses this inherent limitation of how Farkle works with grammars. Instead of generating it every time, the grammar's parser tables are built ahead of time and stored in the program's assembly, when it gets compiled. This significantly boosts the program's startup time by orders of magnitude.
10+
11+
## How to use it
12+
13+
Using the precompiler is a very easy procedure and does not differ very much from regularly using Farkle.
14+
15+
### Preparing the your code
16+
17+
Let's say you have a very complicated designtime Farkle that you want its grammar to be precompiled. The first thing to do is to use the `RuntimeFarkle.markForPrecompile` function. Here's an example in both F# and C#:
18+
19+
``` fsharp
20+
open Farkle;
21+
open Farkle.Builder;
22+
let designtime =
23+
"My complicated language"
24+
||= [!@ beginning .>>. middle .>>. ``end`` => (fun b m e -> b + m + e)]
25+
|> DesigntimeFarkle.addLineComment "//"
26+
|> DesigntimeFarkle.addBlockComment "/*" "*/"
27+
|> RuntimeFarkle.markForPrecompile
28+
29+
let runtime = RuntimeFarkle.build designtime
30+
```
31+
32+
``` csharp
33+
using Farkle;
34+
using Farkle.Builder;
35+
36+
public class MyLanguage {
37+
public static readonly DesigntimeFarkle<int> Designtime;
38+
public static readonly RuntimeFarkle<int> Runtime;
39+
40+
static MyLanguage() {
41+
Designtime =
42+
Nonterminal.Create("My complicated language",
43+
beginning.Extended().Extend(middle).Extend(end).Finish((b, m, e) => b + m + e))
44+
.AddLineComment("//")
45+
.AddBlockComment("/*", "*/")
46+
.MarkForPrecompile();
47+
48+
Runtime = Designtime.Build();
49+
}
50+
}
51+
```
52+
53+
With this simple function (or extension method), Farkle will be able to discover this designtime Farkle in your assembly and precompile it. It will be able to actually find it if you follow these rules:
54+
55+
* The designtime Farkle must be declared in a `static readonly` _field_ (not property). For F#, a let-bound value in a module is equivalent, but it __must not__ be mutable.
56+
57+
* The designtime Farkle's field can be of any visibility (public, internal, private, it doesn't matter). It will be detected even in nested types. Just remember the rule above.
58+
59+
* The `markForPrecompile` function must be the absolute last function to be applied in the designtime Farkle. If you don't apply it to the starting symbol, or applied any other function on top of it, Farkle will not precompile the designtime Farkle, but it will still be perfectly functional.
60+
61+
* The `markForPrecompile` function must be called in the assembly the designtime Farkle was created and should better not used from inside another function.
62+
63+
Say you have the following function in assembly A:
64+
65+
``` fsharp
66+
let markForPrecompileAndRename (df: DesigntimeFarkle<_>) =
67+
df
68+
|> DesigntimeFarkle.rename (df.Name + " Precompiled")
69+
|> DesigntimeFarkle.markForPrecompile
70+
```
71+
72+
And say you use the function above in assembly B. It won't work; Farkle won't be able to precompile the designtime Farkle. A solution would have been to make the function inline, but don't be 100% sure it will work.
73+
74+
* The field of the precompilable designtime Farkle must be either a typed or untyped designtime Farkle:
75+
76+
``` csharp
77+
public class MyLanguage {
78+
// This will work.
79+
public static readonly DesigntimeFarkle Foo = MySimpleDesigntimeFarkle.MarkForPrecompile();
80+
// But not this.
81+
public static readonly object Foo = MyComplicatedDesigntimeFarkle.MarkForPrecompile();
82+
}
83+
```
84+
85+
* All precompilable designtime Farkles within an assembly must have different names, or an error will be raised during precompiling. That's actually why the `DesigntimeFarkle.rename` function was created.
86+
87+
* Multiple references to the same precompilable designtime Farkle do not pose a problem. The following example will work:
88+
89+
``` fsharp
90+
let designtime1 = myComplicatedDesigntimeFarkle |> RuntimeFarkle.markForPrecompile
91+
let designtime2 = designtime1
92+
```
93+
94+
### Preparing your project
95+
96+
With our designtime Farkles being ready to be precompiled, it's time to prepare our project files. Add a reference to [the `Farkle.Tools.MSBuild` package][msbuild] like that:
97+
98+
``` xml
99+
<!-- The following properties are optional. -->
100+
<PropertyGroup>
101+
<!-- Set it to false to disable the precompiler. Your app
102+
will still work, but without the performance boost the precompiler offers. -->
103+
<FarkleEnablePrecompiler>false</FarkleEnablePrecompiler>
104+
<!-- If it is true, grammar errors will raise a warning
105+
instead of an error and not fail the entire build. -->
106+
<FarkleSuppressGrammarErrors>true</FarkleSuppressGrammarErrors>
107+
</PropertyGroup>
108+
<ItemGroup>
109+
<PackageReference Include="Farkle.Tools.MSBuild" Version="6.*" PrivateAssets="all" />
110+
</ItemGroup>
111+
```
112+
If you compile your program now, you should get a message that your designtime Farkles' grammars got precompiled. Hooray! With our grammars being precompiled, calling `RuntimeFarkle.build designtime` (or `buildUntyped`) is now much, much faster.
113+
114+
## Some final notes
115+
116+
### Beware of non-determinism
117+
118+
Farkle's precompiler was made for grammars that are static, that's the reason it only works on static readonly fields: once you created it in your code, you cannot change it. Otherwise, what good would the precompiler be?
119+
120+
You can always call a non-deterministic function like `DateTime.Now()` that will make your designtime Farkle parse integers in the hexadecimal format in your birthday, and in the decimal format in all other days. If you build your app on your birthday, it will produce bizarre results on all the other days, and if you build it on a day other than your birthday, it will work every time, except of your birthday (the worst birthday present) __Just don't do it.__ Farkle cannot be made to detect such things, and you are not getting any smarter by doing it.
121+
122+
### Building from an IDE
123+
124+
And last but not least, the precompiler will not work when running a .NET Framework-based edition of MSBuild. This includes building from IDEs such as Visual Studio. The recommended way to build an app that uses the precompiler is through `dotnet build` and its friends. This _doesn't mean_ that the precompiler won't work on .NET Framework assemblies; you have to use the new project format and build with the .NET Core SDK; it will normally work.
125+
126+
Rider however can use the precompiler with a simple workaround. Open its settings, go to "Build, Execution, Deployment", "Toolset and Build", "Use MSBuild version", and select an MSBuild executable from the .NET Core SDK (it typically has a `.dll` extension).
127+
128+
![The Settings window in JetBrains Rider](img/rider_msbuild_workaround.png)
129+
130+
---
131+
132+
So I hope you enjoyed this little tutorial. If you did, don't forget to give Farkle a try, and maybe you feel especially precompiled today, and want to hit the star button as well. I hope that all of you have an wonderful day, and to see you soon. Goodbye!
133+
134+
[msbuild]: https://www.nuget.org/packages/Farkle.Tools.MSBuild

Diff for: docsrc/files/img/rider_msbuild_workaround.png

74.3 KB
Loading

Diff for: docsrc/tools/templates/template.cshtml

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<li class="nav-header">Documentation</li>
5151
<li><a href="@Root/reference/index.html">API Reference</a></li>
5252
<li><a href="@Root/templates.html">Templating Reference</a></li>
53+
<li><a href="@Root/the-precompiler.html">The precompiler</a></li>
5354
<li><a href="@Root/advanced-features.html">Advanced Features</a></li>
5455
<li><a href="@Root/gold-parser-missing-features.html">Missing Features from GOLD Parser</a></li>
5556
</ul>

Diff for: sample/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ Currently we have:
66

77
* A gramamr for the GOLD Meta-Language, ported from the [official grammar's version 2.6.0](http://www.goldparser.org/grammars/index.htm), and written in F#. It was made for correctness and performance testing.
88

9-
* A grammar for a simple mathematical expressions, written in the GOLD Parser and in F#. It was made to test Farkle's ability to parse GOLD Parser gramamrs, and for demonstration purposes.
9+
* A grammar for a simple mathematical expressions, written in the GOLD Parser and in F#. It was made to test Farkle's ability to parse GOLD Parser gramamrs, and for demonstration purposes.

Diff for: src/Farkle.Tools.MSBuild/Farkle.Tools.MSBuild.targets

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
Outputs="$(_PrecompiledByFarkle)">
7474
<Warning
7575
Condition="$(MSBuildRuntimeType) != Core"
76-
Text="Farkle can only precompile grammars on projects built with the .NET Core SDK (dotnet build etc). Your project will still work, but the grammar will be build every time."/>
76+
Text="Farkle can only precompile grammars on projects built with the .NET Core SDK (dotnet build etc). Your project will still work, but the grammar will be build every time. See more in https://teo-tsirpanis.github.io/Farkle/the-precompiler.html#Building-from-an-IDE"/>
7777
<FarklePrecompileTask
7878
Condition="$(MSBuildRuntimeType) == Core"
7979
AssemblyPath="@(IntermediateAssembly->'%(FullPath)')"

0 commit comments

Comments
 (0)