Skip to content

Commit

Permalink
Clean-up and prepare release workflow. (#346)
Browse files Browse the repository at this point in the history
* Remove terminology from Farkle 6.

* Remove outdated comment.

* Remove or comment out old VSCode tasks.

* Improve packaging of the F# API and auto-load it in F# Interactive.

* Add release workflow and fix the Pack target.

* Suppress some warnings that appear in release mode only.
  • Loading branch information
teo-tsirpanis authored Dec 27, 2024
1 parent b16c8ac commit 00c1359
Show file tree
Hide file tree
Showing 12 changed files with 107 additions and 66 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Release

on:
push:
branches:
- mainstream
- release/*
release:
types: [published]

jobs:
pack:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Restore .NET local tools
run: dotnet tool restore
- name: Pack
run: dotnet run --project eng/Farkle.Build.fsproj -- -t NuGetPack
- name: Upload package artifacts
uses: actions/upload-artifact@v4
with:
name: nuget-packages
path: ./bin
release:
needs: pack
environment: ${{ github.event_name == 'release' && 'release' || 'nightly' }}
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
- name: Download package artifacts
uses: actions/download-artifact@v4
with:
name: nuget-packages
path: ./bin
- name: Publish
run: dotnet nuget push ./bin/*.nupkg --api-key ${{ secrets.NUGET_KEY }} --source ${{ vars.NUGET_FEED }}
5 changes: 2 additions & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
{"label": "🧹CLEAN🧹", "command": "git", "args": ["clean", "-Xdf"], "problemMatcher": []},
{"label": "🏭GENERATE CODE🏭", "command": "pwsh", "args": ["./build.ps1", "target", "GenerateCode"], "problemMatcher": []},
{"label": "🔬TEST🔬", "group": {"kind": "test", "isDefault": true}, "command": "pwsh", "args": ["./build.ps1", "target", "Test"]},
{"label": "🦕LEGACY TEST🦕", "group": {"kind": "test", "isDefault": true}, "command": "pwsh", "args": ["./build.ps1", "target", "TestLegacy"]},
// TODO-FARKLE7: Re-enable once the precompiler is ported.
// {"label": "🦕LEGACY TEST🦕", "group": {"kind": "test", "isDefault": true}, "command": "pwsh", "args": ["./build.ps1", "target", "TestLegacy"]},
{"label": "⚙BUILD TESTS⚙", "group": "build", "presentation": {"reveal": "silent"}, "command": "dotnet", "args": ["build", "tests/Farkle.Tests/Farkle.Tests.fsproj", "-c", "Debug"], "problemMatcher": []},
{"label": "📦PACK📦", "command": "pwsh", "args": ["./build.ps1", "target", "NuGetPack"], "problemMatcher": []},
{"label": "⏱BENCHMARK⏱", "command": "pwsh", "args": ["./build.ps1", "target", "Benchmark"], "problemMatcher": []},
{"label": "📜DOCS📜", "command": "pwsh", "args": ["./build.ps1", "target", "GenerateDocsDebug"], "problemMatcher": []},
{"label": "🚀RELEASE🚀", "presentation": {"reveal": "always", "focus": false, "panel": "new", "showReuseMessage": false, "clear": true}, "command": "pwsh", "args": ["./build.ps1", "target", "Release"], "problemMatcher": []},
{"label": "📝RELEASE DOCS📝", "presentation": {"reveal": "always", "focus": false, "panel": "new", "showReuseMessage": false, "clear": true}, "command": "pwsh", "args": ["./build.ps1", "target", "ReleaseDocs"], "problemMatcher": []}
]
}
16 changes: 5 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@

## How to build

* Install .NET 6 SDK
* Install PowerShell
* `build.ps1`

There are Visual Studio Code tasks to make your life easier.
Open the solution and build it, or run `build.ps1`. There are VSCode tasks for some of the build script targets.

## Code style

* 4 spaces indentation
* No trailing whitespace
* One trailing newline
* Prefer to put types in namespaces, not modules (even internal ones).

## What to contribute

As the main Farkle library is undergoing a complete rewrite, contributions will be appreciated to the following areas:
Contributions are appreciated to the following areas:

* Bug fixes
* Performance improvements
* New features (please open an issue first)
* Documentation
* Tests
* Samples
Expand All @@ -30,7 +28,3 @@ As the main Farkle library is undergoing a complete rewrite, contributions will
Farkle's diagnostic messages can be localized. The officially supported languages are English and Greek. If you know any other language and want to translate them, it would be great!

To do so you need to find the `.resx` files in the repository, and create a new one corresponding to your language's culture name.

## Breaking changes policy

Minor breaking changes on minor APIs (generally anything not named or related to `DesigntimeFarkle` or `RuntimeFarkle`) are tolerable, if they do not impact the average Farkle user.
2 changes: 2 additions & 0 deletions eng/build.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ let farkleToolsMSBuildProject = "./src/Farkle.Tools.MSBuild/Farkle.Tools.MSBuild
let sourceProjects = [
farkleProject
farkleToolsProject
#if TODO_PRECOMPILER
farkleToolsMSBuildProject
#endif
]

// The project to be tested
Expand Down
4 changes: 2 additions & 2 deletions sample/Farkle.Samples.FSharp/IndentBased.fs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ type IndentCodeTokenizer(grammar: IGrammarProvider) as this =

// These two fields hold our virtual terminals. They have
// nothing to do with the virtual terminals we declared above;
// they were designtime Farkles and this one is a Farkle.Grammars.Terminal.
// Caching them instead of calling Grammar.GetTerminalByName
// they were objects of the builder API and this one is from the grammars API.
// Caching them instead of calling GetTokenSymbolFromSpecialName every time
// is a good practice for performance and clarity reasons.
let blockStart = grammar.GetTokenSymbolFromSpecialName BlockStartSpecialName
let blockEnd = grammar.GetTokenSymbolFromSpecialName BlockEndSpecialName
Expand Down
4 changes: 4 additions & 0 deletions src/Farkle/Builder/Lr/AugmentedSyntaxProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ private Symbol GetProductionMember(int memberIndex)
/// Represents a terminal or nonterminal symbol in an augmented grammar.
/// </summary>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
#pragma warning disable CS9113 // Parameter is unread.
public readonly struct Symbol(uint value, AugmentedSyntaxProvider syntax) : IEquatable<Symbol>, IComparable<Symbol>
#pragma warning restore CS9113 // Parameter is unread.
{
private readonly uint _value = value;

Expand Down Expand Up @@ -241,7 +243,9 @@ public bool MoveNext()
}

[DebuggerDisplay("{DebuggerDisplay,nq}")]
#pragma warning disable CS9113 // Parameter is unread.
public readonly struct Production(int index, AugmentedSyntaxProvider syntax) : IEquatable<Production>
#pragma warning restore CS9113 // Parameter is unread.
{
public int Index { get; } = index;

Expand Down
1 change: 0 additions & 1 deletion src/Farkle/Builder/Lr/OperatorScopeConflictResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ private bool TryGetSymbolObject(EntityHandle handle, [MaybeNullWhen(false)] out
for (int i = members.Length - 1; i >= 0; i--)
{
ISymbolBase symbol = members[i].Symbol;
// TODO: Remove this if block (see #41).
if (symbol is INonterminal)
{
continue;
Expand Down
4 changes: 2 additions & 2 deletions src/Farkle/Farkle.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<PolySharpIncludeRuntimeSupportedAttributes>true</PolySharpIncludeRuntimeSupportedAttributes>
</PropertyGroup>
<ItemGroup>
<Content Include="Farkle.fs" Pack="true" PackagePath="src/fs" />
<Content Include="Farkle.props" Pack="true" PackagePath="build;buildTransitive" />
<Content Include="Farkle.fs" Pack="true" PackagePath="contentFiles/fs/any" BuildAction="Compile" />
<Content Include="Farkle.fsx" Pack="true" PackagePath="content" />
<InternalsVisibleTo Include="Farkle.Benchmarks" />
<InternalsVisibleTo Include="Farkle.Tests" />
<InternalsVisibleTo Include="Farkle.Tests.CSharp" />
Expand Down
3 changes: 3 additions & 0 deletions src/Farkle/Farkle.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file gets imported by F# Interactive.
// Load Farkle's F# API.
#load "Farkle.fs"
5 changes: 0 additions & 5 deletions src/Farkle/Farkle.props

This file was deleted.

66 changes: 33 additions & 33 deletions tests/Farkle.Tests/GrammarBuilderTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ let tests = testList "Grammar builder tests" [

test "A grammar that only accepts the empty string indeed accepts it" {
let symbol = "S" |||= [empty]
let runtime = GrammarBuilder.buildSyntaxCheck symbol
let result = CharParser.parseString runtime ""
let parser = GrammarBuilder.buildSyntaxCheck symbol
let result = CharParser.parseString parser ""

expectIsParseSuccess result "Something went wrong"
}
Expand All @@ -64,16 +64,16 @@ let tests = testList "Grammar builder tests" [
}

testProperty "Farkle can properly read signed integers" (fun num ->
let runtime = Terminals.int64 "Signed" |> GrammarBuilder.build
Expect.equal (runtime.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing a signed integer failed")
let parser = Terminals.int64 "Signed" |> GrammarBuilder.build
Expect.equal (parser.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing a signed integer failed")

testProperty "Farkle can properly read unsigned integers" (fun num ->
let runtime = Terminals.uint64 "Unsigned" |> GrammarBuilder.build
Expect.equal (runtime.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing an unsigned integer failed")
let parser = Terminals.uint64 "Unsigned" |> GrammarBuilder.build
Expect.equal (parser.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing an unsigned integer failed")

testProperty "Farkle can properly read floating-point numbers" (fun (NormalFloat num) ->
let runtime = Terminals.float "Floating-point" |> GrammarBuilder.build
Expect.equal (runtime.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing an unsigned integer failed")
let parser = Terminals.float "Floating-point" |> GrammarBuilder.build
Expect.equal (parser.Parse(string num)) (ParserResult.CreateSuccess num) "Parsing an unsigned integer failed")

test "Arithmetic overflows when parsing integers do not cause an exception" {
// Add a space at the beginning to test position propagation.
Expand Down Expand Up @@ -117,41 +117,41 @@ let tests = testList "Grammar builder tests" [
}

test "Farkle can properly handle line groups" {
let runtime =
let parser =
Group.Line("Line Group", "!!", fun _ data -> data.ToString())
|> GrammarBuilder.build
Expect.equal (runtime.Parse "!! No new line") (ParserResult.CreateSuccess "!! No new line")
Expect.equal (parser.Parse "!! No new line") (ParserResult.CreateSuccess "!! No new line")
"Farkle does not properly handle line groups that end on EOF"
Expect.equal (runtime.Parse "!! Has new line\n") (ParserResult.CreateSuccess "!! Has new line")
Expect.equal (parser.Parse "!! Has new line\n") (ParserResult.CreateSuccess "!! Has new line")
"Farkle does not properly handle line groups that end on a new line"
}

test "Terminals named 'Newline' cannot terminate line groups" {
let runtime =
let parser =
"X" |||= [!& "newline"; !& "x1" .>> "x2"]
|> _.AddLineComment("//")
|> GrammarBuilder.buildSyntaxCheck
let testString = "// newline\nx1 x2"

let result = runtime.Parse testString
let result = parser.Parse testString

expectIsParseSuccess result "Parsing failed"
}

test "Farkle can properly handle block groups" {
let runtime =
let parser =
Group.Block("Block Group", "{", "}", fun _ data -> data.ToString())
|> GrammarBuilder.build

Expect.equal (runtime.Parse "{🆙🆙}") (ParserResult.CreateSuccess "{🆙🆙}") "Farkle does not properly handle block groups"
Expect.equal (parser.Parse "{🆙🆙}") (ParserResult.CreateSuccess "{🆙🆙}") "Farkle does not properly handle block groups"
}

test "Farkle can properly handle recursive block groups" {
let runtime =
let parser =
Group.Block("Block Group", "{", "}", (fun _ data -> data.ToString()), GroupOptions.Recursive)
|> GrammarBuilder.build

Expect.equal (runtime.Parse "{{🆙🆙}}") (ParserResult.CreateSuccess "{{🆙🆙}}") "Farkle does not properly handle recursive block groups"
Expect.equal (parser.Parse "{{🆙🆙}}") (ParserResult.CreateSuccess "{{🆙🆙}}") "Farkle does not properly handle recursive block groups"
}

test "Renaming grammar symbols works" {
Expand Down Expand Up @@ -254,7 +254,7 @@ let tests = testList "Grammar builder tests" [
test "Many block groups can be ended by the same symbol" {
// It doesn't cause a DFA conflict because the
// end symbols of the different groups are considered equal.
let runtime =
let parser =
"Test" |||= [
!% Group.Block("Group 1", "{", "}")
!% Group.Block("Group 2", "[", "}")
Expand All @@ -269,51 +269,51 @@ let tests = testList "Grammar builder tests" [
|> GrammarBuilder.buildSyntaxCheck

["{}"; "[}"; "()"; ")"]
|> List.iter (fun x -> expectIsParseSuccess (runtime.Parse x) (sprintf "Parsing %s failed" x))
|> List.iter (fun x -> expectIsParseSuccess (parser.Parse x) (sprintf "Parsing %s failed" x))
}

test "Parsing untyped groups works" {
let runtime =
let parser =
"Test" ||= [
!% Group.Block("Untyped Group", "{", "}") =% ()
]
|> GrammarBuilder.build

expectIsParseSuccess (runtime.Parse "{test}") "Parsing a test string failed"
expectIsParseSuccess (parser.Parse "{test}") "Parsing a test string failed"
}

test "The many(1) operators work" {
let mkRuntime atLeastOne =
let mkParser atLeastOne =
literal "x"
|> _.Cast()
|> if atLeastOne then many1 else many
|> GrammarBuilder.buildSyntaxCheck
let runtime = mkRuntime false
let runtime1 = mkRuntime true
let parser = mkParser false
let parser1 = mkParser true

[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 100]
|> List.iter (fun x ->
let s = String.replicate x "x"
expectIsParseSuccess (runtime.Parse s) (sprintf "Parsing %A with many failed" s)
expectIsParseSuccess (parser.Parse s) (sprintf "Parsing %A with many failed" s)
if x <> 0 then
expectIsParseSuccess (runtime1.Parse s) (sprintf "Parsing %A with many1 failed" s))
expectIsParseSuccess (parser1.Parse s) (sprintf "Parsing %A with many1 failed" s))
}

test "The sepBy(1) operators work" {
let mkRuntime atLeastOne =
let mkParser atLeastOne =
literal "x"
|> _.Cast()
|> (if atLeastOne then sepBy1 else sepBy) (literal ",")
|> GrammarBuilder.buildSyntaxCheck
let runtime = mkRuntime false
let runtime1 = mkRuntime true
let parser = mkParser false
let parser1 = mkParser true

[0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 100]
|> List.iter (fun x ->
let s = Seq.replicate x "x" |> String.concat ","
expectIsParseSuccess (runtime.Parse s) (sprintf "Parsing %A with sepBy failed" s)
expectIsParseSuccess (parser.Parse s) (sprintf "Parsing %A with sepBy failed" s)
if x <> 0 then
expectIsParseSuccess (runtime1.Parse s) (sprintf "Parsing %A with sepBy1 failed" s))
expectIsParseSuccess (parser1.Parse s) (sprintf "Parsing %A with sepBy1 failed" s))
}

#if false // TODO-FARKLE7: Reevaluate when codegen is implemented in Farkle 7.
Expand All @@ -332,14 +332,14 @@ let tests = testList "Grammar builder tests" [
let mkTerminal (name, typ: Type, target) =
let t = typ.GetMethod(name).CreateDelegate<T<char,string>>(target)
terminal name t (Regex.string name)
let runtime =
let parser =
"Test" ||=
List.map (fun x -> !@ (mkTerminal x) |> asProduction) testData
|> DesigntimeFarkle.forceDynamicCodeGen
|> GrammarBuilder.build

for x, _, _ in testData do
Expect.equal (CharParser.parseString runtime x) (ParserResult.CreateSuccess magic) (sprintf "%s was not parsed correctly" x)
Expect.equal (CharParser.parseString parser x) (ParserResult.CreateSuccess magic) (sprintf "%s was not parsed correctly" x)
}
#endif

Expand Down
18 changes: 9 additions & 9 deletions tests/Farkle.Tests/OperatorPrecedenceTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ open Farkle.Samples.FSharp
[<Tests>]
let tests = testList "Operator precedence tests" [
test "The calculator respects operator precedence and associativity" {
let runtime = SimpleMaths.int
let parser = SimpleMaths.int
let testData = [
"5 * 5 - 25", 0
"6 / 2 * (1 + 2)", 9
"125 / 25 / 5", 1
]
for expr, result in testData do
Expect.equal (runtime.Parse expr |> ParserResult.toResult) (Ok result) (sprintf "Parsing '%s' failed" expr)
Expect.equal (parser.Parse expr |> ParserResult.toResult) (Ok result) (sprintf "Parsing '%s' failed" expr)
}

test "Reduce-Reduce conflicts are resolved only on demand" {
Expand Down Expand Up @@ -55,15 +55,15 @@ let tests = testList "Operator precedence tests" [
Expect.isNotNull lrStateMachine "The LR state machine was not built"
Expect.isTrue lrStateMachine.HasConflicts "The LR state machine does not have conflicts"

let runtimeResolved =
let parserResolved =
mkGrammar true
|> GrammarBuilder.build
Expect.isFalse runtimeResolved.IsFailing "Building failed"
Expect.equal (runtimeResolved.Parse "x") (ParserResult.CreateSuccess 2) "The resolved reduction is different than the expected"
Expect.isFalse parserResolved.IsFailing "Building failed"
Expect.equal (parserResolved.Parse "x") (ParserResult.CreateSuccess 2) "The resolved reduction is different than the expected"
}

test "Non-associative operators work" {
let runtime =
let parser =
let number = Terminals.int "Number"
let expr = nonterminal "Expr"
expr.SetProductions(
Expand All @@ -72,14 +72,14 @@ let tests = testList "Operator precedence tests" [
!@ number |> asProduction
)

// Both the string literal and the designtime Farkle literal should be equivalent.
// Both the string literal and the grammar symbol literal should be equivalent.
let opScope = OperatorScope(LeftAssociative("+"), NonAssociative(literal "*"))

expr
|> _.WithOperatorScope(opScope)
|> GrammarBuilder.build

Expect.equal (runtime.Parse "3+4+5") (ParserResult.CreateSuccess 12) "Parsing failed"
expectIsParseFailure (runtime.Parse "3*4*5") "Parsing did not fail"
Expect.equal (parser.Parse "3+4+5") (ParserResult.CreateSuccess 12) "Parsing failed"
expectIsParseFailure (parser.Parse "3*4*5") "Parsing did not fail"
}
]

0 comments on commit 00c1359

Please sign in to comment.