From cd85a6945056238acdde80ad16def080ec4cc0c8 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Tue, 15 Sep 2026 13:55:12 +0200 Subject: [PATCH 1/3] Run the F# analyzers over every project and script, reporting as SARIF Add an Analyze pipeline to build.fsx that runs fsharp-analyzers with Ionide.Analyzers and G-Research.FSharp.Analyzers over the nine solution projects, build.fsx and OldFsYaccTests.fsx in a single invocation, writing one analysis.sarif for the repository. Generated lexer and parser sources, AssemblyInfo.fs and the test SDK entry point are excluded, since findings there are not fixable here. The analyzer packages live in their own paket group with package storage, so no project references them and the script finds them at a fixed path without knowing the version. Pull requests get a separate ubuntu-only analyze job, and pushes to master run the same steps so code scanning has a baseline to diff against. Both upload the report to GitHub code scanning and do not fail the workflow. Remove tests/fsyacc/oldfsyacctests.fsx.lock, a leftover from when that script pulled FAKE in through paket. --- .config/dotnet-tools.json | 7 + .github/workflows/pull-requests.yml | 23 ++ .github/workflows/push-main.yml | 10 + .gitignore | 3 + build.fsx | 85 +++++ paket.dependencies | 12 +- paket.lock | 7 + tests/fsyacc/oldfsyacctests.fsx.lock | 551 --------------------------- 8 files changed, 146 insertions(+), 552 deletions(-) delete mode 100644 tests/fsyacc/oldfsyacctests.fsx.lock diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2c867bd..6ab2075 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -22,6 +22,13 @@ "fsdocs" ], "rollForward": false + }, + "fsharp-analyzers": { + "version": "0.39.2", + "commands": [ + "fsharp-analyzers" + ], + "rollForward": false } } } \ No newline at end of file diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 7c124a7..dc9af13 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -5,6 +5,10 @@ on: branches: - master +permissions: + contents: read + security-events: write + jobs: build: @@ -24,3 +28,22 @@ jobs: run: dotnet paket restore - name: Build run: dotnet fsi build.fsx + + analyze: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + - name: Setup .NET + uses: actions/setup-dotnet@v6 + - name: Install local tools + run: dotnet tool restore + - name: Paket restore + run: dotnet paket restore + - name: Analyze + run: dotnet fsi build.fsx -p Analyze + continue-on-error: true + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ./analysis.sarif diff --git a/.github/workflows/push-main.yml b/.github/workflows/push-main.yml index 045e3e0..273cad5 100644 --- a/.github/workflows/push-main.yml +++ b/.github/workflows/push-main.yml @@ -9,6 +9,7 @@ permissions: contents: read pages: write id-token: write + security-events: write jobs: build: @@ -31,6 +32,15 @@ jobs: run: dotnet fsi build.fsx -p Release - name: Publish NuGets (if main version changed) run: dotnet nuget push "bin/*.nupkg" -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_KEY }} --skip-duplicate + # A baseline on the default branch, so code scanning can tell a pull request's new findings + # from the ones it inherited. + - name: Analyze + run: dotnet fsi build.fsx -p Analyze + continue-on-error: true + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ./analysis.sarif - name: Build documentation run: dotnet fsi build.fsx -p Docs - name: Upload documentation diff --git a/.gitignore b/.gitignore index d8d141e..cfdc1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -233,3 +233,6 @@ tests/fsyacc/repro_#141/Lexer_fail_option_i.fs tests/fsyacc/repro_#141/Lexer_fail_option_i.fsi tests/fsyacc/repro1885/repro1885.fs tests/fsyacc/repro1885/repro1885.fsi + +# Written by the Analyze pipeline in build.fsx +analysis.sarif diff --git a/build.fsx b/build.fsx index 47be4d5..9cce2cf 100755 --- a/build.fsx +++ b/build.fsx @@ -251,6 +251,79 @@ let pack = ] } +// -------------------------------------------------------------------------------------- +// Analyzers +// -------------------------------------------------------------------------------------- + +/// Every project in the solution. Reading the solution rather than globbing keeps the fixtures +/// under tests/fsyacc out, which are inputs to OldFsYaccTests.fsx rather than code of their own. +let projectsToAnalyze: string list = + File.ReadAllLines(root "FsLexYacc.slnx") + |> Array.choose (fun line -> + let m = Text.RegularExpressions.Regex.Match(line, " Array.toList + +/// The scripts the analyzers run over: the only F# in this repository no project compiles. +let scriptsToAnalyze: string list = + [ "build.fsx"; "tests/fsyacc/OldFsYaccTests.fsx" ] + +/// Restored by paket into the Analyzers group, see paket.dependencies. +let analyzerPaths: string list = + [ "Ionide.Analyzers"; "G-Research.FSharp.Analyzers" ] + |> List.map (fun package -> + root + "packages" + "analyzers" + package + "analyzers" + "dotnet" + "fs") + +let analysisReport = root "analysis.sarif" + +/// One run over every project and script, so a single SARIF covers the repository. +/// +/// The tool only exits non-zero for error-severity findings, so a run full of warnings still +/// passes; the findings are read from the report, or from the Code Scanning tab in CI. +let analyze = + async { + let! _ = deleteFiles [ "analysis.sarif" ] + + return! + exec + "dotnet" + [ + "fsharp-analyzers" + for path in analyzerPaths do + "--analyzers-path" + path + for project in projectsToAnalyze do + "--project" + root project + for script in scriptsToAnalyze do + "--script" + root script + // Not ours to fix: what fslex and fsyacc generate, what this script generates, + // the test SDK entry point, and the scripts NuGet writes per `#r "nuget: ..."`. + "--exclude-files" + // Globs, because the tool matches these against absolute paths. + for generated in generatedSources @ generatedTestSources do + "**/" + Path.GetFileName generated + "**/AssemblyInfo.fs" + "**/Microsoft.NET.Test.Sdk.Program.fs" + "**/.packagemanagement/**" + "--configuration" + "Release" + // With a trailing separator, or the tool reads the last segment as a file name + // and reports every path as "FsLexYacc/...", which GitHub cannot link. + "--code-root" + root + string Path.DirectorySeparatorChar + "--report" + analysisReport + ] + } + // -------------------------------------------------------------------------------------- // Pipelines // -------------------------------------------------------------------------------------- @@ -301,4 +374,16 @@ pipeline "Docs" { runIfOnlySpecified true } +// The generated sources have to exist before a project can be type checked, so the tools and +// libraries are built first, the same way the Build pipeline does. +pipeline "Analyze" { + workingDir root + restore + stage "AssemblyInfo" { run generateAssemblyInfo } + stage "BuildTools" { run buildTools } + stage "BuildLibraries" { run buildLibraries } + stage "Analyze" { run analyze } + runIfOnlySpecified true +} + tryPrintPipelineCommandHelp () diff --git a/paket.dependencies b/paket.dependencies index 201496e..81d5d37 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -9,4 +9,14 @@ nuget Microsoft.SourceLink.GitHub copy_local: true nuget Expecto ~> 9.0 nuget Expecto.FsCheck nuget Microsoft.NET.Test.Sdk -nuget YoloDev.Expecto.TestSdk \ No newline at end of file +nuget YoloDev.Expecto.TestSdk + +// Only the Analyze pipeline in build.fsx uses these, so they live in their own group and no +// project references them. A fixed on-disk location, rather than the NuGet cache, so the script +// can point fsharp-analyzers at them without knowing the version. +group Analyzers + source https://api.nuget.org/v3/index.json + storage: packages + + nuget Ionide.Analyzers 0.19.0 + nuget G-Research.FSharp.Analyzers 0.25.0 diff --git a/paket.lock b/paket.lock index d8dd440..d25fccb 100644 --- a/paket.lock +++ b/paket.lock @@ -37,3 +37,10 @@ NUGET Expecto (>= 9.0 < 10.0) - restriction: || (== net10.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) FSharp.Core (>= 4.6.2) - restriction: || (== net10.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) System.Collections.Immutable (>= 6.0) - restriction: || (== net10.0) (&& (== netstandard2.0) (>= netcoreapp3.1)) + +GROUP Analyzers +STORAGE: PACKAGES +NUGET + remote: https://api.nuget.org/v3/index.json + G-Research.FSharp.Analyzers (0.25) + Ionide.Analyzers (0.19) diff --git a/tests/fsyacc/oldfsyacctests.fsx.lock b/tests/fsyacc/oldfsyacctests.fsx.lock deleted file mode 100644 index c3c22b8..0000000 --- a/tests/fsyacc/oldfsyacctests.fsx.lock +++ /dev/null @@ -1,551 +0,0 @@ -STORAGE: NONE -RESTRICTION: == net6.0 -NUGET - remote: https://api.nuget.org/v3/index.json - Fake.Core.Context (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Environment (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.FakeVar (5.20.4) - Fake.Core.Context (>= 5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Process (5.20.4) - Fake.Core.Environment (>= 5.20.4) - Fake.Core.FakeVar (>= 5.20.4) - Fake.Core.String (>= 5.20.4) - Fake.Core.Trace (>= 5.20.4) - Fake.IO.FileSystem (>= 5.20.4) - FSharp.Core (>= 4.7.2) - System.Collections.Immutable (>= 1.7.1) - Fake.Core.String (5.20.4) - FSharp.Core (>= 4.7.2) - Fake.Core.Trace (5.20.4) - Fake.Core.Environment (>= 5.20.4) - Fake.Core.FakeVar (>= 5.20.4) - FSharp.Core (>= 4.7.2) - Fake.DotNet.Fsc (5.20.4) - Fake.Core.Process (>= 5.20.4) - Fake.Core.Trace (>= 5.20.4) - Fake.IO.FileSystem (>= 5.20.4) - FSharp.Compiler.Service (>= 37.0) - FSharp.Core (>= 4.7.2) - Fake.IO.FileSystem (5.20.4) - Fake.Core.String (>= 5.20.4) - FSharp.Core (>= 4.7.2) - FSharp.Compiler.Service (40.0) - FSharp.Core (5.0.2) - Microsoft.Build.Framework (>= 16.9) - Microsoft.Build.Tasks.Core (>= 16.9) - Microsoft.Build.Utilities.Core (>= 16.9) - System.Buffers (>= 4.5.1) - System.Collections.Immutable (>= 5.0) - System.Diagnostics.Process (>= 4.3) - System.Diagnostics.TraceSource (>= 4.3) - System.Linq.Expressions (>= 4.3) - System.Linq.Queryable (>= 4.3) - System.Memory (>= 4.5.4) - System.Net.Requests (>= 4.3) - System.Net.Security (>= 4.3) - System.Reflection.Emit (>= 4.3) - System.Reflection.Metadata (>= 5.0) - System.Reflection.TypeExtensions (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Runtime.Loader (>= 4.3) - System.Security.Claims (>= 4.3) - System.Security.Cryptography.Algorithms (>= 4.3) - System.Security.Principal (>= 4.3) - System.Threading.Tasks.Parallel (>= 4.3) - System.Threading.Thread (>= 4.3) - System.Threading.ThreadPool (>= 4.3) - FSharp.Core (5.0.2) - Microsoft.Build.Framework (17.4) - Microsoft.Win32.Registry (>= 5.0) - System.Security.Permissions (>= 6.0) - Microsoft.Build.Tasks.Core (17.4) - Microsoft.Build.Framework (>= 17.4) - Microsoft.Build.Utilities.Core (>= 17.4) - Microsoft.NET.StringTools (>= 17.4) - Microsoft.Win32.Registry (>= 5.0) - System.CodeDom (>= 6.0) - System.Collections.Immutable (>= 6.0) - System.Reflection.Metadata (>= 6.0) - System.Resources.Extensions (>= 6.0) - System.Security.Cryptography.Pkcs (>= 6.0.1) - System.Security.Cryptography.Xml (>= 6.0) - System.Security.Permissions (>= 6.0) - System.Threading.Tasks.Dataflow (>= 6.0) - Microsoft.Build.Utilities.Core (17.4) - Microsoft.Build.Framework (>= 17.4) - Microsoft.NET.StringTools (>= 17.4) - Microsoft.Win32.Registry (>= 5.0) - System.Collections.Immutable (>= 6.0) - System.Configuration.ConfigurationManager (>= 6.0) - System.Security.Permissions (>= 6.0) - System.Text.Encoding.CodePages (>= 6.0) - Microsoft.NET.StringTools (17.4) - System.Memory (>= 4.5.5) - System.Runtime.CompilerServices.Unsafe (>= 6.0) - Microsoft.NETCore.Platforms (7.0) - Microsoft.NETCore.Targets (5.0) - Microsoft.Win32.Primitives (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - Microsoft.Win32.Registry (5.0) - System.Security.AccessControl (>= 5.0) - System.Security.Principal.Windows (>= 5.0) - Microsoft.Win32.SystemEvents (7.0) - runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.debian.9-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.fedora.27-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.fedora.28-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.native.System (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - runtime.native.System.Net.Http (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - runtime.native.System.Net.Security (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - runtime.native.System.Security.Cryptography.Apple (4.3.1) - runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple (>= 4.3.1) - runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.debian.9-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.fedora.27-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.fedora.28-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.opensuse.42.3-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.ubuntu.18.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.3) - runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.opensuse.42.3-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple (4.3.1) - runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - runtime.ubuntu.18.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3) - System.Buffers (4.5.1) - System.CodeDom (7.0) - System.Collections (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Collections.Concurrent (4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Diagnostics.Tracing (>= 4.3) - System.Globalization (>= 4.3) - System.Reflection (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Collections.Immutable (7.0) - System.Runtime.CompilerServices.Unsafe (>= 6.0) - System.Configuration.ConfigurationManager (7.0) - System.Security.Cryptography.ProtectedData (>= 7.0) - System.Security.Permissions (>= 7.0) - System.Diagnostics.Debug (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Diagnostics.DiagnosticSource (7.0) - System.Runtime.CompilerServices.Unsafe (>= 6.0) - System.Diagnostics.Process (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.Win32.Primitives (>= 4.3) - Microsoft.Win32.Registry (>= 4.3) - runtime.native.System (>= 4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Globalization (>= 4.3) - System.IO (>= 4.3) - System.IO.FileSystem (>= 4.3) - System.IO.FileSystem.Primitives (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Text.Encoding.Extensions (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Threading.Thread (>= 4.3) - System.Threading.ThreadPool (>= 4.3) - System.Diagnostics.TraceSource (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - runtime.native.System (>= 4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Globalization (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Threading (>= 4.3) - System.Diagnostics.Tracing (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Drawing.Common (7.0) - Microsoft.Win32.SystemEvents (>= 7.0) - System.Formats.Asn1 (7.0) - System.Globalization (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Globalization.Calendars (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Globalization (>= 4.3) - System.Runtime (>= 4.3) - System.Globalization.Extensions (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - System.Globalization (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.IO (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.IO.FileSystem (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.IO (>= 4.3) - System.IO.FileSystem.Primitives (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.IO.FileSystem.Primitives (4.3) - System.Runtime (>= 4.3) - System.Linq (4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Linq.Expressions (4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Globalization (>= 4.3) - System.IO (>= 4.3) - System.Linq (>= 4.3) - System.ObjectModel (>= 4.3) - System.Reflection (>= 4.3) - System.Reflection.Emit (>= 4.3) - System.Reflection.Emit.ILGeneration (>= 4.3) - System.Reflection.Emit.Lightweight (>= 4.3) - System.Reflection.Extensions (>= 4.3) - System.Reflection.Primitives (>= 4.3) - System.Reflection.TypeExtensions (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Threading (>= 4.3) - System.Linq.Queryable (4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Linq (>= 4.3) - System.Linq.Expressions (>= 4.3) - System.Reflection (>= 4.3) - System.Reflection.Extensions (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Memory (4.5.5) - System.Net.Http (4.3.4) - Microsoft.NETCore.Platforms (>= 1.1.1) - runtime.native.System (>= 4.3) - runtime.native.System.Net.Http (>= 4.3) - runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.2) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Diagnostics.DiagnosticSource (>= 4.3) - System.Diagnostics.Tracing (>= 4.3) - System.Globalization (>= 4.3) - System.Globalization.Extensions (>= 4.3) - System.IO (>= 4.3) - System.IO.FileSystem (>= 4.3) - System.Net.Primitives (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Security.Cryptography.Algorithms (>= 4.3) - System.Security.Cryptography.Encoding (>= 4.3) - System.Security.Cryptography.OpenSsl (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Security.Cryptography.X509Certificates (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Net.Primitives (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - System.Runtime (>= 4.3.1) - System.Runtime.Handles (>= 4.3) - System.Net.Requests (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Diagnostics.Tracing (>= 4.3) - System.Globalization (>= 4.3) - System.IO (>= 4.3) - System.Net.Http (>= 4.3) - System.Net.Primitives (>= 4.3) - System.Net.WebHeaderCollection (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Net.Security (4.3.2) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.Win32.Primitives (>= 4.3) - runtime.native.System (>= 4.3) - runtime.native.System.Net.Security (>= 4.3) - runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.2) - System.Collections (>= 4.3) - System.Collections.Concurrent (>= 4.3) - System.Diagnostics.Tracing (>= 4.3) - System.Globalization (>= 4.3) - System.Globalization.Extensions (>= 4.3) - System.IO (>= 4.3) - System.Net.Primitives (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Security.Claims (>= 4.3) - System.Security.Cryptography.Algorithms (>= 4.3) - System.Security.Cryptography.Encoding (>= 4.3) - System.Security.Cryptography.OpenSsl (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Security.Cryptography.X509Certificates (>= 4.3) - System.Security.Principal (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Threading.ThreadPool (>= 4.3) - System.Net.WebHeaderCollection (4.3) - System.Collections (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.ObjectModel (4.3) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Threading (>= 4.3) - System.Reflection (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.IO (>= 4.3) - System.Reflection.Primitives (>= 4.3) - System.Runtime (>= 4.3) - System.Reflection.Emit (4.7) - System.Reflection.Emit.ILGeneration (4.7) - System.Reflection.Emit.Lightweight (4.7) - System.Reflection.Extensions (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Reflection (>= 4.3) - System.Runtime (>= 4.3) - System.Reflection.Metadata (7.0) - System.Collections.Immutable (>= 7.0) - System.Reflection.Primitives (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Reflection.TypeExtensions (4.7) - System.Resources.Extensions (7.0) - System.Resources.ResourceManager (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Globalization (>= 4.3) - System.Reflection (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - System.Runtime.CompilerServices.Unsafe (6.0) - System.Runtime.Extensions (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1.1) - Microsoft.NETCore.Targets (>= 1.1.3) - System.Runtime (>= 4.3.1) - System.Runtime.Handles (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Runtime.InteropServices (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Reflection (>= 4.3) - System.Reflection.Primitives (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.Loader (4.3) - System.IO (>= 4.3) - System.Reflection (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Numerics (4.3) - System.Globalization (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Security.AccessControl (6.0) - System.Security.Claims (4.3) - System.Collections (>= 4.3) - System.Globalization (>= 4.3) - System.IO (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Security.Principal (>= 4.3) - System.Security.Cryptography.Algorithms (4.3.1) - Microsoft.NETCore.Platforms (>= 1.1) - runtime.native.System.Security.Cryptography.Apple (>= 4.3.1) - runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.2) - System.Collections (>= 4.3) - System.IO (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Runtime.Numerics (>= 4.3) - System.Security.Cryptography.Encoding (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Security.Cryptography.Cng (5.0) - System.Formats.Asn1 (>= 5.0) - System.Security.Cryptography.Csp (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - System.IO (>= 4.3) - System.Reflection (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Security.Cryptography.Algorithms (>= 4.3) - System.Security.Cryptography.Encoding (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading (>= 4.3) - System.Security.Cryptography.Encoding (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3) - System.Collections (>= 4.3) - System.Collections.Concurrent (>= 4.3) - System.Linq (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Security.Cryptography.OpenSsl (5.0) - System.Formats.Asn1 (>= 5.0) - System.Security.Cryptography.Pkcs (7.0) - System.Formats.Asn1 (>= 7.0) - System.Security.Cryptography.Primitives (4.3) - System.Diagnostics.Debug (>= 4.3) - System.Globalization (>= 4.3) - System.IO (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Security.Cryptography.ProtectedData (7.0) - System.Security.Cryptography.X509Certificates (4.3.2) - Microsoft.NETCore.Platforms (>= 1.1) - runtime.native.System (>= 4.3) - runtime.native.System.Net.Http (>= 4.3) - runtime.native.System.Security.Cryptography.OpenSsl (>= 4.3.2) - System.Collections (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Globalization (>= 4.3) - System.Globalization.Calendars (>= 4.3) - System.IO (>= 4.3) - System.IO.FileSystem (>= 4.3) - System.IO.FileSystem.Primitives (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Runtime.InteropServices (>= 4.3) - System.Runtime.Numerics (>= 4.3) - System.Security.Cryptography.Algorithms (>= 4.3) - System.Security.Cryptography.Cng (>= 4.3) - System.Security.Cryptography.Csp (>= 4.3) - System.Security.Cryptography.Encoding (>= 4.3) - System.Security.Cryptography.OpenSsl (>= 4.3) - System.Security.Cryptography.Primitives (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading (>= 4.3) - System.Security.Cryptography.Xml (7.0) - System.Security.Cryptography.Pkcs (>= 7.0) - System.Security.Permissions (7.0) - System.Windows.Extensions (>= 7.0) - System.Security.Principal (4.3) - System.Runtime (>= 4.3) - System.Security.Principal.Windows (5.0) - System.Text.Encoding (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Text.Encoding.CodePages (7.0) - System.Runtime.CompilerServices.Unsafe (>= 6.0) - System.Text.Encoding.Extensions (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Text.Encoding (>= 4.3) - System.Threading (4.3) - System.Runtime (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Threading.Tasks (4.3) - Microsoft.NETCore.Platforms (>= 1.1) - Microsoft.NETCore.Targets (>= 1.1) - System.Runtime (>= 4.3) - System.Threading.Tasks.Dataflow (7.0) - System.Threading.Tasks.Parallel (4.3) - System.Collections.Concurrent (>= 4.3) - System.Diagnostics.Debug (>= 4.3) - System.Diagnostics.Tracing (>= 4.3) - System.Resources.ResourceManager (>= 4.3) - System.Runtime (>= 4.3) - System.Runtime.Extensions (>= 4.3) - System.Threading (>= 4.3) - System.Threading.Tasks (>= 4.3) - System.Threading.Thread (4.3) - System.Runtime (>= 4.3) - System.Threading.ThreadPool (4.3) - System.Runtime (>= 4.3) - System.Runtime.Handles (>= 4.3) - System.Windows.Extensions (7.0) - System.Drawing.Common (>= 7.0) From 32b1bf567e0b1141c5b8679d91b90436042afc45 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Tue, 15 Sep 2026 14:12:38 +0200 Subject: [PATCH 2/3] Fix every finding the analyzers report The first analyzer run reported 153 findings, all warnings or lower. This resolves them so the code scanning baseline starts clean and a new finding on a pull request stands out. Most of it is mechanical: postfix generic syntax throughout the runtime and the tools, printf-style calls without format specifiers replaced by plain writes, ordinal string comparisons made explicit, list functions instead of Seq over lists, and a type argument on each bare `string` call. A few changes touch the shape of the code. The Rule, Production and ExplicitPrec union cases have named fields. Action is qualified access only, because its Error case shadowed Result.Error. Associativity and Domain are struct unions. TryDecodeUnicodeCategory returns a value option, so the UnicodeCategoryAP active pattern can be struct-returning without a conversion. The JSON example matches on the parse result rather than reading .Value. No behaviour changes. The fsyacc console hint about precedences reads the same, its %% escapes just became single % now that it is no longer a format string. --- build.fsx | 29 ++--- src/Common/Arg.fs | 2 +- src/Common/Arg.fsi | 6 +- src/FsLex.Core/fslexast.fs | 13 +- src/FsLex.Core/fslexdriver.fs | 5 +- src/FsLex/fslex.fs | 4 +- src/FsLexYacc.Runtime/Lexing.fs | 16 +-- src/FsLexYacc.Runtime/Lexing.fsi | 16 +-- src/FsLexYacc.Runtime/Parsing.fs | 32 ++--- src/FsLexYacc.Runtime/Parsing.fsi | 18 +-- src/FsYacc.Core/fsyaccast.fs | 164 +++++++++++++------------ src/FsYacc.Core/fsyaccdriver.fs | 18 +-- src/FsYacc/fsyacc.fs | 2 +- tests/FsLex.Core.Tests/UnicodeTests.fs | 6 +- tests/JsonLexAndYaccExample/Program.fs | 11 +- tests/LexAndYaccMiniProject/Program.fs | 2 +- tests/fsyacc/OldFsYaccTests.fsx | 8 +- 17 files changed, 185 insertions(+), 167 deletions(-) diff --git a/build.fsx b/build.fsx index 9cce2cf..d2d07ba 100755 --- a/build.fsx +++ b/build.fsx @@ -40,7 +40,8 @@ type Release = /// An entry is a "#### - " heading followed by "* " bullets, and the date is /// allowed to read "Unreleased" while the version is still in flight. let release: Release = - let isHeading (line: string) = line.StartsWith "####" + let isHeading (line: string) = + line.StartsWith("####", StringComparison.Ordinal) let lines = File.ReadAllLines(root "RELEASE_NOTES.md") let headingIndex = Array.findIndex isHeading lines @@ -112,19 +113,19 @@ let writeAssemblyInfo (project: string) (product: string) = "namespace System" "open System.Reflection" "" - $"[]" - $"[]" - $"[]" - $"[]" - $"[]" + $"[]" + $"[]" + $"[]" + $"[]" + $"[]" "do ()" "" "module internal AssemblyVersionInformation =" - $" let [] AssemblyTitle = \"{project}\"" - $" let [] AssemblyProduct = \"{product}\"" - $" let [] AssemblyDescription = \"{summary}\"" - $" let [] AssemblyVersion = \"{version}\"" - $" let [] AssemblyFileVersion = \"{version}\"" + $" let [] AssemblyTitle = \"%s{project}\"" + $" let [] AssemblyProduct = \"%s{product}\"" + $" let [] AssemblyDescription = \"%s{summary}\"" + $" let [] AssemblyVersion = \"%s{version}\"" + $" let [] AssemblyFileVersion = \"%s{version}\"" "" ] |> String.concat Environment.NewLine @@ -226,8 +227,8 @@ let pack = "Release" "-o" "bin" - $"/p:PackageReleaseNotes={releaseNotes}" - $"/p:PackageVersion={release.NugetVersion}" + $"/p:PackageReleaseNotes=%s{releaseNotes}" + $"/p:PackageVersion=%s{release.NugetVersion}" ] if projectPackages <> 0 then @@ -318,7 +319,7 @@ let analyze = // With a trailing separator, or the tool reads the last segment as a file name // and reports every path as "FsLexYacc/...", which GitHub cannot link. "--code-root" - root + string Path.DirectorySeparatorChar + root + Path.DirectorySeparatorChar.ToString() "--report" analysisReport ] diff --git a/src/Common/Arg.fs b/src/Common/Arg.fs index bc96f69..2be43b2 100644 --- a/src/Common/Arg.fs +++ b/src/Common/Arg.fs @@ -79,7 +79,7 @@ type ArgParser() = pendline "display this list of options" sbuf.ToString() - static member ParsePartial(cursor: ref, argv, arguments: seq, ?otherArgs, ?usageText) = + static member ParsePartial(cursor: int ref, argv, arguments: ArgInfo seq, ?otherArgs, ?usageText) = let other = defaultArg otherArgs (fun _ -> ()) let usageText = defaultArg usageText "" let nargs = Array.length argv diff --git a/src/Common/Arg.fsi b/src/Common/Arg.fsi index 38c8cd2..d2333e4 100644 --- a/src/Common/Arg.fsi +++ b/src/Common/Arg.fsi @@ -32,15 +32,15 @@ type ArgParser = /// Parse some of the arguments given by 'argv', starting at the given position [] static member ParsePartial: - cursor: int ref * argv: string[] * arguments: seq * ?otherArgs: (string -> unit) * ?usageText: string -> unit + cursor: int ref * argv: string array * arguments: ArgInfo seq * ?otherArgs: (string -> unit) * ?usageText: string -> unit /// Parse the arguments given by System.Environment.GetCommandLineArgs() /// according to the argument processing specifications "specs". /// Args begin with "-". Non-arguments are passed to "f" in /// order. "use" is printed as part of the usage line if an error occurs. - static member Parse: arguments: seq * ?otherArgs: (string -> unit) * ?usageText: string -> unit + static member Parse: arguments: ArgInfo seq * ?otherArgs: (string -> unit) * ?usageText: string -> unit #endif /// Prints the help for each argument. - static member Usage: arguments: seq * ?usage: string -> unit + static member Usage: arguments: ArgInfo seq * ?usage: string -> unit diff --git a/src/FsLex.Core/fslexast.fs b/src/FsLex.Core/fslexast.fs index 29cd345..cc4b57d 100644 --- a/src/FsLex.Core/fslexast.fs +++ b/src/FsLex.Core/fslexast.fs @@ -70,15 +70,16 @@ let EncodeUnicodeCategory s : Parser = else failwithf "invalid Unicode category: '%s'" s -let TryDecodeUnicodeCategory (x: Alphabet) : UnicodeCategory option = +let TryDecodeUnicodeCategory (x: Alphabet) : UnicodeCategory voption = let maybeUnicodeCategory = x - encodedUnicodeCategoryBase |> int32 |> enum if UnicodeCategory.IsDefined(typeof, maybeUnicodeCategory) then - Some maybeUnicodeCategory + ValueSome maybeUnicodeCategory else - None + ValueNone +[] let (|UnicodeCategoryAP|_|) (x: Alphabet) = TryDecodeUnicodeCategory x let IsUnicodeCategory (x: Alphabet) = @@ -249,7 +250,7 @@ type NfaNodeMap() = let node: NfaNode = { Id = nodeId - Name = string nodeId + Name = string nodeId Transitions = trDict Accepted = ac } @@ -282,7 +283,7 @@ let LexerStateToNfa ctx (macros: Map) (clauses: Clause list) = UnicodeCategory.TitlecaseLetter ] - let isCasedLetterCategory = allCasedCategories |> Seq.contains uc + let isCasedLetterCategory = allCasedCategories |> List.contains uc if isCasedLetterCategory then let trs = @@ -446,7 +447,7 @@ let NfaToDfa (nfaNodeMap: NfaNodeMap) nfaStartNode = //printfn "n.Id = %A, #Epsilon = %d" n.Id tr.Length tr |> List.iter (EClosure1 acc) - let EClosure (moves: list) = + let EClosure (moves: NodeId list) = let acc = NfaNodeIdSetBuilder(HashIdentity.Structural) for i in moves do diff --git a/src/FsLex.Core/fslexdriver.fs b/src/FsLex.Core/fslexdriver.fs index 04a8d67..1bad0a6 100644 --- a/src/FsLex.Core/fslexdriver.fs +++ b/src/FsLex.Core/fslexdriver.fs @@ -6,6 +6,7 @@ open System.IO open FSharp.Text.Lexing open System.Collections.Generic +[] type Domain = | Unicode | ASCII @@ -24,8 +25,8 @@ type GeneratorState = domain: Domain } -type PerRuleData = list> -type DfaNodes = list +type PerRuleData = (DfaNode * Code seq) list +type DfaNodes = DfaNode list type Writer(outputFileName, outputFileInterface) = let os = File.CreateText outputFileName :> TextWriter diff --git a/src/FsLex/fslex.fs b/src/FsLex/fslex.fs index 39fbeff..4a9df4e 100644 --- a/src/FsLex/fslex.fs +++ b/src/FsLex/fslex.fs @@ -91,11 +91,11 @@ let main () = exit 1 - printfn "compiling to dfas (can take a while...)" + stdout.WriteLine "compiling to dfas (can take a while...)" let perRuleData, dfaNodes = compileSpec spec parseContext printfn "%d states" dfaNodes.Length - printfn "writing output" + stdout.WriteLine "writing output" let output = match out with diff --git a/src/FsLexYacc.Runtime/Lexing.fs b/src/FsLexYacc.Runtime/Lexing.fs index bd70b85..468da97 100644 --- a/src/FsLexYacc.Runtime/Lexing.fs +++ b/src/FsLexYacc.Runtime/Lexing.fs @@ -152,7 +152,7 @@ and [] LexBuffer<'char>(filler: LexBufferFiller<'char>) as this = and set v = lexemeLength <- v member _.Buffer - with internal get (): 'char[] = buffer + with internal get (): 'char array = buffer and internal set v = buffer <- v member _.BufferMaxScanLength @@ -193,7 +193,7 @@ and [] LexBuffer<'char>(filler: LexBufferFiller<'char>) as this = buffer <- repl static member FromReadFunctions - (syncRead: ('char[] * int * int -> int) option, asyncRead: ('char[] * int * int -> Async) option) + (syncRead: ('char array * int * int -> int) option, asyncRead: ('char array * int * int -> Async) option) : LexBuffer<'char> = let extension = Array.zeroCreate 4096 @@ -224,10 +224,10 @@ and [] LexBuffer<'char>(filler: LexBufferFiller<'char>) as this = LexBuffer<_>(fillers) // A full type signature is required on this method because it is used at more specific types within its own scope - static member FromFunction(f: 'char[] * int * int -> int) : LexBuffer<'char> = + static member FromFunction(f: 'char array * int * int -> int) : LexBuffer<'char> = LexBuffer<_>.FromReadFunctions(Some(f), None) - static member FromAsyncFunction(f: 'char[] * int * int -> Async) : LexBuffer<'char> = + static member FromAsyncFunction(f: 'char array * int * int -> Async) : LexBuffer<'char> = LexBuffer<_>.FromReadFunctions(None, Some(f)) static member FromCharFunction f : LexBuffer = @@ -245,7 +245,7 @@ and [] LexBuffer<'char>(filler: LexBufferFiller<'char>) as this = n) // A full type signature is required on this method because it is used at more specific types within its own scope - static member FromArray(s: 'char[]) : LexBuffer<'char> = + static member FromArray(s: 'char array) : LexBuffer<'char> = let lexBuffer = LexBuffer<_> { @@ -285,7 +285,7 @@ module GenericImplFragments = lexBuffer.LexemeLength <- 0 lexBuffer.BufferAcceptAction <- -1 - let afterRefill (trans: uint16[] array, sentinel, lexBuffer: LexBuffer<_>, scanUntilSentinel, endOfScan, state, eofPos) = + let afterRefill (trans: uint16 array array, sentinel, lexBuffer: LexBuffer<_>, scanUntilSentinel, endOfScan, state, eofPos) = // end of file occurs if we couldn't extend the buffer if lexBuffer.BufferScanLength = lexBuffer.BufferMaxScanLength then let snew = int trans.[state].[eofPos] // == EOF @@ -309,7 +309,7 @@ module GenericImplFragments = open GenericImplFragments [] -type AsciiTables(trans: uint16[] array, accept: uint16[]) = +type AsciiTables(trans: uint16 array array, accept: uint16 array) = let rec scanUntilSentinel (lexBuffer, state) = let sentinel = 255 * 256 + 255 // Return an endOfScan after consuming the input @@ -378,7 +378,7 @@ type AsciiTables(trans: uint16[] array, accept: uint16[]) = static member Create(trans, accept) = AsciiTables(trans, accept) [] -type UnicodeTables(trans: uint16[] array, accept: uint16[]) = +type UnicodeTables(trans: uint16 array array, accept: uint16 array) = let sentinel = 255 * 256 + 255 let numUnicodeCategories = 30 let numLowUnicodeChars = 128 diff --git a/src/FsLexYacc.Runtime/Lexing.fsi b/src/FsLexYacc.Runtime/Lexing.fsi index 4315171..07f3128 100644 --- a/src/FsLexYacc.Runtime/Lexing.fsi +++ b/src/FsLexYacc.Runtime/Lexing.fsi @@ -101,25 +101,25 @@ type LexBuffer<'char> = member DiscardInput: unit -> unit /// Create a lex buffer suitable for byte lexing that reads characters from the given array - static member FromBytes: byte[] -> LexBuffer + static member FromBytes: byte array -> LexBuffer /// Create a lex buffer suitable for Unicode lexing that reads characters from the given array - static member FromChars: char[] -> LexBuffer + static member FromChars: char array -> LexBuffer /// Create a lex buffer suitable for Unicode lexing that reads characters from the given string static member FromString: string -> LexBuffer /// Create a lex buffer that reads character or byte inputs by using the given function - static member FromFunction: ('char[] * int * int -> int) -> LexBuffer<'char> + static member FromFunction: ('char array * int * int -> int) -> LexBuffer<'char> /// Create a lex buffer that asynchronously reads character or byte inputs by using the given function - static member FromAsyncFunction: ('char[] * int * int -> Async) -> LexBuffer<'char> + static member FromAsyncFunction: ('char array * int * int -> Async) -> LexBuffer<'char> [.FromFunction instead")>] - static member FromCharFunction: (char[] -> int -> int) -> LexBuffer + static member FromCharFunction: (char array -> int -> int) -> LexBuffer [.FromFunction instead")>] - static member FromByteFunction: (byte[] -> int -> int) -> LexBuffer + static member FromByteFunction: (byte array -> int -> int) -> LexBuffer #if !FABLE_COMPILER @@ -134,7 +134,7 @@ type LexBuffer<'char> = /// The type of tables for an ascii lexer generated by fslex. [] type AsciiTables = - static member Create: uint16[] array * uint16[] -> AsciiTables + static member Create: uint16 array array * uint16 array -> AsciiTables /// Interpret tables for an ascii lexer generated by fslex. member Interpret: initialState: int * LexBuffer -> int @@ -147,7 +147,7 @@ type AsciiTables = [] type UnicodeTables = - static member Create: uint16[] array * uint16[] -> UnicodeTables + static member Create: uint16 array array * uint16 array -> UnicodeTables /// Interpret tables for a unicode lexer generated by fslex. member Interpret: initialState: int * LexBuffer -> int diff --git a/src/FsLexYacc.Runtime/Parsing.fs b/src/FsLexYacc.Runtime/Parsing.fs index 9e8216b..ad65c85 100644 --- a/src/FsLexYacc.Runtime/Parsing.fs +++ b/src/FsLexYacc.Runtime/Parsing.fs @@ -53,19 +53,19 @@ type ParseErrorContext<'tok> // This is the data structure emitted as code by FSYACC. type Tables<'tok> = - { reductions: (IParseState -> obj)[] + { reductions: (IParseState -> obj) array endOfInputTag: int tagOfToken: 'tok -> int dataOfToken: 'tok -> obj - actionTableElements: uint16[] - actionTableRowOffsets: uint16[] - reductionSymbolCounts: uint16[] - immediateActions: uint16[] - gotos: uint16[] - sparseGotoTableRowOffsets: uint16[] - stateToProdIdxsTableElements: uint16[] - stateToProdIdxsTableRowOffsets: uint16[] - productionToNonTerminalTable: uint16[] + actionTableElements: uint16 array + actionTableRowOffsets: uint16 array + reductionSymbolCounts: uint16 array + immediateActions: uint16 array + gotos: uint16 array + sparseGotoTableRowOffsets: uint16 array + stateToProdIdxsTableElements: uint16 array + stateToProdIdxsTableRowOffsets: uint16 array + productionToNonTerminalTable: uint16 array /// For fsyacc.exe, this entry is filled in by context from the generated parser file. If no 'parse_error' function /// is defined by the user then ParseHelpers.parse_error is used by default (ParseHelpers is opened /// at the top of the generated parser file) @@ -141,7 +141,7 @@ module Implementation = //------------------------------------------------------------------------- // Read the tables written by FSYACC. - type AssocTable(elemTab:uint16[], offsetTab:uint16[], initialCacheCapacity:int) = + type AssocTable(elemTab:uint16 array, offsetTab:uint16 array, initialCacheCapacity:int) = // Cache capacity is configurable (issue #54): two AssocTables are constructed per Interpret // call, so the historical fixed 2000-capacity dominated allocation for parsers run over many // small inputs. The capacity is supplied by the caller (Tables.Interpret), defaulting to @@ -194,7 +194,7 @@ module Implementation = [ for i in firstElemNumber .. (firstElemNumber+numberOfElementsInAssoc-1) -> (int elemTab.[i*2], int elemTab.[i*2+1]) ], defaultValueOfAssoc - type IdxToIdxListTable(elemTab:uint16[], offsetTab:uint16[]) = + type IdxToIdxListTable(elemTab:uint16 array, offsetTab:uint16 array) = // Read all entries in a row of the table member _.ReadAll(n) = @@ -253,10 +253,10 @@ module Implementation = let mutable eofCountDown = 20 // Number of EOFs to supply at the end for error recovery // The 100 here means a maximum of 100 elements for each rule - let ruleStartPoss = (Array.zeroCreate 100 : Position[]) - let ruleEndPoss = (Array.zeroCreate 100 : Position[]) - let ruleValues = (Array.zeroCreate 100 : obj[]) - let lhsPos = (Array.zeroCreate 2 : Position[]) + let ruleStartPoss = (Array.zeroCreate 100 : Position array) + let ruleEndPoss = (Array.zeroCreate 100 : Position array) + let ruleValues = (Array.zeroCreate 100 : obj array) + let lhsPos = (Array.zeroCreate 2 : Position array) let reductions = tables.reductions let actionTable = AssocTable(tables.actionTableElements, tables.actionTableRowOffsets, assocCacheInitialCapacity) diff --git a/src/FsLexYacc.Runtime/Parsing.fsi b/src/FsLexYacc.Runtime/Parsing.fsi index f50b6ec..425e1c3 100644 --- a/src/FsLexYacc.Runtime/Parsing.fsi +++ b/src/FsLexYacc.Runtime/Parsing.fsi @@ -73,31 +73,31 @@ type Tables<'tok> = dataOfToken: 'tok -> obj /// The sparse action table elements - actionTableElements: uint16[] + actionTableElements: uint16 array /// The sparse action table row offsets - actionTableRowOffsets: uint16[] + actionTableRowOffsets: uint16 array /// The number of symbols for each reduction - reductionSymbolCounts: uint16[] + reductionSymbolCounts: uint16 array /// The immediate action table - immediateActions: uint16[] + immediateActions: uint16 array /// The sparse goto table - gotos: uint16[] + gotos: uint16 array /// The sparse goto table row offsets - sparseGotoTableRowOffsets: uint16[] + sparseGotoTableRowOffsets: uint16 array /// The sparse table for the productions active for each state - stateToProdIdxsTableElements: uint16[] + stateToProdIdxsTableElements: uint16 array /// The sparse table offsets for the productions active for each state - stateToProdIdxsTableRowOffsets: uint16[] + stateToProdIdxsTableRowOffsets: uint16 array /// This table is logically part of the Goto table - productionToNonTerminalTable: uint16[] + productionToNonTerminalTable: uint16 array /// This function is used to hold the user specified "parse_error" or "parse_error_rich" functions parseError: ParseErrorContext<'tok> -> unit diff --git a/src/FsYacc.Core/fsyaccast.fs b/src/FsYacc.Core/fsyaccast.fs index 52ebcb8..50a468a 100644 --- a/src/FsYacc.Core/fsyaccast.fs +++ b/src/FsYacc.Core/fsyaccast.fs @@ -6,6 +6,7 @@ module FsLexYacc.FsYacc.AST open System open System.Collections.Generic +open System.IO open Printf open Microsoft.FSharp.Collections open FSharp.Text.Lexing @@ -16,12 +17,13 @@ let (|KeyValue|) (kvp: KeyValuePair<_, _>) = kvp.Key, kvp.Value type Identifier = string type Code = string * Position +[] type Associativity = | LeftAssoc | RightAssoc | NonAssoc -type Rule = Rule of Identifier list * Identifier option * Code option +type Rule = Rule of symbols: Identifier list * precedenceSymbol: Identifier option * code: Code option type ParserSpec = { @@ -58,20 +60,20 @@ let OutputSyms os syms = let OutputTerminalSet os (tset: string seq) = fprintf os "%s" (String.Join(";", tset |> Seq.toArray)) -let OutputAssoc os p = +let OutputAssoc (os: TextWriter) p = match p with - | LeftAssoc -> fprintf os "left" - | RightAssoc -> fprintf os "right" - | NonAssoc -> fprintf os "nonassoc" + | LeftAssoc -> os.Write "left" + | RightAssoc -> os.Write "right" + | NonAssoc -> os.Write "nonassoc" //--------------------------------------------------------------------- // PreProcess Raw Parser Spec AST type PrecedenceInfo = - | ExplicitPrec of Associativity * int + | ExplicitPrec of associativity: Associativity * precedence: int | NoPrecedence -type Production = Production of NonTerminal * PrecedenceInfo * Symbols * Code option +type Production = Production of nonTerminal: NonTerminal * precedence: PrecedenceInfo * symbols: Symbols * code: Code option type ProcessedParserSpec = { @@ -89,7 +91,7 @@ let ProcessParserSpecAst (spec: ParserSpec) = |> List.map (fun (precSym, assoc) -> precSym, ExplicitPrec(assoc, 9999 - n))) |> List.concat - for key, _ in explicitPrecInfo |> Seq.countBy fst |> Seq.filter (fun (_, n) -> n > 1) do + for key, _ in explicitPrecInfo |> List.countBy fst |> List.filter (fun (_, n) -> n > 1) do failwithf "%s is given two associativities" key let explicitPrecInfo = explicitPrecInfo |> Map.ofList @@ -152,7 +154,7 @@ let ProcessParserSpecAst (spec: ParserSpec) = if not (IsTerminal t) then failwith (sprintf "token %s is not declared" t) - if spec.StartSymbols = [] then + if List.isEmpty spec.StartSymbols then (failwith "at least one %start declaration is required") for nt, _ in spec.Types do @@ -183,16 +185,17 @@ let prodIdx_of_item0 (item0: Item0) = int32 (item0 >>> 16) let dotIdx_of_item0 (item0: Item0) = int32 (item0 &&& 0xFFFFu) /// Part of the output of CompilerLalrParserSpec +[] type Action = | Shift of int | Reduce of ProductionIndex | Accept | Error -let outputPrecInfo os p = +let outputPrecInfo (os: TextWriter) p = match p with | ExplicitPrec(assoc, n) -> fprintf os "explicit %a %d" OutputAssoc assoc n - | NoPrecedence -> fprintf os "noprec" + | NoPrecedence -> os.Write "noprec" /// LR(0) kernels type Kernel = Set @@ -398,7 +401,7 @@ type Closure1Table() = t.[a].Add(b) member table.Count = t.Count - member table.IEnumerable = (t :> seq<_>) + member table.IEnumerable = (t :> _ seq) member table.Contains(a, b) = t.ContainsKey(a) && t.[a].Contains(b) /// A mutable table giving a lookahead set Set for each kernel. The terminals represent the @@ -413,7 +416,7 @@ type SpontaneousTable() = t.[a].Add(b) member table.Count = t.Count - member table.IEnumerable = (t :> seq<_>) + member table.IEnumerable = (t :> _ seq) /// A mutable table giving a Set for each kernel. The kernels represent the /// "propagate" items for the kernel. TODO: document this more w.r.t. the Dragon book. @@ -429,21 +432,21 @@ type PropagateTable() = member table.Item with get a = let ok, v = t.TryGetValue(a) - if ok then v :> seq<_> else Seq.empty + if ok then v :> _ seq else Seq.empty member table.Count = t.Count -type Prod = NonTerminal * int * Symbols * option +type Prod = NonTerminal * int * Symbols * Code option type ActionTable = (PrecedenceInfo * Action) array array type CompiledSpec = { - prods: Prod[] - states: int list[] + prods: Prod array + states: int list array startStates: int list actionTable: ActionTable - immediateActionTable: Action option[] - gotoTable: int option[][] + immediateActionTable: Action option array + gotoTable: int option array array endOfInputTerminalIdx: int errorTerminalIdx: int nonTerminals: string list @@ -489,7 +492,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = let errorTerminalIdx = termTab.ToIndex "error" // Compute the FIRST function - printf "computing first function..." + stdout.Write "computing first function..." stdout.Flush() let computedFirstTable = @@ -510,7 +513,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = Set.empty) ] - let add (changed: ref) ss (x, y) = + let add (changed: bool ref) ss (x, y) = let s = Map.find x ss if Set.contains y s then @@ -644,10 +647,10 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = let OutputItem0Set os s = Set.iter (fun item -> fprintfn os "%a" OutputItem0 item) s - let OutputFirstSet os m = + let OutputFirstSet (os: TextWriter) m = Set.iter (function - | None -> fprintf os "" + | None -> os.Write "" | Some x -> fprintfn os " term %s" x) m @@ -658,13 +661,13 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = fprintfn os "%a" OutputFirstSet y) m - let OutputAction os m = + let OutputAction (os: TextWriter) m = match m with - | Shift n -> fprintf os " shift %d" n - | Reduce prodIdx -> + | Action.Shift n -> fprintf os " shift %d" n + | Action.Reduce prodIdx -> fprintf os " reduce %s --> %a" (ntTab.OfIndex(prodTab.NonTerminal prodIdx)) OutputSyms (prodTab.Symbols prodIdx) - | Error -> fprintf os " error" - | Accept -> fprintf os " accept" + | Action.Error -> os.Write " error" + | Action.Accept -> os.Write " accept" let OutputActions os m = Array.iteri @@ -679,9 +682,9 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = fprintfn os "%a" OutputActions n) m - let OutputImmediateActions os m = + let OutputImmediateActions (os: TextWriter) m = match m with - | None -> fprintf os "" + | None -> os.Write "" | Some a -> OutputAction os a let OutputGotos os m = @@ -694,21 +697,21 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = | None -> ()) m - let OutputCombined os m = + let OutputCombined (os: TextWriter) m = Array.iteri (fun i (a, b, c, d) -> fprintf os "state %d:" i - fprintf os " items:" + os.Write " items:" fprintf os "%a" OutputItem0Set a - fprintf os " actions:" + os.Write " actions:" fprintf os "%a" OutputActions b - fprintf os " immediate action: " + os.Write " immediate action: " fprintf os "%a" OutputImmediateActions c - fprintf os " gotos:" + os.Write " gotos:" fprintf os "%a" OutputGotos d) m - let OutputLalrTables os (_, states, startStates, actionTable, immediateActionTable, gotoTable, _, _) = + let OutputLalrTables (os: TextWriter) (_, states, startStates, actionTable, immediateActionTable, gotoTable, _, _) = let combined = Array.ofList ( List.map2 @@ -717,11 +720,11 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = (List.zip (Array.toList actionTable) (List.zip (Array.toList immediateActionTable) (Array.toList gotoTable))) ) - fprintfn os "------------------------" - fprintfn os "states = " + os.WriteLine "------------------------" + os.WriteLine "states = " fprintfn os "%a" OutputCombined combined - fprintfn os "startStates = %s" (String.Join(";", Array.ofList (List.map string startStates))) - fprintfn os "------------------------" + fprintfn os "startStates = %s" (String.Join(";", Array.ofList (List.map string startStates))) + os.WriteLine "------------------------" // Closure of LR(0) nonTerminals, items etc let ComputeClosure0NonTerminal = @@ -768,7 +771,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // Build the full set of LR(0) kernels reportTime () - printf "building kernels..." + stdout.Write "building kernels..." stdout.Flush() let startItems = @@ -794,7 +797,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = acc.Value |> Seq.toList |> List.map (Set.filter IsKernelItem) reportTime () - printf "building kernel table..." + stdout.Write "building kernel table..." stdout.Flush() // Give an index to each LR(0) kernel, and from now on refer to them only by index let kernelTab = KernelTable(kernels) @@ -867,7 +870,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // - assess if it's possible to use the symbol we're looking for to help trim the jset reportTime () - printf "computing lookahead relations..." + stdout.Write "computing lookahead relations..." stdout.Flush() let spontaneous, propagate = @@ -879,7 +882,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = let count = ref 0 for kernelIdx in kernelTab.Indexes do - printf "." + stdout.Write "." stdout.Flush() //printf "kernelIdx = %d\n" kernelIdx; stdout.Flush(); let kernel = kernelTab.Kernel(kernelIdx) @@ -915,7 +918,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // Repeatedly use the "spontaneous" and "propagate" maps to build the full set // of lookaheads for each LR(0) kernelItem. reportTime () - printf "building lookahead table..." + stdout.Write "building lookahead table..." stdout.Flush() let lookaheadTable = @@ -944,7 +947,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = //printf "built lookahead table, #lookaheads = %d\n" lookaheadTable.Count; stdout.Flush(); reportTime () - printf "building action table..." + stdout.Write "building action table..." stdout.Flush() let shiftReduceConflicts = ref 0 let reduceReduceConflicts = ref 0 @@ -953,7 +956,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // Now build the action tables. First a utility to merge the given action // into the table, taking into account precedences etc. and reporting errors. - let addResolvingPrecedence (arr: _[]) kernelIdx termIdx (precNew, actionNew) = + let addResolvingPrecedence (arr: _ array) kernelIdx termIdx (precNew, actionNew) = // printf "DEBUG: state %d: adding action for %s, precNew = %a, actionNew = %a\n" kernelIdx (termTab.OfIndex termIdx) outputPrec precNew OutputAction actionNew; // We add in order of precedence - however the precedences may be the same, and we give warnings when rpecedence resolution is based on implicit file orderings @@ -969,8 +972,8 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = let reportAction (p, a) = let an, astr = match a with - | Shift x -> "shift", sprintf "shift(%d)" x - | Reduce x -> + | Action.Shift x -> "shift", sprintf "shift(%d)" x + | Action.Reduce x -> let nt = prodTab.NonTerminal x "reduce", @@ -1012,14 +1015,14 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = reason) match itemSoFar, itemNew with - | (_, Shift _), (_, Shift _) -> + | (_, Action.Shift _), (_, Action.Shift _) -> if actionSoFar <> actionNew then reportConflict itemSoFar itemNew "internal error" itemSoFar - | (precShift, Shift _ as shiftItem, (precReduce, Reduce _ as reduceItem)) - | (precReduce, Reduce _ as reduceItem, (precShift, Shift _ as shiftItem)) -> + | (precShift, Action.Shift _ as shiftItem, (precReduce, Action.Reduce _ as reduceItem)) + | (precReduce, Action.Reduce _ as reduceItem, (precShift, Action.Shift _ as shiftItem)) -> match precReduce, precShift with | ExplicitPrec(_, p1), ExplicitPrec(assocNew, p2) -> if p1 < p2 then @@ -1038,7 +1041,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = reportConflict shiftItem reduceItem "we prefer shift when unable to compare precedences" shiftReduceConflicts.Value <- shiftReduceConflicts.Value + 1 shiftItem - | (_, Reduce prodIdx1), (_, Reduce prodIdx2) -> + | (_, Action.Reduce prodIdx1), (_, Action.Reduce prodIdx2) -> "we prefer the rule earlier in the file" |> if prodIdx1 < prodIdx2 then reportConflict itemSoFar itemNew @@ -1054,7 +1057,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // This build the action table for one state. let ComputeActions kernelIdx = let kernel = kernelTab.Kernel kernelIdx - let arr = Array.create terminals.Length (NoPrecedence, Error) + let arr = Array.create terminals.Length (NoPrecedence, Action.Error) //printf "building lookahead table LR(1) items for kernelIdx %d\n" kernelIdx; stdout.Flush(); @@ -1077,7 +1080,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = let action = match gotoKernel (GotoItemIdx(kernelIdx, PTerminal termIdx)) with | None -> failwith "action on terminal should have found a non-empty goto state" - | Some gkernelItemIdx -> Shift gkernelItemIdx + | Some gkernelItemIdx -> Action.Shift gkernelItemIdx let prec = termTab.PrecInfoOfIndex termIdx addResolvingPrecedence arr kernelIdx termIdx (prec, action) @@ -1086,19 +1089,19 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = if not (IsStartItem(item0)) then let prodIdx = prodIdx_of_item0 item0 let prec = prec_of_item0 item0 - let action = (prec, Reduce prodIdx) + let action = (prec, Action.Reduce prodIdx) addResolvingPrecedence arr kernelIdx lookahead action elif lookahead = endOfInputTerminalIdx then let prec = prec_of_item0 item0 - let action = (prec, Accept) + let action = (prec, Action.Accept) addResolvingPrecedence arr kernelIdx lookahead action else () | _ -> () - // If there is a single item A -> B C . and no Shift or Accept actions (i.e. only Error or Reduce, so the choice of terminal + // If there is a single item A -> B C . and no Action.Shift or Action.Accept actions (i.e. only Action.Error or Action.Reduce, so the choice of terminal // cannot affect what we do) then we emit an immediate reduce action for the rule corresponding to that item - // Also do the same for Accept rules. + // Also do the same for Action.Accept rules. let closure = (ComputeClosure0 kernel) let immediateAction = @@ -1108,27 +1111,27 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = | None when (let reduceOrErrorAction = function - | Error - | Reduce _ -> true - | Shift _ - | Accept -> false + | Action.Error + | Action.Reduce _ -> true + | Action.Shift _ + | Action.Accept -> false termTab.Indexes |> List.forall (fun terminalIdx -> reduceOrErrorAction (snd (arr.[terminalIdx])))) -> - Some(Reduce(prodIdx_of_item0 item0)) + Some(Action.Reduce(prodIdx_of_item0 item0)) | None when (let acceptOrErrorAction = function - | Error - | Accept -> true - | Shift _ - | Reduce _ -> false + | Action.Error + | Action.Accept -> true + | Action.Shift _ + | Action.Reduce _ -> false List.forall (fun terminalIdx -> acceptOrErrorAction (snd (arr.[terminalIdx]))) termTab.Indexes) -> - Some Accept + Some Action.Accept | _ -> None | _ -> None @@ -1140,9 +1143,16 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = match rsym_of_item0 item0 with | None -> for terminalIdx in termTab.Indexes do - if snd (arr.[terminalIdx]) = Error then + if snd (arr.[terminalIdx]) = Action.Error then let prodIdx = prodIdx_of_item0 item0 - let action = (prec, (if IsStartItem(item0) then Accept else Reduce prodIdx)) + + let action = + (prec, + (if IsStartItem(item0) then + Action.Accept + else + Action.Reduce prodIdx)) + addResolvingPrecedence arr kernelIdx terminalIdx action | _ -> () @@ -1154,7 +1164,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = // The goto table is much simpler - it is based on LR(0) kernels alone. reportTime () - printf " building goto table..." + stdout.Write " building goto table..." stdout.Flush() let gotoTable = @@ -1164,7 +1174,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = Array.ofList (List.map gotos kernelTab.Indexes) reportTime () - printfn " returning tables." + stdout.WriteLine " returning tables." stdout.Flush() if shiftReduceConflicts.Value > 0 then @@ -1176,10 +1186,10 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = stdout.Flush() if shiftReduceConflicts.Value > 0 || reduceReduceConflicts.Value > 0 then - printfn - " consider setting precedences explicitly using %%left %%right and %%nonassoc on terminals and/or setting explicit precedence on rules using %%prec" + stdout.WriteLine + " consider setting precedences explicitly using %left %right and %nonassoc on terminals and/or setting explicit precedence on rules using %prec" - printfn " the detail of each conflict is written to the listing file produced by -v" + stdout.WriteLine " the detail of each conflict is written to the listing file produced by -v" stdout.Flush() /// The final results @@ -1189,7 +1199,7 @@ let CompilerLalrParserSpec logf (spec: ProcessedParserSpec) : CompiledSpec = Array.ofList (List.map (fun (Production(nt, _, syms, code)) -> (nt, ntTab.ToIndex nt, syms, code)) prods) logf (fun logStream -> - printfn "writing tables to log" + stdout.WriteLine "writing tables to log" stdout.Flush() OutputLalrTables diff --git a/src/FsYacc.Core/fsyaccdriver.fs b/src/FsYacc.Core/fsyaccdriver.fs index fe9f869..41e6b00 100644 --- a/src/FsYacc.Core/fsyaccdriver.fs +++ b/src/FsYacc.Core/fsyaccdriver.fs @@ -17,7 +17,7 @@ let chop_extension (s: string) = Path.Combine(Path.GetDirectoryName s, Path.GetFileNameWithoutExtension(s)) -let checkSuffix (x: string) (y: string) = x.EndsWith(y) +let checkSuffix (x: string) (y: string) = x.EndsWith(y, StringComparison.Ordinal) let readSpecFromFile fileName codePage = let stream, reader, lexbuf = UnicodeFileAsLexbuf(fileName, codePage) @@ -37,7 +37,7 @@ let printTokens filename codePage = try while true do - printf "tokenize - getting one token" + stdout.Write "tokenize - getting one token" let t = Lexer.token lexbuf in printf "tokenize - got %s" (Parser.token_to_string t) @@ -157,10 +157,10 @@ let actionCoding = let acceptFlag = 0xc000 function - | Accept -> acceptFlag - | Shift n -> shiftFlag ||| n - | Reduce n -> reduceFlag ||| n - | Error -> errorFlag + | Action.Accept -> acceptFlag + | Action.Shift n -> shiftFlag ||| n + | Action.Reduce n -> reduceFlag ||| n + | Action.Error -> errorFlag type GeneratorState = { @@ -379,10 +379,10 @@ let writeSpecToFile (generatorState: GeneratorState) (spec: ParserSpec) (compile // box null does the right thing regardless of NRT and gets optimized to a single ldnull. | None -> "Microsoft.FSharp.Core.Operators.box null") - for key, _ in spec.Types |> Seq.countBy fst |> Seq.filter (fun (_, n) -> n > 1) do + for key, _ in spec.Types |> List.countBy fst |> List.filter (fun (_, n) -> n > 1) do failwithf "%s is given multiple %%type declarations" key - for key, _ in spec.Tokens |> Seq.countBy fst |> Seq.filter (fun (_, n) -> n > 1) do + for key, _ in spec.Tokens |> List.countBy fst |> List.filter (fun (_, n) -> n > 1) do failwithf "%s is given %%token declarations" key let types = Map.ofList spec.Types @@ -488,7 +488,7 @@ let writeSpecToFile (generatorState: GeneratorState) (spec: ParserSpec) (compile countPerAction.[action] <- 1 let mostCommonAction = - let mostCommon = ref Error + let mostCommon = ref Action.Error let max = ref 0 for KeyValue(x, y) in countPerAction do diff --git a/src/FsYacc/fsyacc.fs b/src/FsYacc/fsyacc.fs index e789241..a7f7c26 100644 --- a/src/FsYacc/fsyacc.fs +++ b/src/FsYacc/fsyacc.fs @@ -99,7 +99,7 @@ let main () = | None -> new NullLogger() :> Logger let compiledSpec = compileSpec spec logger - printfn " building tables" + stdout.WriteLine " building tables" printfn " %d states" compiledSpec.states.Length printfn " %d nonterminals" compiledSpec.gotoTable.[0].Length printfn " %d terminals" compiledSpec.actionTable.[0].Length diff --git a/tests/FsLex.Core.Tests/UnicodeTests.fs b/tests/FsLex.Core.Tests/UnicodeTests.fs index 863d5e2..a125eb9 100644 --- a/tests/FsLex.Core.Tests/UnicodeTests.fs +++ b/tests/FsLex.Core.Tests/UnicodeTests.fs @@ -25,11 +25,11 @@ let tests = } testProperty "TryDecodeUnicodeCategory should decode all valid EncodeUnicodeCategoryIndex outputs" <| fun (a:UnicodeCategory) -> - a |> int |> AST.EncodeUnicodeCategoryIndex |> AST.TryDecodeUnicodeCategory = Some a + a |> int |> AST.EncodeUnicodeCategoryIndex |> AST.TryDecodeUnicodeCategory = ValueSome a - testProperty "TryDecodeUnicodeCategory should return None for all EncodeChar outputs" <| fun (c:FsCheck.UnicodeChar) -> + testProperty "TryDecodeUnicodeCategory should return ValueNone for all EncodeChar outputs" <| fun (c:FsCheck.UnicodeChar) -> let encodedChar = AST.EncodeChar (c.Get) {unicode=true; caseInsensitive=false} - encodedChar |> AST.TryDecodeUnicodeCategory = None + encodedChar |> AST.TryDecodeUnicodeCategory = ValueNone ] ] diff --git a/tests/JsonLexAndYaccExample/Program.fs b/tests/JsonLexAndYaccExample/Program.fs index 3320ad9..46b3ba0 100644 --- a/tests/JsonLexAndYaccExample/Program.fs +++ b/tests/JsonLexAndYaccExample/Program.fs @@ -11,10 +11,15 @@ let main argv = let res = Parser.start Lexer.read lexbuf res + let printParsed (result: JsonValue option) = + match result with + | Some value -> printfn "%s" (JsonValue.print value) + | None -> failwith "expected a parse result" + //a few parsing tests with simple and complex json let simpleJson = "{\"f\" : 1}" let parseResult = simpleJson |> parse - printfn "%s" (JsonValue.print parseResult.Value) + printParsed parseResult let simpleJson2 = @"{ @@ -25,7 +30,7 @@ let main argv = ] }" let parseResult2 = simpleJson2 |> parse - printfn "%s" (JsonValue.print parseResult2.Value) + printParsed parseResult2 let complexJson = System.IO.File.ReadAllText "randomComplexTestsJson.json" complexJson |> parse |> ignore @@ -64,7 +69,7 @@ let main argv = try let simpleJson = "{\"f\"\n" + "\n" + ";" let parseResult = simpleJson |> parse - printfn "%s" (JsonValue.print parseResult.Value) + printParsed parseResult with | e -> printfn "Error is expected here: \n %s" (e.Message) diff --git a/tests/LexAndYaccMiniProject/Program.fs b/tests/LexAndYaccMiniProject/Program.fs index eea63da..02a9dee 100644 --- a/tests/LexAndYaccMiniProject/Program.fs +++ b/tests/LexAndYaccMiniProject/Program.fs @@ -25,7 +25,7 @@ let testFile = Path.Combine(__SOURCE_DIRECTORY__, "test.txt") File.WriteAllText(testFile, "hello hello") testLexerAndParserFromFile testFile 2 -printfn "Press any key to continue..." +stdout.WriteLine "Press any key to continue..." System.Console.ReadLine() |> ignore diff --git a/tests/fsyacc/OldFsYaccTests.fsx b/tests/fsyacc/OldFsYaccTests.fsx index 9cc98a2..eb90537 100755 --- a/tests/fsyacc/OldFsYaccTests.fsx +++ b/tests/fsyacc/OldFsYaccTests.fsx @@ -62,7 +62,7 @@ let test proj shouldBeOK (arguments: string list, baseLineOutput) = // For some reason, the output is captured in the stderr error.Split('\n', StringSplitOptions.RemoveEmptyEntries) |> Array.map (fun line -> - if line.StartsWith("parsed") then + if line.StartsWith("parsed", StringComparison.Ordinal) then let pieces = line.Split(' ') let pathPiece = pieces.[1] @@ -73,7 +73,7 @@ let test proj shouldBeOK (arguments: string list, baseLineOutput) = else "/" - pathPiece.LastIndexOf(value) + pathPiece.LastIndexOf(value, StringComparison.Ordinal) let pathPiece = if idx >= 0 then @@ -94,12 +94,12 @@ let test proj shouldBeOK (arguments: string list, baseLineOutput) = output.Length <> expectedLines.Length || Seq.map2 (=) output expectedLines |> Seq.exists not then - printfn "Expected:" + stdout.WriteLine "Expected:" for line in expectedLines do printfn "\t%s" line - printfn "Output:" + stdout.WriteLine "Output:" for line in output do printfn "\t%s" line From baea2af7c2830ee2dfb1c2ba44b8e335f5336235 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Tue, 15 Sep 2026 14:20:20 +0200 Subject: [PATCH 3/3] Escape the release notes before passing them to dotnet pack The pack step hands RELEASE_NOTES.md to MSBuild as a /p:PackageReleaseNotes switch. MSBuild reads a newline, a semicolon or a comma in a property value as the start of the next switch, so the multi-line notes were cut into fragments and the Release pipeline failed with MSB1006 "Property is not valid" on every push to master since the move to Fun.Build. Each of those characters, and the others MSBuild gives meaning to, is now written as its %XX escape. MSBuild unescapes the value when it reads the property, so the nuspec carries the notes exactly as written. --- build.fsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/build.fsx b/build.fsx index d2d07ba..110a7df 100755 --- a/build.fsx +++ b/build.fsx @@ -212,6 +212,25 @@ let buildLibraries = // Packaging // -------------------------------------------------------------------------------------- +/// Escape a value for a `/p:Name=value` switch. +/// +/// MSBuild reads a newline, `;` or `,` in a property value as the start of the next switch, and +/// treats `$`, `%` and friends as its own syntax. Each becomes its `%XX` escape, which MSBuild +/// unescapes again when it reads the property, so the release notes arrive as written. +let msbuildEscape (value: string) = + let special = + Collections.Generic.HashSet [ '%'; '$'; '@'; '\''; ';'; ','; '?'; '*'; '('; ')'; '\r'; '\n' ] + + let escaped = Text.StringBuilder() + + for c in value do + if special.Contains c then + escaped.Append('%').Append((int c).ToString "X2") |> ignore + else + escaped.Append c |> ignore + + escaped.ToString() + let pack = async { let releaseNotes = String.concat Environment.NewLine release.Notes @@ -227,7 +246,7 @@ let pack = "Release" "-o" "bin" - $"/p:PackageReleaseNotes=%s{releaseNotes}" + $"/p:PackageReleaseNotes=%s{msbuildEscape releaseNotes}" $"/p:PackageVersion=%s{release.NugetVersion}" ]