From 00c1359412c7f9c35999835b7e411ef73c5de7af Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Fri, 27 Dec 2024 02:57:52 +0200 Subject: [PATCH] Clean-up and prepare release workflow. (#346) * 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. --- .github/workflows/release.yml | 45 +++++++++++++ .vscode/tasks.json | 5 +- CONTRIBUTING.md | 16 ++--- eng/build.fs | 2 + sample/Farkle.Samples.FSharp/IndentBased.fs | 4 +- .../Builder/Lr/AugmentedSyntaxProvider.cs | 4 ++ .../Lr/OperatorScopeConflictResolver.cs | 1 - src/Farkle/Farkle.csproj | 4 +- src/Farkle/Farkle.fsx | 3 + src/Farkle/Farkle.props | 5 -- tests/Farkle.Tests/GrammarBuilderTests.fs | 66 +++++++++---------- tests/Farkle.Tests/OperatorPrecedenceTests.fs | 18 ++--- 12 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 src/Farkle/Farkle.fsx delete mode 100644 src/Farkle/Farkle.props diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..029f13c7 --- /dev/null +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f92d5981..4a6a8a16 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -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": []} ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa9bc2d9..e8f68d71 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 @@ -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. diff --git a/eng/build.fs b/eng/build.fs index b41fc86c..0dc88280 100644 --- a/eng/build.fs +++ b/eng/build.fs @@ -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 diff --git a/sample/Farkle.Samples.FSharp/IndentBased.fs b/sample/Farkle.Samples.FSharp/IndentBased.fs index 9911fcbf..68fd332f 100644 --- a/sample/Farkle.Samples.FSharp/IndentBased.fs +++ b/sample/Farkle.Samples.FSharp/IndentBased.fs @@ -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 diff --git a/src/Farkle/Builder/Lr/AugmentedSyntaxProvider.cs b/src/Farkle/Builder/Lr/AugmentedSyntaxProvider.cs index 4d1dca68..d5b0589c 100644 --- a/src/Farkle/Builder/Lr/AugmentedSyntaxProvider.cs +++ b/src/Farkle/Builder/Lr/AugmentedSyntaxProvider.cs @@ -152,7 +152,9 @@ private Symbol GetProductionMember(int memberIndex) /// Represents a terminal or nonterminal symbol in an augmented grammar. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] +#pragma warning disable CS9113 // Parameter is unread. public readonly struct Symbol(uint value, AugmentedSyntaxProvider syntax) : IEquatable, IComparable +#pragma warning restore CS9113 // Parameter is unread. { private readonly uint _value = value; @@ -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 +#pragma warning restore CS9113 // Parameter is unread. { public int Index { get; } = index; diff --git a/src/Farkle/Builder/Lr/OperatorScopeConflictResolver.cs b/src/Farkle/Builder/Lr/OperatorScopeConflictResolver.cs index db0708cb..0e719809 100644 --- a/src/Farkle/Builder/Lr/OperatorScopeConflictResolver.cs +++ b/src/Farkle/Builder/Lr/OperatorScopeConflictResolver.cs @@ -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; diff --git a/src/Farkle/Farkle.csproj b/src/Farkle/Farkle.csproj index 87a65c7b..811f6e29 100644 --- a/src/Farkle/Farkle.csproj +++ b/src/Farkle/Farkle.csproj @@ -12,8 +12,8 @@ true - - + + diff --git a/src/Farkle/Farkle.fsx b/src/Farkle/Farkle.fsx new file mode 100644 index 00000000..b27522f4 --- /dev/null +++ b/src/Farkle/Farkle.fsx @@ -0,0 +1,3 @@ +// This file gets imported by F# Interactive. +// Load Farkle's F# API. +#load "Farkle.fs" diff --git a/src/Farkle/Farkle.props b/src/Farkle/Farkle.props deleted file mode 100644 index 719b4e80..00000000 --- a/src/Farkle/Farkle.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/Farkle.Tests/GrammarBuilderTests.fs b/tests/Farkle.Tests/GrammarBuilderTests.fs index b477e380..7db90075 100644 --- a/tests/Farkle.Tests/GrammarBuilderTests.fs +++ b/tests/Farkle.Tests/GrammarBuilderTests.fs @@ -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" } @@ -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. @@ -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" { @@ -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", "[", "}") @@ -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. @@ -332,14 +332,14 @@ let tests = testList "Grammar builder tests" [ let mkTerminal (name, typ: Type, target) = let t = typ.GetMethod(name).CreateDelegate>(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 diff --git a/tests/Farkle.Tests/OperatorPrecedenceTests.fs b/tests/Farkle.Tests/OperatorPrecedenceTests.fs index c9011edd..84e9c1d3 100644 --- a/tests/Farkle.Tests/OperatorPrecedenceTests.fs +++ b/tests/Farkle.Tests/OperatorPrecedenceTests.fs @@ -14,14 +14,14 @@ open Farkle.Samples.FSharp [] 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" { @@ -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( @@ -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" } ]