├── .config └── dotnet-tools.json ├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Directory.Build.props ├── Directory.Packages.props ├── LICENSE ├── NuGet.config ├── README.md ├── XParsec.sln ├── bench └── XParsec.Json.Benchmarks │ ├── FParsecJson.fs │ ├── Json.fs │ ├── Program.fs │ ├── XParsec.Json.Benchmarks.fsproj │ └── packages.lock.json ├── global.json ├── interactive ├── Shared │ └── Types.fs ├── XParsec.Client │ ├── XParsec.Client.fsproj │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── packages.lock.json │ ├── public │ │ └── oxpecker-128.png │ ├── src │ │ ├── API.fs │ │ ├── App.fs │ │ ├── Component.fs │ │ ├── Program.fs │ │ ├── Serialization.fs │ │ └── index.css │ └── vite.config.mts └── XParsec.Server │ ├── Handlers.fs │ ├── Program.fs │ ├── Properties │ └── launchSettings.json │ ├── Serialization.fs │ ├── XParsec.Server.fsproj │ └── packages.lock.json ├── package.json ├── src ├── Fable.Package.SDK │ ├── Fable.Package.SDK.props │ ├── Fable.Package.SDK.targets │ └── README.md ├── XParsec.CLArgs │ ├── Types.fs │ ├── XParsec.CLArgs.fsproj │ └── packages.lock.json ├── XParsec.Json │ ├── JsonParser.fs │ ├── XParsec.Json.fsproj │ └── packages.lock.json └── XParsec │ ├── ByteParsers.fs │ ├── CharParsers.fs │ ├── Combinators.fs │ ├── ErrorFormatting.fs │ ├── FableTypes.fs │ ├── ImmutableArray.fs │ ├── OperatorParsing.fs │ ├── Parsers.fs │ ├── Readables.fs │ ├── Types.fs │ ├── XParsec.fsproj │ └── packages.lock.json └── test ├── XParsec.CLArgs.Interactive ├── Program.fs ├── XParsec.CLArgs.Interactive.fsproj └── packages.lock.json ├── XParsec.CLArgs.Tests ├── CLArgsParsersTests.fs ├── Main.fs ├── XParsec.CLArgs.Tests.fsproj └── packages.lock.json ├── XParsec.Json.Tests ├── JsonParserArrayTests.fs ├── JsonParserStringTests.fs ├── Main.fs ├── XParsec.Json.Tests.fsproj └── packages.lock.json ├── XParsec.MessagePack.Tests ├── Main.fs ├── MessagePack.fs ├── MessagePackTests.fs ├── XParsec.MessagePack.Tests.fsproj └── packages.lock.json └── XParsec.Tests ├── ByteParsersTests.fs ├── CombinatorTests.fs ├── ErrorFormattingTests.fs ├── Main.fs ├── OperatorParsingTests.fs ├── ParserTests.fs ├── XParsec.Tests.fsproj └── packages.lock.json /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fantomas": { 6 | "version": "7.0.1", 7 | "commands": [ 8 | "fantomas" 9 | ], 10 | "rollForward": false 11 | }, 12 | "fable": { 13 | "version": "4.24.0", 14 | "commands": [ 15 | "fable" 16 | ], 17 | "rollForward": false 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.{fs,fsx,fsi}] 7 | fsharp_bar_before_discriminated_union_declaration = true 8 | fsharp_multiline_block_brackets_on_same_column = true 9 | fsharp_multiline_bracket_style = aligned 10 | fsharp_keep_max_number_of_blank_lines=2 11 | fsharp_multi_line_lambda_closing_newline=true 12 | 13 | # Visual Studio Solution Files 14 | [*.sln] 15 | indent_style = tab 16 | 17 | # XML project files 18 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,sfproj}] 19 | indent_size = 2 20 | 21 | # XML config files 22 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | configuration: [Debug, Release] 17 | os: [ubuntu-latest, windows-latest, macOS-latest] 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Setup necessary dotnet SDKs 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | global-json-file: ./global.json 26 | 27 | - name: Build via Bash 28 | if: runner.os != 'Windows' 29 | run: | 30 | dotnet build 31 | dotnet test 32 | env: 33 | CONFIGURATION: ${{ matrix.configuration }} 34 | CI: true 35 | - name: Build via Windows 36 | if: runner.os == 'Windows' 37 | run: | 38 | dotnet build 39 | dotnet test 40 | env: 41 | CONFIGURATION: ${{ matrix.configuration }} 42 | CI: true 43 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NuGet 2 | 3 | on: 4 | workflow_run: 5 | workflows: [Build main] 6 | types: 7 | - completed 8 | 9 | env: 10 | CONFIGURATION: Release 11 | NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} 12 | 13 | jobs: 14 | publish: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | global-json-file: ./global.json 24 | 25 | - name: Get Package Version 26 | shell: pwsh 27 | run: | 28 | [xml]$BuildProps = Get-Content ./Directory.Build.props 29 | $PackageVersion = $BuildProps.Project.PropertyGroup.PackageVersion 30 | [semver]$PackageVersion = "$PackageVersion".Trim() 31 | Write-Output "Current branch is '$env:GITHUB_REF_NAME'" 32 | Write-Output "Creating Packages with version='$PackageVersion'" 33 | Write-Output "PACKAGE_VERSION=$PackageVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 34 | 35 | - name: Pack Published Projects 36 | shell: pwsh 37 | run: | 38 | $Project = "./src/XParsec/XParsec.fsproj" 39 | dotnet restore $Project 40 | dotnet list $Project package 41 | dotnet pack $Project 42 | dotnet nuget push "./src/XParsec/bin/Release/XParsec.$env:PACKAGE_VERSION.nupkg" --api-key $env:NUGET_TOKEN --source "nuget.org" --skip-duplicate 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | # but not Directory.Build.rsp, as it configures directory-level build defaults 86 | !Directory.Build.rsp 87 | *.sbr 88 | *.tlb 89 | *.tli 90 | *.tlh 91 | *.tmp 92 | *.tmp_proj 93 | *_wpftmp.csproj 94 | *.log 95 | *.tlog 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # TeamCity is a build add-in 138 | _TeamCity* 139 | 140 | # DotCover is a Code Coverage Tool 141 | *.dotCover 142 | 143 | # AxoCover is a Code Coverage Tool 144 | .axoCover/* 145 | !.axoCover/settings.json 146 | 147 | # Coverlet is a free, cross platform Code Coverage Tool 148 | coverage*.json 149 | coverage*.xml 150 | coverage*.info 151 | 152 | # Visual Studio code coverage results 153 | *.coverage 154 | *.coveragexml 155 | 156 | # NCrunch 157 | _NCrunch_* 158 | .*crunch*.local.xml 159 | nCrunchTemp_* 160 | 161 | # MightyMoose 162 | *.mm.* 163 | AutoTest.Net/ 164 | 165 | # Web workbench (sass) 166 | .sass-cache/ 167 | 168 | # Installshield output folder 169 | [Ee]xpress/ 170 | 171 | # DocProject is a documentation generator add-in 172 | DocProject/buildhelp/ 173 | DocProject/Help/*.HxT 174 | DocProject/Help/*.HxC 175 | DocProject/Help/*.hhc 176 | DocProject/Help/*.hhk 177 | DocProject/Help/*.hhp 178 | DocProject/Help/Html2 179 | DocProject/Help/html 180 | 181 | # Click-Once directory 182 | publish/ 183 | 184 | # Publish Web Output 185 | *.[Pp]ublish.xml 186 | *.azurePubxml 187 | # Note: Comment the next line if you want to checkin your web deploy settings, 188 | # but database connection strings (with potential passwords) will be unencrypted 189 | *.pubxml 190 | *.publishproj 191 | 192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 193 | # checkin your Azure Web App publish settings, but sensitive information contained 194 | # in these scripts will be unencrypted 195 | PublishScripts/ 196 | 197 | # NuGet Packages 198 | *.nupkg 199 | # NuGet Symbol Packages 200 | *.snupkg 201 | # The packages folder can be ignored because of Package Restore 202 | **/[Pp]ackages/* 203 | # except build/, which is used as an MSBuild target. 204 | !**/[Pp]ackages/build/ 205 | # Uncomment if necessary however generally it will be regenerated when needed 206 | #!**/[Pp]ackages/repositories.config 207 | # NuGet v3's project.json files produces more ignorable files 208 | *.nuget.props 209 | *.nuget.targets 210 | 211 | # Microsoft Azure Build Output 212 | csx/ 213 | *.build.csdef 214 | 215 | # Microsoft Azure Emulator 216 | ecf/ 217 | rcf/ 218 | 219 | # Windows Store app package directories and files 220 | AppPackages/ 221 | BundleArtifacts/ 222 | Package.StoreAssociation.xml 223 | _pkginfo.txt 224 | *.appx 225 | *.appxbundle 226 | *.appxupload 227 | 228 | # Visual Studio cache files 229 | # files ending in .cache can be ignored 230 | *.[Cc]ache 231 | # but keep track of directories ending in .cache 232 | !?*.[Cc]ache/ 233 | 234 | # Others 235 | ClientBin/ 236 | ~$* 237 | *~ 238 | *.dbmdl 239 | *.dbproj.schemaview 240 | *.jfm 241 | *.pfx 242 | *.publishsettings 243 | orleans.codegen.cs 244 | 245 | # Including strong name files can present a security risk 246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 247 | #*.snk 248 | 249 | # Since there are multiple workflows, uncomment next line to ignore bower_components 250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 251 | #bower_components/ 252 | 253 | # RIA/Silverlight projects 254 | Generated_Code/ 255 | 256 | # Backup & report files from converting an old project file 257 | # to a newer Visual Studio version. Backup files are not needed, 258 | # because we have git ;-) 259 | _UpgradeReport_Files/ 260 | Backup*/ 261 | UpgradeLog*.XML 262 | UpgradeLog*.htm 263 | ServiceFabricBackup/ 264 | *.rptproj.bak 265 | 266 | # SQL Server files 267 | *.mdf 268 | *.ldf 269 | *.ndf 270 | 271 | # Business Intelligence projects 272 | *.rdl.data 273 | *.bim.layout 274 | *.bim_*.settings 275 | *.rptproj.rsuser 276 | *- [Bb]ackup.rdl 277 | *- [Bb]ackup ([0-9]).rdl 278 | *- [Bb]ackup ([0-9][0-9]).rdl 279 | 280 | # Microsoft Fakes 281 | FakesAssemblies/ 282 | 283 | # GhostDoc plugin setting file 284 | *.GhostDoc.xml 285 | 286 | # Node.js Tools for Visual Studio 287 | .ntvs_analysis.dat 288 | node_modules/ 289 | 290 | # Visual Studio 6 build log 291 | *.plg 292 | 293 | # Visual Studio 6 workspace options file 294 | *.opt 295 | 296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 297 | *.vbw 298 | 299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 300 | *.vbp 301 | 302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 303 | *.dsw 304 | *.dsp 305 | 306 | # Visual Studio 6 technical files 307 | *.ncb 308 | *.aps 309 | 310 | # Visual Studio LightSwitch build output 311 | **/*.HTMLClient/GeneratedArtifacts 312 | **/*.DesktopClient/GeneratedArtifacts 313 | **/*.DesktopClient/ModelManifest.xml 314 | **/*.Server/GeneratedArtifacts 315 | **/*.Server/ModelManifest.xml 316 | _Pvt_Extensions 317 | 318 | # Paket dependency manager 319 | .paket/paket.exe 320 | paket-files/ 321 | 322 | # FAKE - F# Make 323 | .fake/ 324 | 325 | # CodeRush personal settings 326 | .cr/personal 327 | 328 | # Python Tools for Visual Studio (PTVS) 329 | __pycache__/ 330 | *.pyc 331 | 332 | # Cake - Uncomment if you are using it 333 | # tools/** 334 | # !tools/packages.config 335 | 336 | # Tabs Studio 337 | *.tss 338 | 339 | # Telerik's JustMock configuration file 340 | *.jmconfig 341 | 342 | # BizTalk build output 343 | *.btp.cs 344 | *.btm.cs 345 | *.odx.cs 346 | *.xsd.cs 347 | 348 | # OpenCover UI analysis results 349 | OpenCover/ 350 | 351 | # Azure Stream Analytics local run output 352 | ASALocalRun/ 353 | 354 | # MSBuild Binary and Structured Log 355 | *.binlog 356 | 357 | # NVidia Nsight GPU debugger configuration file 358 | *.nvuser 359 | 360 | # MFractors (Xamarin productivity tool) working folder 361 | .mfractor/ 362 | 363 | # Local History for Visual Studio 364 | .localhistory/ 365 | 366 | # Visual Studio History (VSHistory) files 367 | .vshistory/ 368 | 369 | # BeatPulse healthcheck temp database 370 | healthchecksdb 371 | 372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 373 | MigrationBackup/ 374 | 375 | # Ionide (cross platform F# VS Code tools) working folder 376 | .ionide/ 377 | 378 | # Fody - auto-generated XML schema 379 | FodyWeavers.xsd 380 | 381 | # VS Code files for those working on multiple tools 382 | .vscode/* 383 | !.vscode/settings.json 384 | !.vscode/tasks.json 385 | !.vscode/launch.json 386 | !.vscode/extensions.json 387 | *.code-workspace 388 | 389 | # Local History for Visual Studio Code 390 | .history/ 391 | 392 | # Windows Installer files from build outputs 393 | *.cab 394 | *.msi 395 | *.msix 396 | *.msm 397 | *.msp 398 | 399 | # JetBrains Rider 400 | *.sln.iml 401 | 402 | # Fable Output 403 | test/XParsec.Tests/js/ 404 | test/XParsec.CLArgs.Tests/js/ 405 | interactive/XParsec.Client/build/ 406 | *.fs.js 407 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "XParsec Tests", 12 | "skipFiles": [ 13 | "/**" 14 | ], 15 | "program": "${workspaceFolder}/test/XParsec.Tests/js/Main.js", 16 | "preLaunchTask": "Fable Build: XParsec.Tests", 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Fable Build: XParsec.Tests", 8 | "type": "shell", 9 | "command": "dotnet fable ./test/XParsec.Tests/XParsec.Tests.fsproj -o ./test/XParsec.Tests/js -s" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory) 4 | 5 | 6 | Robert Lenders 7 | Copyright (c) Robert Lenders 2025 8 | 0.1.0 9 | https://github.com/roboz0r/XParsec 10 | git 11 | https://github.com/roboz0r/XParsec 12 | false 13 | LICENSE 14 | README.md 15 | 16 | 17 | true 18 | true 19 | 20 | 21 | 22 | true 23 | 24 | 25 | true 26 | true 27 | 28 | 29 | false 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Robert Lenders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XParsec 2 | 3 | XParsec is a parser combinator library for F# 4 | 5 | It aims to be a successor to the popular [FParsec](https://github.com/stephan-tolksdorf/fparsec) library with several important differences: 6 | 7 | - Generalization over collection and token types 8 | 9 | With XParsec all common contiguous collections `string` `'T array` `ResizeArray<'T>` `ImmutableArray<'T>` and `Stream` can be parsed with essentially the same code. 10 | 11 | - Pure F# implementation 12 | 13 | F# is a great .NET language but with the power of Fable, a powerful JavaScript language too. By implementing XParsec in completely in F#, I aim to provide an equally robust and easy to use parsing library for Fable target languages. 14 | 15 | - More Performant 16 | 17 | By making use of newer F# & .NET technologies like `[]` `Span<'T>` and `struct` unions I aim to make XParsec competitive with imperative parsing libraries while remaining terse and easy to reason about. 18 | 19 | Initial results are encoraging with roughly 2/3 the execution time and 1/4 the allocations for the equivalent parser code parsing a single large json file. 20 | 21 | | Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | 22 | |------------ |---------:|---------:|---------:|----------:|---------:|---------:|----------:| 23 | | XParsecJson | 41.40 ms | 0.205 ms | 0.182 ms | 1000.0000 | 916.6667 | - | 50.91 MB | 24 | | FParsecJson | 67.38 ms | 1.247 ms | 1.106 ms | 4375.0000 | 875.0000 | 250.0000 | 200.98 MB | 25 | 26 | - Simplified operator precedence parsing 27 | - No line number tracking by default 28 | 29 | ## Running Tests 30 | 31 | ### .NET 32 | 33 | ```pwsh 34 | dotnet test 35 | ``` 36 | 37 | ### Fable JS 38 | 39 | ```pwsh 40 | npm run test 41 | ``` 42 | 43 | ## TODO 44 | 45 | - [x] `ByteParsers` module 46 | - [x] Fable JS compatibility 47 | - [ ] Other Fable targets compatibility 48 | - [x] Multi-token operator parsing 49 | - [ ] Performance benchmarks and optimization 50 | - [ ] Complete FParsec API coverage 51 | - [ ] Tests with complex grammars 52 | - [ ] Improvements to error messages 53 | - [ ] Release to NuGet 54 | -------------------------------------------------------------------------------- /bench/XParsec.Json.Benchmarks/FParsecJson.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec.Json.Benchmarks 2 | 3 | open System 4 | open System.Collections.Immutable 5 | open System.Text 6 | 7 | open XParsec.Json 8 | 9 | open FParsec 10 | 11 | #nowarn "40" // Recursive value definitions 12 | 13 | type FParsecJsonParsers = 14 | 15 | static let pWhitespace = skipMany (anyOf [ ' '; '\t'; '\n'; '\r' ]) 16 | 17 | static let pDigit = satisfyL (fun c -> c >= '0' && c <= '9') ("Char in range '0' - '9'") 18 | 19 | static let pOneNine = satisfyL (fun c -> c >= '1' && c <= '9') ("Char in range '1' - '9'") 20 | 21 | static let pFraction = 22 | parse { 23 | let! dot = pchar '.' 24 | let! digits = many1Chars pDigit 25 | return digits 26 | } 27 | 28 | static let pExponent = 29 | parse { 30 | let! e = anyOf [ 'e'; 'E' ] 31 | let! sign = opt (anyOf [ '+'; '-' ]) 32 | let! digits = many1Chars pDigit 33 | return (struct (sign, digits)) 34 | } 35 | 36 | static let pNumber = 37 | parse { 38 | let! sign = opt (pchar '-') 39 | 40 | let! int = (pchar '0' >>% "0") <|> (many1Chars2 pOneNine pDigit) 41 | 42 | let! fraction = opt pFraction 43 | let! exponent = opt pExponent 44 | 45 | let! number = 46 | let sb = StringBuilder(16) 47 | 48 | match sign with 49 | | Some c -> sb.Append(c) |> ignore 50 | | _ -> () 51 | 52 | sb.Append(int) |> ignore 53 | 54 | match fraction with 55 | | Some f -> sb.Append('.').Append(f) |> ignore 56 | | _ -> () 57 | 58 | match exponent with 59 | | Some(sign, digits) -> 60 | sb.Append('e') |> ignore 61 | 62 | match sign with 63 | | Some sign -> sb.Append(sign) |> ignore 64 | | None -> () 65 | 66 | sb.Append(digits) |> ignore 67 | | _ -> () 68 | 69 | let number = sb.ToString() 70 | 71 | match Double.TryParse(number) with 72 | | true, result -> preturn result 73 | | _ -> failwithf "Failed to parse number: %s" number 74 | 75 | return number 76 | } 77 | 78 | static let pEscape = 79 | parse { 80 | let! _ = pchar '\\' 81 | let! escaped = anyOf [ '"'; '\\'; '/'; 'b'; 'f'; 'n'; 'r'; 't' ] 82 | 83 | return 84 | match escaped with 85 | | 'b' -> '\b' 86 | | 'f' -> '\f' 87 | | 'n' -> '\n' 88 | | 'r' -> '\r' 89 | | 't' -> '\t' 90 | | c -> c 91 | } 92 | 93 | static let pHexDigit = 94 | satisfyL Char.IsAsciiHexDigit ("Hex digit") 95 | |>> function 96 | | c when c >= '0' && c <= '9' -> int c - int '0' 97 | | c when c >= 'a' && c <= 'f' -> int c - int 'a' + 10 98 | | c when c >= 'A' && c <= 'F' -> int c - int 'A' + 10 99 | | _ -> failwith "Invalid hex digit" 100 | 101 | static let pUnicodeEscape = 102 | parse { 103 | let! _ = pchar '\\' 104 | let! _ = pchar 'u' 105 | let! hex0 = pHexDigit 106 | let! hex1 = pHexDigit 107 | let! hex2 = pHexDigit 108 | let! hex3 = pHexDigit 109 | let hexValue = (hex0 <<< 12) + (hex1 <<< 8) + (hex2 <<< 4) + hex3 110 | return Convert.ToChar(hexValue) 111 | } 112 | 113 | static let pOtherChar = 114 | satisfyL 115 | (function 116 | | '"' 117 | | '\\' -> false 118 | | c -> not (Char.IsControl c)) 119 | ("Other Char") 120 | 121 | static let pString = 122 | parse { 123 | let! _ = pchar '"' 124 | let! chars = manyChars (choiceL [ pEscape; pUnicodeEscape; pOtherChar ] "") 125 | let! _ = pchar '"' 126 | return chars 127 | } 128 | 129 | static let pTrue = pstring "true" >>% JsonValue.True 130 | 131 | static let pFalse = pstring "false" >>% JsonValue.False 132 | 133 | static let pNull = pstring "null" >>% JsonValue.Null 134 | 135 | static let rec pValue = 136 | choiceL 137 | [ 138 | pString |>> JsonValue.String 139 | pNumber |>> JsonValue.Number 140 | pTrue 141 | pFalse 142 | pNull 143 | pObject 144 | pArray 145 | ] 146 | "" 147 | 148 | and pElement = 149 | parse { 150 | let! _ = pWhitespace 151 | let! value = pValue 152 | let! _ = pWhitespace 153 | return value 154 | } 155 | 156 | and pMember = 157 | parse { 158 | let! _ = pWhitespace 159 | let! name = pString 160 | let! _ = pWhitespace 161 | let! _ = pchar ':' 162 | let! value = pElement 163 | return { Name = name; Value = value } 164 | } 165 | 166 | and pObject = 167 | parse { 168 | let! _ = pchar '{' 169 | let! _ = pWhitespace 170 | let! members = sepBy pMember (pchar ',') 171 | let! _ = pchar '}' 172 | return JsonValue.Object(ImmutableArray.CreateRange members) 173 | } 174 | 175 | and pArray = 176 | parse { 177 | let! _ = pchar '[' 178 | let! _ = pWhitespace 179 | let! values = sepBy pElement (pchar ',') 180 | let! _ = pchar ']' 181 | return JsonValue.Array(ImmutableArray.CreateRange values) 182 | } 183 | 184 | static let pJson: Parser<_, _> = (pElement .>> eof) 185 | 186 | static member Parser = pJson 187 | static member PString = pString 188 | -------------------------------------------------------------------------------- /bench/XParsec.Json.Benchmarks/Json.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Json.Benchmarks.Json 2 | 3 | open System 4 | open System.Net.Http 5 | 6 | open BenchmarkDotNet.Attributes 7 | 8 | open XParsec 9 | open XParsec.Parsers 10 | open XParsec.CharParsers 11 | open XParsec.Json 12 | 13 | 14 | [] 15 | type ParsingLargeJson() = 16 | 17 | let client = new HttpClient() 18 | 19 | let source = 20 | "https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/master/service-schema.json" 21 | 22 | let content = (client.GetStringAsync source).Result 23 | 24 | [] 25 | member __.XParsecJson() = 26 | let reader = Reader.ofString content () 27 | let p = JsonParsers.Parser 28 | let result = p reader 29 | () 30 | 31 | [] 32 | member __.FParsecJson() = 33 | let p = FParsecJsonParsers.Parser 34 | let result = FParsec.CharParsers.runParserOnString p () "Name" content 35 | () 36 | 37 | interface IDisposable with 38 | member __.Dispose() = client.Dispose() 39 | -------------------------------------------------------------------------------- /bench/XParsec.Json.Benchmarks/Program.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Json.Benchmarks.Program 2 | 3 | open BenchmarkDotNet.Running 4 | open System.Runtime.InteropServices 5 | 6 | open Json 7 | 8 | [] 9 | let main _ = 10 | let _ = BenchmarkRunner.Run() 11 | 0 12 | -------------------------------------------------------------------------------- /bench/XParsec.Json.Benchmarks/XParsec.Json.Benchmarks.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.203", 4 | "rollForward": "disable" 5 | }, 6 | "notes": [ 7 | "rollForward was disabled to address https://github.com/dotnet/sdk/issues/38200" 8 | ] 9 | } -------------------------------------------------------------------------------- /interactive/Shared/Types.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec.Shared 2 | 3 | open System 4 | 5 | [] 6 | type ParseSyntax = | Json 7 | 8 | type ParseRequest = 9 | { 10 | Id: Guid 11 | Input: string 12 | Syntax: ParseSyntax 13 | } 14 | 15 | type ParseResponse = 16 | { 17 | RequestId: Guid 18 | Success: bool 19 | Message: string 20 | } 21 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/XParsec.Client.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | XParsec Testing App 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": "true", 3 | "scripts": { 4 | "postinstall": "dotnet tool restore", 5 | "dev": "dotnet fable watch --extension .jsx -o ./build/ -s --run vite", 6 | "build": "dotnet fable --extension .jsx -o ./build/ --run vite build" 7 | }, 8 | "dependencies": { 9 | "@solidjs/router": "^0.15.3", 10 | "@tailwindcss/vite": "^4.0.17", 11 | "solid-js": "1.9.5", 12 | "tailwindcss": "^4.0.17" 13 | }, 14 | "devDependencies": { 15 | "vite": "6.2.3", 16 | "vite-plugin-solid": "2.11.6" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Fable.Browser.Dom": { 6 | "type": "Direct", 7 | "requested": "[2.18.1, )", 8 | "resolved": "2.18.1", 9 | "contentHash": "UNrFT/fhsS4voVZgLSO1DwgOu9XAqKhPIfvZlPwcgRmjG85/zfL+RFaG3pJcMGgGd+uqoLKx0SYMWjZCVl+DMg==", 10 | "dependencies": { 11 | "FSharp.Core": "4.7.2", 12 | "Fable.Browser.Blob": "1.4.0", 13 | "Fable.Browser.Event": "1.7.0", 14 | "Fable.Browser.WebStorage": "1.3.0", 15 | "Fable.Core": "3.2.8" 16 | } 17 | }, 18 | "Fable.Core": { 19 | "type": "Direct", 20 | "requested": "[4.5.0, )", 21 | "resolved": "4.5.0", 22 | "contentHash": "ZcX8XN5sQZGCbrS8VYnsa/ynhrCBfpDyqDkTl33GTXOpgfKibxoq0+W0hCSbRzuukVNoLtqGL/B6+8yTNDXbNA==" 23 | }, 24 | "Fable.Fetch": { 25 | "type": "Direct", 26 | "requested": "[2.7.0, )", 27 | "resolved": "2.7.0", 28 | "contentHash": "2ndGZZTqpX9Hyso51tnIxWAskN2zrHX+7LeAwfG4zew+DtMMGa/3IyJGl2BOYUwweq2MhfuVqs1K3avgBFDq+Q==", 29 | "dependencies": { 30 | "FSharp.Core": "4.7.2", 31 | "Fable.Browser.Blob": "1.2.0", 32 | "Fable.Browser.Event": "1.5.0", 33 | "Fable.Core": "3.7.1", 34 | "Fable.Promise": "2.2.2" 35 | } 36 | }, 37 | "Oxpecker.Solid": { 38 | "type": "Direct", 39 | "requested": "[0.6.0, )", 40 | "resolved": "0.6.0", 41 | "contentHash": "4VwfntvHoIGHwS83P814+lwAuN/taN0mSM29KVs6vRTk8qxEjFwCEgE734psX597/bnqUdM11/ecqg8EU+VnUQ==", 42 | "dependencies": { 43 | "FSharp.Core": "9.0.201", 44 | "Fable.Browser.Dom": "2.18.1", 45 | "Fable.Core": "4.5.0", 46 | "Oxpecker.Solid.FablePlugin": "0.6.0" 47 | } 48 | }, 49 | "Thoth.Fetch": { 50 | "type": "Direct", 51 | "requested": "[3.0.1, )", 52 | "resolved": "3.0.1", 53 | "contentHash": "5i8KQwTFzDEoIjE/fAwCw0GFICCsFzVkVq2w4uU1fRlOqbSfLlUNcCEq6JkeAvQ+Jj7syMKNPSH994T8NswcpA==", 54 | "dependencies": { 55 | "FSharp.Core": "4.7.2", 56 | "Fable.Core": "3.2.8", 57 | "Fable.Fetch": "2.1.0", 58 | "Fable.Promise": "2.0.0", 59 | "Thoth.Json": "6.0.0" 60 | } 61 | }, 62 | "Thoth.Json": { 63 | "type": "Direct", 64 | "requested": "[10.4.1, )", 65 | "resolved": "10.4.1", 66 | "contentHash": "hs76/uO+gHhvnlaxQDqbpUX2Y0L97ilEZ1Nx+LA4D6N7fuAYJmNwQWZB/KQLBE7wIeWK5oXMFHCuKdImSrF1Bg==", 67 | "dependencies": { 68 | "FSharp.Core": "5.0.2", 69 | "Fable.Core": "4.1.0" 70 | } 71 | }, 72 | "Fable.AST": { 73 | "type": "Transitive", 74 | "resolved": "4.5.0", 75 | "contentHash": "EAQhSI5oRfCDPj87QVk0OndyLBABp7lNqzvUkLWoAQN1jHhCLHBQuHmzA76PBVPQ65NhBoHXu8ojf8tL6PHjwA==" 76 | }, 77 | "Fable.Browser.Blob": { 78 | "type": "Transitive", 79 | "resolved": "1.4.0", 80 | "contentHash": "UlaxrIXUfMmABjP+8a4XJp/Af+eCRKa8KJ57Olq4sqphmPLn/gNtp3sk5hRNBZ385lwUszbO5yd3Q/rrl9BdOQ==", 81 | "dependencies": { 82 | "FSharp.Core": "4.7.2", 83 | "Fable.Core": "3.2.8" 84 | } 85 | }, 86 | "Fable.Browser.Event": { 87 | "type": "Transitive", 88 | "resolved": "1.7.0", 89 | "contentHash": "x+wqXQK0l4VlCnELDp68GC/mZAx6NbicDxYPliyAoNq8RPNDeR3R782icNwI5YmA+ufq11XvG6w1JjsL/ldy7w==", 90 | "dependencies": { 91 | "FSharp.Core": "4.7.2", 92 | "Fable.Browser.Gamepad": "1.3.0", 93 | "Fable.Core": "3.2.8" 94 | } 95 | }, 96 | "Fable.Browser.Gamepad": { 97 | "type": "Transitive", 98 | "resolved": "1.3.0", 99 | "contentHash": "C4HZDzCgff+U094QjpQlJh425W5j5/vojvOi2FV5UFS34l7TJ6YBgBPpKoro02QhAi/UF3AeocR+V2yiYxHb0A==", 100 | "dependencies": { 101 | "FSharp.Core": "4.7.2", 102 | "Fable.Core": "3.2.8" 103 | } 104 | }, 105 | "Fable.Browser.WebStorage": { 106 | "type": "Transitive", 107 | "resolved": "1.3.0", 108 | "contentHash": "x8JL9oEtPiK0JY4GrRTqhomiLxT6Jaiv5uu8VXiNeA78bFvUogZWxQeejsK83iNFGErK5wpdiPd0tsREZTRLeg==", 109 | "dependencies": { 110 | "FSharp.Core": "4.7.2", 111 | "Fable.Browser.Event": "1.6.0", 112 | "Fable.Core": "3.2.8" 113 | } 114 | }, 115 | "Fable.Promise": { 116 | "type": "Transitive", 117 | "resolved": "2.2.2", 118 | "contentHash": "yHFSo7GCY0l/Wjskh/HESuFoGzXIoRM22UlrARA5ewnX736Y1wM27kcqCWeGcIzaEsgJnZcDkp093M0gQyMcWA==", 119 | "dependencies": { 120 | "FSharp.Core": "4.7.2", 121 | "Fable.Core": "3.1.5" 122 | } 123 | }, 124 | "Oxpecker.Solid.FablePlugin": { 125 | "type": "Transitive", 126 | "resolved": "0.6.0", 127 | "contentHash": "1kTq6OTN8YO0fzZe2ih/LtGiCFBdbuLxZeEalBVAqcS38c9gfCKs48Dkp2IQnJvbGi3IqYSXjuRqrh8uw6avDw==", 128 | "dependencies": { 129 | "FSharp.Core": "9.0.201", 130 | "Fable.AST": "4.5.0" 131 | } 132 | }, 133 | "FSharp.Core": { 134 | "type": "CentralTransitive", 135 | "requested": "[9.0.201, )", 136 | "resolved": "9.0.201", 137 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /interactive/XParsec.Client/public/oxpecker-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roboz0r/XParsec/09c13a07b38273c4413401b6b8c726b57fe415de/interactive/XParsec.Client/public/oxpecker-128.png -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/API.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Client.API 2 | 3 | open Oxpecker.Solid 4 | open Fable.Core 5 | open Thoth.Fetch 6 | open Thoth.Json 7 | 8 | open XParsec.Shared 9 | open Serialization 10 | 11 | let apiBase = "http://localhost:65357" 12 | 13 | 14 | let fetchRoot () : JS.Promise = 15 | promise { 16 | let! response = Fetch.fetch $"{apiBase}/" [] // Fetch.get($"{apiBase}/", caseStrategy = CaseStrategy.CamelCase) 17 | return! response.text () 18 | } 19 | 20 | let private coders = 21 | Extra.empty |> Extra.withCustom ParseRequest.encode ParseRequest.decoder 22 | 23 | let parseContent (input: ParseRequest) : JS.Promise = 24 | promise { 25 | let! response = Fetch.post ($"{apiBase}/parse", input, caseStrategy = CaseStrategy.CamelCase, extra = coders) 26 | do! Promise.sleep 1000 27 | return response 28 | } 29 | 30 | let orders, ordersMgr = createResource (fetchRoot) 31 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/App.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Client.App 2 | 3 | open Oxpecker.Solid 4 | 5 | open Browser 6 | open Browser.Types 7 | open Fable.Core.JsInterop 8 | open System 9 | 10 | open XParsec.Shared 11 | 12 | type Deferred<'T> = 13 | | NotStarted 14 | | InProgress of id: Guid 15 | | Success of value: 'T 16 | | Failure of error: string 17 | 18 | module Deferred = 19 | let resolve deferred id value = 20 | match deferred with 21 | | InProgress id' when id = id' -> 22 | match value with 23 | | Ok value -> Success value 24 | | Error error -> Failure error 25 | | _ -> deferred 26 | 27 | let start get set processReq request (id: Guid) = 28 | set (InProgress id) 29 | 30 | promise { 31 | try 32 | let! result = processReq request 33 | do set (resolve (get ()) id (Ok result)) 34 | with ex -> 35 | do set (resolve (get ()) id (Error ex.Message)) 36 | } 37 | 38 | [] 39 | let App () : HtmlElement = 40 | let inputText, setInputText = createSignal "" 41 | let outputState, setOutputState = createSignal NotStarted 42 | 43 | let mutable textAreaRef = Unchecked.defaultof // mutable reference to textarea element 44 | 45 | let updateOuputState value = 46 | let id = Guid.NewGuid() 47 | 48 | let value = 49 | { 50 | Id = id 51 | Input = value 52 | Syntax = ParseSyntax.Json 53 | } 54 | 55 | let set x = 56 | setOutputState x 57 | 58 | match textAreaRef, x with 59 | | null, _ -> () 60 | | textArea, Success value -> textArea.value <- value.Message 61 | | textArea, Failure error -> textArea.value <- error 62 | | textArea, InProgress id -> textArea.value <- "Parsing..." 63 | | _ -> () 64 | 65 | Deferred.start outputState set API.parseContent value id |> ignore 66 | 67 | let handleInputChange (event: Event) = 68 | let text: string = event.target?value 69 | setInputText text 70 | updateOuputState text 71 | 72 | 73 | div (class' = "flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white") { 74 | h1 () { "XParsec Test Client" } 75 | 76 | div (class' = "flex flex-col justify-between mb-5") { 77 | input ( 78 | type' = "text", 79 | placeholder = "Enter partial json to test parser errors", 80 | required = true, 81 | value = inputText (), 82 | onChange = handleInputChange, 83 | class' = 84 | "w-auto flex-1 p-2.5 border-neutral-700 border rounded mr-2.5 bg-neutral-800 text-neutral-200 sm:w-96" 85 | ) 86 | 87 | (textarea ( 88 | placeholder = "Output", 89 | readonly = true, 90 | rows = 10, 91 | class' = 92 | "w-auto flex-1 p-2.5 border-neutral-700 border rounded mr-2.5 bg-neutral-800 text-neutral-200 sm:w-96" 93 | )) 94 | .ref (fun e -> textAreaRef <- e) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/Component.fs: -------------------------------------------------------------------------------- 1 | namespace Oxpecker.Solid.Component 2 | 3 | open Oxpecker.Solid 4 | open Fable.Core 5 | 6 | [] 7 | type ImportedTag() = 8 | inherit RegularNode() 9 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/Program.fs: -------------------------------------------------------------------------------- 1 | open Browser 2 | open Oxpecker.Solid 3 | open Oxpecker.Solid.Router 4 | open Fable.Core.JsInterop 5 | 6 | open XParsec.Client.App 7 | 8 | // HMR doesn't work in Root for some reason 9 | [] 10 | let Root () = 11 | Router() { Route(path = "/", component' = App) } 12 | 13 | render (Root, document.getElementById "root") 14 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/Serialization.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Client.Serialization 2 | 3 | open System 4 | open Thoth.Json 5 | open XParsec.Shared 6 | 7 | module ParseSyntax = 8 | let encode = 9 | function 10 | | ParseSyntax.Json -> "json" 11 | 12 | let decode = 13 | function 14 | | "json" -> ParseSyntax.Json 15 | | x -> failwithf "Unknown ParseSyntax: %s" x 16 | 17 | module ParseRequest = 18 | let encode (x: ParseRequest) = 19 | Encode.object 20 | [ 21 | "id", Encode.string (x.Id.ToString()) 22 | "input", Encode.string x.Input 23 | "syntax", Encode.string (ParseSyntax.encode x.Syntax) 24 | ] 25 | 26 | let decoder: Decoder = 27 | Decode.object (fun get -> 28 | { 29 | Id = get.Required.Field "id" Decode.guid 30 | Input = get.Required.Field "input" Decode.string 31 | Syntax = get.Required.Field "syntax" (Decode.string |> Decode.map ParseSyntax.decode) 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | -------------------------------------------------------------------------------- /interactive/XParsec.Client/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import solidPlugin from 'vite-plugin-solid'; 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | clearScreen: false, 8 | server: { 9 | watch: { 10 | ignored: [ 11 | "**/*.md" , // Don't watch markdown files 12 | "**/*.fs" , // Don't watch F# files 13 | "**/*.fsx" // Don't watch F# script files 14 | ] 15 | } 16 | }, 17 | plugins: [ 18 | tailwindcss(), 19 | solidPlugin(), 20 | ], 21 | }) -------------------------------------------------------------------------------- /interactive/XParsec.Server/Handlers.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Server.Handlers 2 | 3 | open System 4 | open System.Threading.Tasks 5 | open Microsoft.AspNetCore.Http 6 | open Microsoft.AspNetCore.Http.Extensions 7 | open Microsoft.Extensions.Logging 8 | 9 | open Thoth.Json.System.Text.Json 10 | open Oxpecker 11 | open XParsec 12 | open XParsec.Shared 13 | 14 | open type Microsoft.AspNetCore.Http.TypedResults 15 | open System.Text.Json 16 | open Serialization 17 | 18 | #nowarn FS3511 // This state machine is not statically compilable 19 | 20 | let parse (ctx: HttpContext) : Task = 21 | let noInput = 22 | { 23 | RequestId = Guid.Empty 24 | Success = false 25 | Message = "No input provided" 26 | } 27 | 28 | task { 29 | let logger = ctx.GetLogger() 30 | 31 | try 32 | let! input = ctx.Request.ReadFromJsonAsync() 33 | 34 | match input with 35 | | null -> return! json noInput ctx 36 | | input -> 37 | let input = Decode.fromValue ParseRequest.decoder input.RootElement 38 | 39 | match input with 40 | | Error _ -> return! json noInput ctx 41 | | Ok input -> 42 | logger.LogInformation("Parsed input: {0}", input) 43 | 44 | if String.IsNullOrWhiteSpace input.Input then 45 | logger.LogInformation("No input provided") 46 | return! json noInput ctx 47 | else 48 | let success, msg = 49 | match input.Syntax with 50 | | ParseSyntax.Json -> 51 | let reader = Reader.ofString input.Input () 52 | let pJson = XParsec.Json.JsonParsers.Parser 53 | 54 | match pJson reader with 55 | | Ok value -> 56 | logger.LogInformation("Parsed JSON: {0}", value) 57 | true, "Parsed JSON" 58 | | Error msg -> 59 | logger.LogError("Error parsing JSON: {0}", msg) 60 | false, ErrorFormatting.formatStringError input.Input msg 61 | 62 | let resp = 63 | { 64 | RequestId = input.Id 65 | Success = success 66 | Message = msg 67 | } 68 | 69 | return! json resp ctx 70 | with ex -> 71 | return! 72 | json 73 | { 74 | RequestId = Guid.Empty 75 | Success = false 76 | Message = "Error: " + ex.Message 77 | } 78 | ctx 79 | } 80 | 81 | let notFound (ctx: HttpContext) : Task = 82 | let logger = ctx.GetLogger() 83 | logger.LogWarning("Unhandled 404 error") 84 | ctx.Write <| NotFound {| Error = "Resource was not found" |} 85 | 86 | let endpoints = 87 | [ 88 | // Add routes here 89 | route "/" <| text "Hello World!" 90 | route "/parse" <| parse 91 | ] 92 | -------------------------------------------------------------------------------- /interactive/XParsec.Server/Program.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Server.Program 2 | 3 | open Microsoft.AspNetCore.Builder 4 | open Microsoft.AspNetCore.Http 5 | open Microsoft.AspNetCore.Http.Json 6 | open Microsoft.Extensions.DependencyInjection 7 | 8 | open System 9 | open Oxpecker 10 | open XParsec.Shared 11 | open Handlers 12 | 13 | open type Microsoft.AspNetCore.Http.TypedResults 14 | 15 | 16 | let configureServices (services: IServiceCollection) = 17 | services 18 | .AddCors(fun options -> 19 | options.AddDefaultPolicy(fun policy -> policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader() |> ignore) 20 | ) 21 | .AddRouting() 22 | .AddOxpecker() 23 | |> ignore 24 | 25 | let configureApp (app: IApplicationBuilder) = 26 | app.UseRouting().UseCors().UseOxpecker(endpoints).Run(Handlers.notFound) 27 | 28 | [] 29 | let main args = 30 | let builder = WebApplication.CreateBuilder(args) 31 | configureServices builder.Services 32 | let app = builder.Build() 33 | configureApp app 34 | app.Run() 35 | 0 // Exit code 36 | -------------------------------------------------------------------------------- /interactive/XParsec.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "XParsec.Server": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "https://localhost:65356;http://localhost:65357" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /interactive/XParsec.Server/Serialization.fs: -------------------------------------------------------------------------------- 1 | module XParsec.Server.Serialization 2 | 3 | open System 4 | open System.Text.Json 5 | open Thoth.Json.Core 6 | open Thoth.Json.System.Text.Json 7 | open XParsec.Shared 8 | 9 | module ParseSyntax = 10 | let encode = 11 | function 12 | | ParseSyntax.Json -> "json" 13 | 14 | let decode = 15 | function 16 | | "json" -> ParseSyntax.Json 17 | | x -> failwithf "Unknown ParseSyntax: %s" x 18 | 19 | module ParseRequest = 20 | let encode (x: ParseRequest) = 21 | Encode.helpers.encodeObject 22 | [ 23 | "id", Encode.helpers.encodeString (x.Id.ToString()) 24 | "input", Encode.helpers.encodeString x.Input 25 | "syntax", Encode.helpers.encodeString (ParseSyntax.encode x.Syntax) 26 | ] 27 | 28 | let decoder: Decoder = 29 | Decode.object (fun get -> 30 | { 31 | Id = get.Required.Field "id" Decode.guid 32 | Input = get.Required.Field "input" Decode.string 33 | Syntax = get.Required.Field "syntax" (Decode.string |> Decode.map ParseSyntax.decode) 34 | } 35 | ) 36 | -------------------------------------------------------------------------------- /interactive/XParsec.Server/XParsec.Server.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | false 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /interactive/XParsec.Server/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Oxpecker": { 6 | "type": "Direct", 7 | "requested": "[1.2.0, )", 8 | "resolved": "1.2.0", 9 | "contentHash": "ut32Q0uHtV2o0T/dx6wld0sCNl5YQjlAMI/OkJFmDISD53VIOw44n+R/z7OzsJiGnKkig9yw4EqAJEVp1HfNrw==", 10 | "dependencies": { 11 | "FSharp.Core": "9.0.100", 12 | "Microsoft.IO.RecyclableMemoryStream": "3.0.1", 13 | "Oxpecker.ViewEngine": "1.1.0" 14 | } 15 | }, 16 | "Thoth.Json.System.Text.Json": { 17 | "type": "Direct", 18 | "requested": "[0.2.1, )", 19 | "resolved": "0.2.1", 20 | "contentHash": "fEh3IyCtTJhfS6olPsTuU0YSnl3DT755wzyz332TXY4DGIIXkHfcSZkVZ3soDKlsRCd0HvAGqs8jXrYK4+0/aw==", 21 | "dependencies": { 22 | "FSharp.Core": "5.0.2", 23 | "Fable.Core": "4.1.0", 24 | "System.Text.Json": "9.0.0", 25 | "Thoth.Json.Core": "0.7.0" 26 | } 27 | }, 28 | "Microsoft.Extensions.ObjectPool": { 29 | "type": "Transitive", 30 | "resolved": "9.0.0", 31 | "contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA==" 32 | }, 33 | "Microsoft.IO.RecyclableMemoryStream": { 34 | "type": "Transitive", 35 | "resolved": "3.0.1", 36 | "contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g==" 37 | }, 38 | "Oxpecker.ViewEngine": { 39 | "type": "Transitive", 40 | "resolved": "1.1.0", 41 | "contentHash": "VjdtK+Bwui0nPf/T4wVWhxgGMpyt3hEjaRxtFT3HKdzlr+JbH0C1qd/SxhKsuD90jM9EJObCgPrgBdPj576HVw==", 42 | "dependencies": { 43 | "FSharp.Core": "9.0.100", 44 | "Microsoft.Extensions.ObjectPool": "9.0.0" 45 | } 46 | }, 47 | "System.Text.Json": { 48 | "type": "Transitive", 49 | "resolved": "9.0.0", 50 | "contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==" 51 | }, 52 | "Thoth.Json.Core": { 53 | "type": "Transitive", 54 | "resolved": "0.7.0", 55 | "contentHash": "BENcsjQMfG++NvzIUQG3xv8esB2PfUfe8ggpcNwhmqEPuJp8KORM5MCNqqwuBHZ4LOUK61L/bldFPGqr0K9sLQ==", 56 | "dependencies": { 57 | "FSharp.Core": "5.0.2", 58 | "Fable.Core": "4.1.0" 59 | } 60 | }, 61 | "xparsec": { 62 | "type": "Project", 63 | "dependencies": { 64 | "FSharp.Core": "[9.0.201, )", 65 | "System.Collections.Immutable": "[9.0.3, )" 66 | } 67 | }, 68 | "xparsec.json": { 69 | "type": "Project", 70 | "dependencies": { 71 | "XParsec": "[0.1.0, )" 72 | } 73 | }, 74 | "Fable.Core": { 75 | "type": "CentralTransitive", 76 | "requested": "[4.5.0, )", 77 | "resolved": "4.5.0", 78 | "contentHash": "ZcX8XN5sQZGCbrS8VYnsa/ynhrCBfpDyqDkTl33GTXOpgfKibxoq0+W0hCSbRzuukVNoLtqGL/B6+8yTNDXbNA==" 79 | }, 80 | "FSharp.Core": { 81 | "type": "CentralTransitive", 82 | "requested": "[9.0.201, )", 83 | "resolved": "9.0.201", 84 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 85 | }, 86 | "System.Collections.Immutable": { 87 | "type": "CentralTransitive", 88 | "requested": "[9.0.3, )", 89 | "resolved": "9.0.3", 90 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xparsec.tests", 3 | "version": "1.0.0", 4 | "description": "XParsec Tests", 5 | "type": "module", 6 | "main": "-", 7 | "scripts": { 8 | "test": "dotnet fable ./test/XParsec.Tests/XParsec.Tests.fsproj -o ./test/XParsec.Tests/js -s && node ./test/XParsec.Tests/js/Main.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/roboz0r/XParsec.git" 13 | }, 14 | "author": "Robert Lenders", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/roboz0r/XParsec/issues" 18 | }, 19 | "homepage": "https://github.com/roboz0r/XParsec#readme" 20 | } 21 | -------------------------------------------------------------------------------- /src/Fable.Package.SDK/Fable.Package.SDK.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | embedded 12 | 14 | true 15 | 16 | 17 | 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Fable.Package.SDK/Fable.Package.SDK.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 10 | 11 | <_PackageFiles Include="@(Compile)"> 12 | true 13 | fable/%(RecursiveDir)%(Filename)%(Extension) 14 | None 15 | 16 | 17 | <_PackageFiles Include="$(MSBuildProjectFullPath)"> 18 | true 19 | fable/ 20 | None 21 | 22 | 23 | 24 | 25 | 26 | 27 | $(GenerateNuspecDependsOn);CreateFablePackageFiles 28 | 29 | 30 | 33 | 34 | 35 | $(PackageTags);fable 38 | 39 | $(PackageTags);fable-library 43 | $(PackageTags);fable-binding 47 | 48 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 71 | 72 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/Fable.Package.SDK/README.md: -------------------------------------------------------------------------------- 1 | # Fable.Package.SDK 2 | 3 | These files copied from temporarily until [Issue 11](https://github.com/fable-compiler/Fable.Package.SDK/issues/11) can be resolved. 4 | -------------------------------------------------------------------------------- /src/XParsec.CLArgs/Types.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec.CLArgs 2 | 3 | open XParsec 4 | open XParsec.CharParsers 5 | 6 | type CLOption<'T> = 7 | | CLFlag of name: string * result: 'T * alias: char option 8 | | CLSetting of name: string * toResult: (string -> 'T) * alias: char option * allowEqualsAssginment: bool 9 | | CLBoolSetting of name: string * toResult: (bool -> 'T) * alias: char option * allowEqualsAssginment: bool 10 | | CLParsedSetting of 11 | name: string * 12 | parser: Parser<'T, char, unit, ReadableString, ReadableStringSlice> * 13 | alias: char option * 14 | allowEqualsAssginment: bool 15 | 16 | module NestedParser = 17 | open System.Text 18 | 19 | let pNestedString innerState (pInner: Parser<_, _, _, _, _>) (reader: Reader<_, _, _, _>) : ParseResult<_, _, _> = 20 | match reader.Peek() with 21 | | ValueSome(x: string) -> 22 | let innerReader = Reader.ofString x innerState 23 | 24 | match pInner innerReader with 25 | | Ok result -> 26 | reader.Skip() 27 | ParseSuccess.create result.Parsed 28 | 29 | | Error e -> 30 | let formatError = ErrorFormatting.formatStringError x e 31 | ParseError.create (Message formatError) reader.Position 32 | 33 | | ValueNone -> ParseError.create EndOfInput reader.Position 34 | 35 | open NestedParser 36 | 37 | 38 | module CLParser = 39 | open XParsec.Parsers 40 | 41 | let private pEqualsAssignment setting pValue = 42 | parser { 43 | let! _ = pstring setting 44 | let! _ = pchar '=' 45 | let! value = pValue 46 | do! eof 47 | return value 48 | } 49 | 50 | let private pbool = 51 | parser { return! (pstring "true" >>% true) <|> (pstring "false" >>% false) } 52 | 53 | let private parseSetting name parser alias allowEqualsAssginment = 54 | seq { 55 | let setting = $"--%s{name}" 56 | yield pitem setting >>. (pNestedString () parser) 57 | 58 | if allowEqualsAssginment then 59 | let pSetting = pEqualsAssignment setting parser 60 | 61 | yield pNestedString () pSetting 62 | 63 | match alias with 64 | | Some alias -> 65 | let aliasSetting = $"-%c{alias}" 66 | yield pitem aliasSetting >>. (pNestedString () parser) 67 | 68 | if allowEqualsAssginment then 69 | let pAliasSetting = pEqualsAssignment aliasSetting parser 70 | 71 | yield pNestedString () pAliasSetting 72 | 73 | | None -> () 74 | 75 | } 76 | |> choice 77 | 78 | let ofOption (option: CLOption<'T>) : Parser<_, _, _, _, _> = 79 | match option with 80 | | CLFlag(name, result, alias) -> 81 | let flag = $"--%s{name}" 82 | 83 | match alias with 84 | | Some alias -> 85 | let aliasFlag = $"-%c{alias}" 86 | let pName = pitem flag 87 | let pAlias = pitem aliasFlag 88 | (pName <|> pAlias) >>% result 89 | 90 | | None -> itemReturn flag result 91 | | CLSetting(name, toResult, alias, allowEqualsAssginment) -> 92 | let p = (many1Chars anyChar) |>> toResult 93 | parseSetting name p alias allowEqualsAssginment 94 | 95 | | CLBoolSetting(name, toResult, alias, allowEqualsAssginment) -> 96 | parseSetting name (pbool |>> toResult) alias allowEqualsAssginment 97 | 98 | | CLParsedSetting(name, parser, alias, allowEqualsAssginment) -> 99 | parseSetting name parser alias allowEqualsAssginment 100 | 101 | let ofOptions (options: CLOption<'T> list) : Parser<_, _, _, _, _> = 102 | let p = options |> List.map ofOption |> choice |> many 103 | 104 | p .>> eof 105 | -------------------------------------------------------------------------------- /src/XParsec.CLArgs/XParsec.CLArgs.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;netstandard2.0 4 | true 5 | true 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/XParsec.CLArgs/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | ".NETStandard,Version=v2.0": { 5 | "NETStandard.Library": { 6 | "type": "Direct", 7 | "requested": "[2.0.3, )", 8 | "resolved": "2.0.3", 9 | "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 10 | "dependencies": { 11 | "Microsoft.NETCore.Platforms": "1.1.0" 12 | } 13 | }, 14 | "System.Collections.Immutable": { 15 | "type": "Direct", 16 | "requested": "[9.0.3, )", 17 | "resolved": "9.0.3", 18 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==", 19 | "dependencies": { 20 | "System.Memory": "4.5.5", 21 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 22 | } 23 | }, 24 | "Microsoft.NETCore.Platforms": { 25 | "type": "Transitive", 26 | "resolved": "1.1.0", 27 | "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" 28 | }, 29 | "System.Buffers": { 30 | "type": "Transitive", 31 | "resolved": "4.5.1", 32 | "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" 33 | }, 34 | "System.Memory": { 35 | "type": "Transitive", 36 | "resolved": "4.5.5", 37 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", 38 | "dependencies": { 39 | "System.Buffers": "4.5.1", 40 | "System.Numerics.Vectors": "4.4.0", 41 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 42 | } 43 | }, 44 | "System.Numerics.Vectors": { 45 | "type": "Transitive", 46 | "resolved": "4.4.0", 47 | "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" 48 | }, 49 | "System.Runtime.CompilerServices.Unsafe": { 50 | "type": "Transitive", 51 | "resolved": "6.0.0", 52 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 53 | }, 54 | "xparsec": { 55 | "type": "Project", 56 | "dependencies": { 57 | "FSharp.Core": "[9.0.201, )", 58 | "System.Collections.Immutable": "[9.0.3, )" 59 | } 60 | }, 61 | "FSharp.Core": { 62 | "type": "CentralTransitive", 63 | "requested": "[9.0.201, )", 64 | "resolved": "9.0.201", 65 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 66 | } 67 | }, 68 | "net8.0": { 69 | "Microsoft.NET.ILLink.Tasks": { 70 | "type": "Direct", 71 | "requested": "[8.0.15, )", 72 | "resolved": "8.0.15", 73 | "contentHash": "s4eXlcRGyHeCgFUGQnhq0e/SCHBPp0jOHgMqZg3fQ2OCHJSm1aOUhI6RFWuVIcEb9ig2WgI2kWukk8wu72EbUQ==" 74 | }, 75 | "System.Collections.Immutable": { 76 | "type": "Direct", 77 | "requested": "[9.0.3, )", 78 | "resolved": "9.0.3", 79 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 80 | }, 81 | "xparsec": { 82 | "type": "Project", 83 | "dependencies": { 84 | "FSharp.Core": "[9.0.201, )", 85 | "System.Collections.Immutable": "[9.0.3, )" 86 | } 87 | }, 88 | "FSharp.Core": { 89 | "type": "CentralTransitive", 90 | "requested": "[9.0.201, )", 91 | "resolved": "9.0.201", 92 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/XParsec.Json/JsonParser.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec.Json 2 | 3 | open System 4 | open System.Collections.Immutable 5 | open System.Text 6 | 7 | open XParsec 8 | open XParsec.Parsers 9 | open XParsec.CharParsers 10 | 11 | #nowarn "40" // Recursive value definitions 12 | 13 | [] 14 | type JsonValue = 15 | | String of string 16 | | Number of float 17 | | Object of JsonObject 18 | | Array of JsonArray 19 | | True 20 | | False 21 | | Null 22 | 23 | and JsonMember = { Name: string; Value: JsonValue } 24 | and JsonObject = ImmutableArray 25 | 26 | and JsonArray = ImmutableArray 27 | 28 | type JsonParsers<'Input, 'InputSlice 29 | when 'Input :> IReadable and 'InputSlice :> IReadable> = 30 | 31 | static let pWhitespace = skipMany (anyOf [ ' '; '\t'; '\n'; '\r' ]) 32 | 33 | static let pDigit = satisfyL (fun c -> c >= '0' && c <= '9') ("Char in range '0' - '9'") 34 | 35 | static let pOneNine = satisfyL (fun c -> c >= '1' && c <= '9') ("Char in range '1' - '9'") 36 | 37 | static let pFraction = 38 | parser { 39 | let! dot = pitem '.' 40 | let! digits = many1Chars pDigit 41 | return digits 42 | } 43 | 44 | static let pExponent = 45 | parser { 46 | let! e = anyOf [ 'e'; 'E' ] 47 | let! sign = opt (anyOf [ '+'; '-' ]) 48 | let! digits = many1Chars pDigit 49 | return struct (sign, digits) 50 | } 51 | 52 | static let pNumber = 53 | parser { 54 | let! sign = opt (pitem '-') 55 | 56 | let! int = (pitem '0' >>% "0") <|> (many1Chars2 pOneNine pDigit) 57 | 58 | let! fraction = opt pFraction 59 | let! exponent = opt pExponent 60 | 61 | let! number = 62 | let sb = StringBuilder(16) 63 | 64 | match sign with 65 | | ValueSome c -> sb.Append(c) |> ignore 66 | | _ -> () 67 | 68 | sb.Append(int) |> ignore 69 | 70 | match fraction with 71 | | ValueSome f -> sb.Append('.').Append(f) |> ignore 72 | | _ -> () 73 | 74 | match exponent with 75 | | ValueSome(sign, digits) -> 76 | sb.Append('e') |> ignore 77 | 78 | match sign with 79 | | ValueSome sign -> sb.Append(sign) |> ignore 80 | | ValueNone -> () 81 | 82 | sb.Append(digits) |> ignore 83 | | _ -> () 84 | 85 | let number = sb.ToString() 86 | 87 | match Double.TryParse(number) with 88 | | true, result -> preturn result 89 | | _ -> failwithf "Failed to parse number: %s" number 90 | 91 | return JsonValue.Number number 92 | } 93 | 94 | static let pEscape = 95 | fun (reader: Reader<_, _, _, _>) -> 96 | let span = reader.PeekN 2 97 | 98 | if span.Length = 2 && span[0] = '\\' then 99 | match span[1] with 100 | | '"' 101 | | '\\' 102 | | '/' as c -> 103 | reader.SkipN 2 104 | preturn c reader 105 | | 'b' -> 106 | reader.SkipN 2 107 | preturn '\b' reader 108 | | 'f' -> 109 | reader.SkipN 2 110 | preturn '\f' reader 111 | | 'n' -> 112 | reader.SkipN 2 113 | preturn '\n' reader 114 | | 'r' -> 115 | reader.SkipN 2 116 | preturn '\r' reader 117 | | 't' -> 118 | reader.SkipN 2 119 | preturn '\t' reader 120 | | c -> fail (Unexpected c) reader 121 | else 122 | fail (Message "Escape char") reader 123 | 124 | 125 | //static let pEscape_CE = 126 | // parser { 127 | // let! _ = pitem '\\' 128 | // let! escaped = anyOf [ '"'; '\\'; '/'; 'b'; 'f'; 'n'; 'r'; 't' ] 129 | 130 | // return 131 | // match escaped with 132 | // | 'b' -> '\b' 133 | // | 'f' -> '\f' 134 | // | 'n' -> '\n' 135 | // | 'r' -> '\r' 136 | // | 't' -> '\t' 137 | // | c -> c 138 | // } 139 | 140 | static let pHexDigit = 141 | satisfyL Char.IsAsciiHexDigit ("Hex digit") 142 | |>> function 143 | | c when c >= '0' && c <= '9' -> int c - int '0' 144 | | c when c >= 'a' && c <= 'f' -> int c - int 'a' + 10 145 | | c when c >= 'A' && c <= 'F' -> int c - int 'A' + 10 146 | | _ -> failwith "Invalid hex digit" 147 | 148 | static let pUnicodeEscape: Parser<_, _, _, _, _> = 149 | parser { 150 | let! _ = pitem '\\' 151 | let! _ = pitem 'u' 152 | let! hex0 = pHexDigit 153 | let! hex1 = pHexDigit 154 | let! hex2 = pHexDigit 155 | let! hex3 = pHexDigit 156 | let hexValue = (hex0 <<< 12) + (hex1 <<< 8) + (hex2 <<< 4) + hex3 157 | return Convert.ToChar(hexValue) 158 | } 159 | 160 | static let pOtherChar: Parser<_, _, _, _, _> = 161 | satisfyL 162 | (function 163 | | '"' 164 | | '\\' -> false 165 | | c -> not (Char.IsControl c)) 166 | ("Other Char") 167 | 168 | static let pString = 169 | parser { 170 | let! _ = pitem '"' 171 | let! chars = manyChars (choiceL [ pEscape; pUnicodeEscape; pOtherChar ] "") 172 | let! _ = pitem '"' 173 | return chars 174 | } 175 | 176 | static let pTrue = pstring "true" >>% JsonValue.True 177 | static let pFalse = pstring "false" >>% JsonValue.False 178 | static let pNull = pstring "null" >>% JsonValue.Null 179 | static let pStringValue = pString |>> JsonValue.String 180 | 181 | static let rec pValue: Parser = 182 | fun cursor -> 183 | match cursor.Peek() with 184 | | ValueSome '{' -> pObject cursor 185 | | ValueSome '[' -> pArray cursor 186 | | ValueSome '"' -> pStringValue cursor 187 | | ValueSome 't' -> pTrue cursor 188 | | ValueSome 'f' -> pFalse cursor 189 | | ValueSome 'n' -> pNull cursor 190 | | _ -> pNumber cursor 191 | 192 | and pElement = 193 | parser { 194 | let! _ = pWhitespace 195 | let! value = pValue 196 | let! _ = pWhitespace 197 | return value 198 | } 199 | 200 | and pMember = 201 | parser { 202 | let! _ = pWhitespace 203 | let! name = pString 204 | let! _ = pWhitespace 205 | let! _ = pitem ':' 206 | let! value = pElement 207 | return { Name = name; Value = value } 208 | } 209 | 210 | and pObject = 211 | parser { 212 | let! _ = pitem '{' 213 | let! _ = pWhitespace 214 | let! members, _ = sepBy pMember (pitem ',') 215 | let! _ = pitem '}' 216 | return JsonValue.Object members 217 | } 218 | 219 | and pArray = 220 | parser { 221 | let! _ = pitem '[' 222 | let! _ = pWhitespace 223 | let! values, _ = sepBy pElement (pitem ',') 224 | let! _ = pitem ']' 225 | return JsonValue.Array values 226 | } 227 | 228 | static let pJson: Parser = (pElement .>> eof) 229 | 230 | static member Parser = pJson 231 | static member PString = pString 232 | -------------------------------------------------------------------------------- /src/XParsec.Json/XParsec.Json.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | true 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/XParsec.Json/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "xparsec": { 6 | "type": "Project", 7 | "dependencies": { 8 | "FSharp.Core": "[9.0.201, )", 9 | "System.Collections.Immutable": "[9.0.3, )" 10 | } 11 | }, 12 | "FSharp.Core": { 13 | "type": "CentralTransitive", 14 | "requested": "[9.0.201, )", 15 | "resolved": "9.0.201", 16 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 17 | }, 18 | "System.Collections.Immutable": { 19 | "type": "CentralTransitive", 20 | "requested": "[9.0.3, )", 21 | "resolved": "9.0.3", 22 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/XParsec/CharParsers.fs: -------------------------------------------------------------------------------- 1 | /// Defines parsers for characters and strings. 2 | /// These parsers are more optimized for working with strings than the general parsers in `XParsec.Parsers`. 3 | module XParsec.CharParsers 4 | 5 | open System 6 | open System.Text 7 | open Parsers 8 | 9 | // TODO: Look at using SearchValues https://learn.microsoft.com/en-us/dotnet/api/system.buffers.searchvalues-1?view=net-9.0 10 | [] 11 | module ParseError = 12 | let asciiLetter = Message "Expected Char in range 'A' - 'Z' or 'a' - 'z'." 13 | let digit = Message "Expected Char in range '0' - '9'." 14 | let expectedNewline = Message "Expected Newline." 15 | 16 | let inline isLetter c = Char.IsLetter(c) 17 | let isDigit c = c >= '0' && c <= '9' 18 | 19 | /// Succeeds if the next char in the input is equal to the given char, and consumes one char. Returns the char, otherwise fails with the Expected char. 20 | let pchar (c: char) (reader: Reader) = pitem c reader 21 | 22 | /// Succeeds if the next char in the input is equal to the given char, and consumes one char. Returns unit, otherwise fails with the Expected char. 23 | let skipChar (c: char) (reader: Reader) = skipItem c reader 24 | 25 | /// Succeeds if the next char in the input is equal to the given char, and consumes one char. Returns the `result`, otherwise fails with the Expected char. 26 | let charReturn (c: char) (result) (reader: Reader) = itemReturn c result reader 27 | 28 | /// Succeeds if the Reader position is not at the end of the input, and consumes one char. 29 | let anyChar (reader: Reader) = pid reader 30 | 31 | let pstring (s: string) (reader: Reader) = 32 | let span = reader.PeekN(s.Length) 33 | 34 | if span.IsEmpty then 35 | fail (EndOfInput) reader 36 | elif MemoryExtensions.Equals(s.AsSpan(), span, StringComparison.Ordinal) then 37 | reader.SkipN(s.Length) 38 | preturn s reader 39 | else 40 | fail (ExpectedSeq s) reader 41 | 42 | 43 | let stringCIReturn (s: string) (result) (reader: Reader) = 44 | let span = reader.PeekN(s.Length) 45 | 46 | if span.IsEmpty then 47 | fail (EndOfInput) reader 48 | elif MemoryExtensions.Equals(s.AsSpan(), span, StringComparison.OrdinalIgnoreCase) then 49 | reader.SkipN(s.Length) 50 | preturn result reader 51 | else 52 | fail (ExpectedSeq s) reader 53 | 54 | let stringReturn (s: string) (result) (reader: Reader) = 55 | let span = reader.PeekN(s.Length) 56 | 57 | if span.IsEmpty then 58 | fail (EndOfInput) reader 59 | elif MemoryExtensions.Equals(s.AsSpan(), span, StringComparison.Ordinal) then 60 | reader.SkipN(s.Length) 61 | preturn result reader 62 | else 63 | fail (ExpectedSeq s) reader 64 | 65 | let asciiLetter (reader: Reader) = 66 | match reader.Peek() with 67 | | ValueSome(c) -> 68 | if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') then 69 | reader.Skip() 70 | preturn c reader 71 | else 72 | fail ParseError.asciiLetter reader 73 | | _ -> fail EndOfInput reader 74 | 75 | let digit (reader: Reader) = 76 | match reader.Peek() with 77 | | ValueSome(c) -> 78 | if c >= '0' && c <= '9' then 79 | reader.Skip() 80 | preturn c reader 81 | else 82 | fail ParseError.digit reader 83 | | _ -> fail EndOfInput reader 84 | 85 | let manyChars (p1: Parser<_, char, _, _, _>) (reader: Reader) = 86 | let pos = reader.Position 87 | 88 | match p1 reader with 89 | | Ok s1 -> 90 | let sb = StringBuilder() 91 | let inline append (c: char) = sb.Append(c) |> ignore 92 | append s1.Parsed 93 | let mutable ok = true 94 | 95 | while ok do 96 | let pos = reader.Position 97 | 98 | match p1 reader with 99 | | Ok sx -> append sx.Parsed 100 | | Error _ -> 101 | reader.Position <- pos 102 | ok <- false 103 | 104 | preturn (sb.ToString()) reader 105 | | Error err -> 106 | reader.Position <- pos 107 | preturn "" reader 108 | 109 | let many1Chars (p1: Parser<_, char, _, _, _>) (reader: Reader) = 110 | match p1 reader with 111 | | Ok s1 -> 112 | let sb = StringBuilder() 113 | let inline append (c: char) = sb.Append(c) |> ignore 114 | append s1.Parsed 115 | let mutable ok = true 116 | 117 | while ok do 118 | let pos = reader.Position 119 | 120 | match p1 reader with 121 | | Ok sx -> append sx.Parsed 122 | | Error _ -> 123 | reader.Position <- pos 124 | ok <- false 125 | 126 | preturn (sb.ToString()) reader 127 | | Error err -> Error err 128 | 129 | let spaces (reader: Reader) = 130 | (manyChars ( 131 | satisfyL 132 | (function 133 | | ' ' 134 | | '\t' 135 | | '\r' 136 | | '\n' -> true 137 | | _ -> false) 138 | ("Zero or more whitespace characters") 139 | )) 140 | >>% () 141 | <| reader 142 | 143 | let spaces1 (reader: Reader) = 144 | (many1Chars ( 145 | satisfyL 146 | (function 147 | | ' ' 148 | | '\t' 149 | | '\r' 150 | | '\n' -> true 151 | | _ -> false) 152 | ("One or more whitespace characters") 153 | )) 154 | >>% () 155 | <| reader 156 | 157 | let many1Chars2 158 | (p1: Parser<_, char, _, _, _>) 159 | (p: Parser<_, char, _, _, _>) 160 | (reader: Reader) 161 | = 162 | match p1 reader with 163 | | Ok s1 -> 164 | let sb = StringBuilder() 165 | let inline append (c: char) = sb.Append(c) |> ignore 166 | append s1.Parsed 167 | let mutable ok = true 168 | 169 | while ok do 170 | let pos = reader.Position 171 | 172 | match p reader with 173 | | Ok sx -> append sx.Parsed 174 | | Error _ -> 175 | reader.Position <- pos 176 | ok <- false 177 | 178 | preturn (sb.ToString()) reader 179 | | Error err -> Error err 180 | 181 | 182 | let manyCharsTill 183 | (p: Parser<'A, _, _, _, _>) 184 | (pEnd: Parser<'B, _, _, _, _>) 185 | (reader: Reader) 186 | = 187 | let xs = StringBuilder() 188 | // let mutable reader = reader 189 | let mutable endTok = None 190 | let mutable err = None 191 | 192 | while endTok.IsNone && err.IsNone do 193 | match pEnd reader with 194 | | Ok s -> endTok <- Some s.Parsed 195 | | Error _ -> 196 | match p reader with 197 | | Ok s -> xs.Append(s.Parsed) |> ignore 198 | | Error e -> err <- Some e 199 | 200 | match err with 201 | | None -> preturn (xs.ToString(), endTok.Value) reader 202 | | Some err -> Error err 203 | 204 | 205 | let newline (reader: Reader) = 206 | let s = reader.PeekN(2) 207 | 208 | if s.IsEmpty then 209 | fail ParseError.expectedNewline reader 210 | else 211 | match s[0] with 212 | | '\n' -> 213 | reader.Skip() 214 | preturn '\n' reader 215 | | '\r' -> 216 | if s.Length = 2 && s[1] = '\n' then 217 | reader.SkipN(2) 218 | preturn '\n' reader 219 | else 220 | reader.Skip() 221 | preturn '\n' reader 222 | | _ -> fail ParseError.expectedNewline reader 223 | 224 | let skipNewline (reader: Reader) = 225 | let s = reader.PeekN(2) 226 | 227 | if s.IsEmpty then 228 | fail ParseError.expectedNewline reader 229 | else 230 | match s[0] with 231 | | '\n' -> 232 | reader.Skip() 233 | preturn () reader 234 | | '\r' -> 235 | if s.Length = 2 && s[1] = '\n' then 236 | reader.SkipN(2) 237 | preturn () reader 238 | else 239 | reader.Skip() 240 | preturn () reader 241 | | _ -> fail ParseError.expectedNewline reader 242 | 243 | let newlineReturn x (reader: Reader) = 244 | let s = reader.PeekN(2) 245 | 246 | if s.IsEmpty then 247 | fail ParseError.expectedNewline reader 248 | else 249 | match s[0] with 250 | | '\n' -> 251 | reader.Skip() 252 | preturn x reader 253 | | '\r' -> 254 | if s.Length = 2 && s[1] = '\n' then 255 | reader.SkipN(2) 256 | preturn x reader 257 | else 258 | reader.Skip() 259 | preturn x reader 260 | | _ -> fail ParseError.expectedNewline reader 261 | 262 | let anyOf (chars: char seq) = 263 | let chars = 264 | match chars with 265 | | :? string as s -> s 266 | | _ -> new String(Array.ofSeq chars) 267 | 268 | let err = $"Any character: '{chars}'" 269 | 270 | #if NET5_0_OR_GREATER 271 | satisfyL (chars.Contains: char -> bool) err 272 | #else 273 | satisfyL (fun c -> chars.Contains(string c)) err 274 | #endif 275 | -------------------------------------------------------------------------------- /src/XParsec/ErrorFormatting.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec 2 | 3 | open System 4 | open System.Collections.Immutable 5 | open System.Text 6 | open XParsec.Parsers 7 | open XParsec.CharParsers 8 | 9 | [] 10 | type LineEndings<'Input, 'InputSlice 11 | when 'Input :> IReadable and 'InputSlice :> IReadable>() = 12 | 13 | static let lineEndingParser: Parser<_, _, _, 'Input, 'InputSlice> = 14 | parser { 15 | let! _ = skipManyTill pid newline 16 | let! pos = getPosition 17 | return pos.Index - 1L 18 | } 19 | 20 | static let lineEndings: Parser<_, _, _, 'Input, 'InputSlice> = many lineEndingParser 21 | 22 | static member Parser = lineEndings 23 | 24 | type LineIndex(endings: ImmutableArray, maxIndex) = 25 | 26 | member _.Indices = endings 27 | 28 | /// Returns the 1-based line number and column number of the given 0-based index 29 | member _.GetLineCol(index: int64) = 30 | if index < 0 then 31 | invalidArg "index" "Index must be non-negative" 32 | 33 | if index > maxIndex then 34 | raise (IndexOutOfRangeException $"Index must be less than or equal to {maxIndex}") 35 | // Line and column are 1-based 36 | if endings.IsEmpty then 37 | struct (1, index + 1L) 38 | else 39 | let minV = endings.[0] 40 | 41 | if index <= minV then 42 | 1, index + 1L 43 | else 44 | // Binary search for the line number 45 | let rec findIndex low high = 46 | if low = high then 47 | low 48 | else 49 | match high - low with 50 | | 1 -> 51 | let highV = endings.[high] 52 | if highV <= index then high else low 53 | | diff -> 54 | let mid = low + diff / 2 55 | let midV = endings.[mid] 56 | 57 | if midV = index then mid 58 | elif midV < index then findIndex mid high 59 | else findIndex low mid 60 | 61 | let iLine = findIndex 0 (endings.Length - 1) 62 | 63 | match index - endings.[iLine] with 64 | | 0L -> iLine + 1, index - endings.[iLine - 1] 65 | | col -> iLine + 2, col 66 | 67 | member _.GetIndex(line: int, col: int64) = 68 | if line < 1 then 69 | invalidArg "line" "Line must be greater than 0" 70 | 71 | if col < 1 then 72 | invalidArg "col" "Column must be greater than 0" 73 | 74 | match line with 75 | | 1 -> col - 1L 76 | | _ -> 77 | let iLine = endings.[line - 2] 78 | let i = iLine + int64 col 79 | 80 | if i > maxIndex then 81 | raise (IndexOutOfRangeException $"Index must be less than or equal to {maxIndex}") 82 | 83 | i 84 | 85 | static member OfString(input: string) = 86 | match LineEndings.Parser(Reader.ofString input ()) with 87 | | Ok result -> LineIndex(result.Parsed, int64 (input.Length - 1)) 88 | | Error _ -> invalidOp "LineIndex: Failed to parse line endings" 89 | 90 | static member OfString(input: string, maxLength: int) = 91 | if maxLength < 0 then 92 | invalidArg "maxLength" "maxLength must be non-negative" 93 | 94 | if maxLength > input.Length then 95 | raise (ArgumentOutOfRangeException $"maxLength must be less than or equal to {input.Length}") 96 | 97 | let maxLength = int64 maxLength 98 | let reader = Reader.ofString input () 99 | let reader = reader.Slice(0L, int64 maxLength) 100 | 101 | match LineEndings.Parser reader with 102 | | Ok result -> LineIndex(result.Parsed, maxLength - 1L) 103 | | Error _ -> invalidOp "LineIndex: Failed to parse line endings" 104 | 105 | 106 | module ErrorFormatting = 107 | type StringBuilder with 108 | member this.Append(input: #IReadable, start: int64, count: int) = 109 | let span = input.SpanSlice(start, count) 110 | #if !FABLE_COMPILER && NET8_0_OR_GREATER 111 | this.Append(span) 112 | #else 113 | for c in span do 114 | this.Append c |> ignore 115 | 116 | this 117 | #endif 118 | 119 | member this.Append(input: #IReadable) = 120 | if input.Length > Int32.MaxValue then 121 | invalidOp "StringBuilder.Append: input is too long" 122 | 123 | let span = input.SpanSlice(0L, int input.Length) 124 | #if !FABLE_COMPILER && NET8_0_OR_GREATER 125 | this.Append(span) 126 | #else 127 | for c in span do 128 | this.Append c |> ignore 129 | 130 | this 131 | #endif 132 | 133 | [] 134 | let private UpRight = '\u2514' 135 | 136 | [] 137 | let private Horizontal = '\u2500' 138 | 139 | [] 140 | let private Vertical = '\u2502' 141 | 142 | [] 143 | let private VerticalRight = '\u251C' 144 | // TODO: Generalizing to IReadable int -> int64 conversion issues 145 | let private findIndexBack (input: #IReadable) (index: int64) (backLimit: int) = 146 | let backLimit = int64 backLimit 147 | 148 | let rec loop i = 149 | if i <= 0L then 150 | 0L 151 | else 152 | let i = min (input.Length - 1L) i 153 | 154 | match input.[i] with 155 | | '\r' 156 | | '\n' -> min (i + 1L) index 157 | | _ -> if index - i > backLimit then i else loop (i - 1L) 158 | 159 | loop index 160 | 161 | let private findIndexForward (input: #IReadable) (index: int64) (forwardLimit: int) = 162 | let forwardLimit = int64 forwardLimit 163 | 164 | let rec loop i = 165 | if i >= input.Length then 166 | input.Length 167 | else 168 | match input.[i] with 169 | | '\r' 170 | | '\n' -> max (i - 1L) index 171 | | _ -> if i - index > forwardLimit then i else loop (i + 1L) 172 | 173 | loop index 174 | 175 | let private terminalSuberror = $"{UpRight}{Horizontal}{Horizontal}{Horizontal}" 176 | 177 | let private nonTerminalSuberror = 178 | $"{VerticalRight}{Horizontal}{Horizontal}{Horizontal}" 179 | 180 | let private terminalIndent = " " 181 | let private nonTerminalIndent = $"{Vertical} " 182 | 183 | type private Prefix = 184 | | T 185 | | NT 186 | 187 | let formatErrorsLine 188 | (lineIndex: LineIndex) 189 | (input: #IReadable) 190 | (index: int64) 191 | (sb: StringBuilder) 192 | : StringBuilder = 193 | let iBack = findIndexBack input index 25 194 | let iForward = findIndexForward input index 40 195 | 196 | let sb = sb.Append(input, iBack, int (iForward - iBack)).AppendLine() 197 | 198 | let struct (ln, col) = lineIndex.GetLineCol index 199 | #if FABLE_COMPILER 200 | // TODO: Fable doesn't support Append(char, int) overload 201 | // Was added in 5.0.0-alpha.6 202 | let spaces = String.replicate (int (index - iBack)) " " 203 | sb.Append(spaces).Append('^').AppendLine($" At index {index} (Ln {ln}, Col {col})") 204 | #else 205 | sb.Append(' ', int (index - iBack)).Append('^').AppendLine($" At index {index} (Ln {ln}, Col {col})") 206 | #endif 207 | 208 | let formatParseError<'T, 'State> 209 | (formatOne: 'T -> StringBuilder -> StringBuilder) 210 | (formatSeq: 'T seq -> StringBuilder -> StringBuilder) 211 | (error: ParseError<'T, 'State>) 212 | (sb: StringBuilder) 213 | = 214 | let inline appendString (s: string) (sb: StringBuilder) = sb.Append s 215 | let inline appendNewline (sb: StringBuilder) = sb.AppendLine() 216 | 217 | let appendPrefixes prefixes (sb: StringBuilder) = 218 | let rec appendIndent prefixes (sb: StringBuilder) = 219 | match prefixes with 220 | | [] -> sb 221 | | T :: prefixes -> sb |> appendIndent prefixes |> appendString terminalIndent 222 | | NT :: prefixes -> sb |> appendIndent prefixes |> appendString nonTerminalIndent 223 | 224 | match prefixes with 225 | | [] -> sb 226 | | [ T ] -> sb |> appendString terminalSuberror 227 | | [ NT ] -> sb |> appendString nonTerminalSuberror 228 | | T :: prefixes -> sb |> appendIndent prefixes |> appendString terminalSuberror 229 | | NT :: prefixes -> sb |> appendIndent prefixes |> appendString nonTerminalSuberror 230 | 231 | let rec f prefixes pos errors sb = 232 | match errors with 233 | | EndOfInput -> sb |> appendPrefixes prefixes |> appendString "Unexpected end of input" 234 | | Expected e -> sb |> appendPrefixes prefixes |> appendString "Expected " |> formatOne e 235 | 236 | | ExpectedSeq es -> sb |> appendPrefixes prefixes |> appendString "Expected " |> formatSeq es 237 | | Unexpected e -> sb |> appendPrefixes prefixes |> appendString "Unexpected " |> formatOne e 238 | | UnexpectedSeq es -> sb |> appendPrefixes prefixes |> appendString "Unexpected " |> formatSeq es 239 | | Nested(e, es) -> 240 | sb |> f prefixes pos e |> appendNewline |> ignore 241 | 242 | let rec g es = 243 | match es with 244 | | [] -> sb 245 | | [ { Errors = e; Position = pos } ] -> sb |> f (T :: prefixes) pos e 246 | | { Errors = e; Position = pos } :: es -> 247 | sb |> f (NT :: prefixes) pos e |> appendNewline |> ignore 248 | g es 249 | 250 | g es 251 | 252 | | Message m -> sb |> appendPrefixes prefixes |> appendString m 253 | 254 | let { Errors = errors; Position = pos } = error 255 | f [] pos errors sb 256 | 257 | let formatStringError (input: string) (error: ParseError) = 258 | let index = LineIndex.OfString input 259 | let readable = ReadableString input 260 | 261 | let formatOne (x: char) (sb: StringBuilder) = sb.Append(''').Append(x).Append(''') 262 | 263 | let formatSeq (xs: char seq) (sb: StringBuilder) = 264 | sb.Append('"') |> ignore 265 | (sb, xs) ||> Seq.fold (fun sb x -> sb.Append x) |> ignore 266 | sb.Append('"') 267 | 268 | 269 | StringBuilder() 270 | |> formatErrorsLine index readable error.Position.Index 271 | |> formatParseError formatOne formatSeq error 272 | |> _.ToString() 273 | -------------------------------------------------------------------------------- /src/XParsec/FableTypes.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec 2 | 3 | // This file contains types to allow the XParsec library to be compiled with Fable. 4 | // API compatibility is maintained with the original types but not necessarily with an efficient implementation. 5 | 6 | #if FABLE_COMPILER 7 | type ImmutableArrayBuilder<'T> = ResizeArray<'T> 8 | #else 9 | type ImmutableArrayBuilder<'T> = System.Collections.Immutable.ImmutableArray<'T>.Builder 10 | #endif 11 | 12 | #if FABLE_COMPILER 13 | namespace System 14 | 15 | open System.Collections.Generic 16 | open System.Runtime.CompilerServices 17 | 18 | type ReadOnlySpan<'T> = 19 | abstract Length: int 20 | abstract Slice: newStart: int * newLength: int -> ReadOnlySpan<'T> 21 | abstract Item: int -> 'T with get 22 | abstract IsEmpty: bool 23 | abstract ToArray: unit -> 'T array 24 | inherit IEnumerable<'T> 25 | 26 | type Span<'T> = 27 | inherit ReadOnlySpan<'T> 28 | 29 | type ArraySpan<'T>(array: 'T array, start: int, length: int) = 30 | let getSeq () = 31 | seq { 32 | for i in start .. start + length - 1 do 33 | yield array.[i] 34 | } 35 | 36 | interface Span<'T> with 37 | member _.Length = length 38 | 39 | member _.Slice(newStart, newLength) = 40 | ArraySpan(array, start + newStart, newLength) 41 | 42 | member _.Item 43 | with get (i) = array.[start + i] 44 | 45 | member _.IsEmpty = length = 0 46 | 47 | member _.ToArray() = 48 | Array.init length (fun i -> array.[start + i]) 49 | 50 | member _.GetEnumerator() : IEnumerator<'T> = getSeq().GetEnumerator() 51 | member _.GetEnumerator() : System.Collections.IEnumerator = getSeq().GetEnumerator() :> _ 52 | 53 | type ResizeArraySpan<'T>(array: ResizeArray<'T>, start: int, length: int) = 54 | let getSeq () = 55 | seq { 56 | for i in start .. start + length - 1 do 57 | yield array.[i] 58 | } 59 | 60 | interface Span<'T> with 61 | member _.Length = length 62 | 63 | member _.Slice(newStart, newLength) = 64 | ResizeArraySpan(array, start + newStart, newLength) 65 | 66 | member _.Item 67 | with get (i) = array.[start + i] 68 | 69 | member _.IsEmpty = length = 0 70 | 71 | member _.ToArray() = 72 | Array.init length (fun i -> array.[start + i]) 73 | 74 | member _.GetEnumerator() : IEnumerator<'T> = getSeq().GetEnumerator() 75 | member _.GetEnumerator() : System.Collections.IEnumerator = getSeq().GetEnumerator() :> _ 76 | 77 | type StringSpan(string: string, start: int, length: int) = 78 | let getSeq () = 79 | seq { 80 | for i in start .. start + length - 1 do 81 | yield string.[i] 82 | } 83 | 84 | interface ReadOnlySpan with 85 | member _.Length = length 86 | 87 | member _.Slice(newStart, newLength) = 88 | StringSpan(string, start + newStart, newLength) 89 | 90 | member _.Item 91 | with get (i) = string[start + i] 92 | 93 | member _.IsEmpty = length = 0 94 | 95 | member _.ToArray() = 96 | Array.init length (fun i -> string[start + i]) 97 | 98 | member _.GetEnumerator() : IEnumerator = getSeq().GetEnumerator() 99 | member _.GetEnumerator() : System.Collections.IEnumerator = getSeq().GetEnumerator() :> _ 100 | #endif 101 | 102 | #if FABLE_COMPILER 103 | namespace System.Collections.Immutable 104 | 105 | open System 106 | open System.Runtime.CompilerServices 107 | 108 | [] 109 | type ImmutableArray<'T> = 110 | internal 111 | { 112 | // Using a record as the simplest way to get structural equality 113 | Array: 'T array 114 | } 115 | 116 | member this.IsEmpty = Array.isEmpty this.Array 117 | 118 | member this.Item 119 | with get (i) = this.Array.[i] 120 | 121 | member this.Length = this.Array.Length 122 | 123 | member this.AsSpan() : ReadOnlySpan<'T> = 124 | ArraySpan<'T>(this.Array, 0, this.Length) 125 | 126 | static member Empty: ImmutableArray<'T> = { Array = Array.empty<'T> } 127 | 128 | interface System.Collections.Generic.IEnumerable<'T> with 129 | member this.GetEnumerator() : System.Collections.Generic.IEnumerator<'T> = 130 | (this.Array :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() 131 | 132 | member this.GetEnumerator() : System.Collections.IEnumerator = 133 | (this.Array :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() 134 | 135 | type ImmutableArray = 136 | static member inline CreateRange<'T>(xs: 'T seq) = { Array = Array.ofSeq<'T> xs } 137 | static member inline CreateBuilder<'T>() = ResizeArray<'T>() 138 | static member inline CreateBuilder<'T>(initialCapacity: int) = ResizeArray<'T>(initialCapacity) 139 | 140 | [] 141 | type ImmutableArrayExtensions = 142 | [] 143 | static member Contains<'T when 'T: equality>(array: ImmutableArray<'T>, x: 'T) = Array.contains x array.Array 144 | #endif 145 | 146 | #if FABLE_COMPILER 147 | namespace System 148 | 149 | open System.Runtime.CompilerServices 150 | open System.Collections.Immutable 151 | 152 | [] 153 | type MemoryExtensions = 154 | static member SequenceEqual<'T when 'T: equality>(x: ReadOnlySpan<'T>, y: ReadOnlySpan<'T>) = 155 | if x.Length <> y.Length then 156 | false 157 | else 158 | let rec f i = 159 | if i = x.Length then true 160 | elif x[i] = y[i] then f (i + 1) 161 | else false 162 | 163 | f 0 164 | 165 | static member SequenceEqual<'T when 'T: equality>(x: ReadOnlySpan<'T>, y: Span<'T>) = 166 | if x.Length <> y.Length then 167 | false 168 | else 169 | let rec f i = 170 | if i = x.Length then true 171 | elif x[i] = y[i] then f (i + 1) 172 | else false 173 | 174 | f 0 175 | 176 | static member SequenceEqual<'T when 'T: equality>(x: Span<'T>, y: ReadOnlySpan<'T>) = 177 | if x.Length <> y.Length then 178 | false 179 | else 180 | let rec f i = 181 | if i = x.Length then true 182 | elif x[i] = y[i] then f (i + 1) 183 | else false 184 | 185 | f 0 186 | 187 | static member SequenceEqual<'T when 'T: equality>(x: Span<'T>, y: Span<'T>) = 188 | if x.Length <> y.Length then 189 | false 190 | else 191 | let rec f i = 192 | if i = x.Length then true 193 | elif x[i] = y[i] then f (i + 1) 194 | else false 195 | 196 | f 0 197 | 198 | static member Equals(x: ReadOnlySpan, y: ReadOnlySpan, comparison: System.StringComparison) = 199 | let sx = System.String(x.ToArray()) 200 | let sy = System.String(y.ToArray()) 201 | sx.Equals(sy, comparison) 202 | 203 | [] 204 | static member inline AsSpan<'T>(array: 'T array) : Span<'T> = ArraySpan(array, 0, array.Length) 205 | 206 | [] 207 | static member inline AsSpan<'T>(array: 'T array, start: int, length: int) : Span<'T> = 208 | ArraySpan(array, start, length) 209 | 210 | [] 211 | static member inline AsSpan(string: string) : ReadOnlySpan = StringSpan(string, 0, string.Length) 212 | 213 | [] 214 | static member inline AsSpan(string: string, start: int, length: int) : ReadOnlySpan = 215 | StringSpan(string, start, length) 216 | 217 | [] 218 | static member inline AsSpan<'T>(array: ImmutableArray<'T>) : ReadOnlySpan<'T> = 219 | ArraySpan(array.Array, 0, array.Length) 220 | 221 | [] 222 | static member inline AsSpan<'T>(array: ImmutableArray<'T>, start: int, length: int) : ReadOnlySpan<'T> = 223 | ArraySpan(array.Array, start, length) 224 | 225 | [] 226 | module Extensions = 227 | type ReadOnlySpan<'T> with 228 | static member Empty: ReadOnlySpan<'T> = ArraySpan<'T>([||], 0, 0) :> ReadOnlySpan<'T> 229 | 230 | type System.Collections.Generic.List<'T> with 231 | member inline this.ToImmutable() = { Array = this.ToArray() } 232 | member inline this.ToImmutableArray() = { Array = this.ToArray() } 233 | 234 | member inline this.MoveToImmutable() = 235 | let xs = this.ToArray() 236 | this.Clear() 237 | { Array = xs } 238 | 239 | type ``[]``<'T> with 240 | member inline this.ToImmutableArray() = { Array = Array.copy this } 241 | #endif 242 | -------------------------------------------------------------------------------- /src/XParsec/ImmutableArray.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec 2 | 3 | open System 4 | open System.Collections.Immutable 5 | #if !FABLE_COMPILER 6 | open System.Runtime.InteropServices 7 | #endif 8 | 9 | type ImmutableArrayCE() = 10 | member inline _.Yield(x) = 11 | fun (b: ImmutableArrayBuilder<'T>) -> b.Add(x) 12 | 13 | member inline _.YieldFrom(xs: 'T seq) = 14 | fun (b: ImmutableArrayBuilder<'T>) -> b.AddRange(xs) 15 | 16 | member inline _.Combine([] f, [] g) = 17 | fun (b: ImmutableArrayBuilder<'T>) -> 18 | f b 19 | g b 20 | 21 | member inline _.Delay([] f) = 22 | fun (builder: ImmutableArrayBuilder<'T>) -> (f ()) builder 23 | 24 | member inline _.Zero() = 25 | fun (b: ImmutableArrayBuilder<'T>) -> () 26 | 27 | member inline __.For(xs: seq<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 28 | fun (b: ImmutableArrayBuilder<'T>) -> 29 | for x in xs do 30 | (f x) b 31 | 32 | member inline __.While 33 | ([] p: unit -> bool, [] f: ImmutableArrayBuilder<'T> -> unit) 34 | = 35 | fun (b: ImmutableArrayBuilder<'T>) -> 36 | while p () do 37 | f b 38 | 39 | member inline __.Run([] f) = 40 | let builder = ImmutableArray.CreateBuilder<'T>() 41 | f builder 42 | builder.ToImmutable() 43 | 44 | type ImmutableArrayCE with 45 | 46 | member inline _.YieldFrom(xs: 'T array) = 47 | fun (b: ImmutableArrayBuilder<'T>) -> b.AddRange(xs) 48 | 49 | member inline _.YieldFrom(xs: ResizeArray<'T>) = 50 | fun (b: ImmutableArrayBuilder<'T>) -> 51 | #if NET5_0_OR_GREATER && !FABLE_COMPILER 52 | b.AddRange(CollectionsMarshal.AsSpan xs) 53 | #else 54 | b.AddRange(xs) 55 | #endif 56 | member inline _.YieldFrom(xs: ImmutableArray<'T>) = 57 | fun (b: ImmutableArrayBuilder<'T>) -> b.AddRange(xs) 58 | 59 | #if !FABLE_COMPILER 60 | member inline _.YieldFrom(xs: Memory<'T>) = 61 | fun (b: ImmutableArrayBuilder<'T>) -> b.AddRange(xs.Span) 62 | 63 | member inline _.YieldFrom(xs: ReadOnlyMemory<'T>) = 64 | fun (b: ImmutableArrayBuilder<'T>) -> b.AddRange(xs.Span) 65 | #endif 66 | 67 | member inline __.For(xs: array<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 68 | fun (b: ImmutableArrayBuilder<'T>) -> 69 | for x in xs do 70 | (f x) b 71 | 72 | #if !FABLE_COMPILER 73 | member inline __.For(xs: ImmutableArray<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 74 | fun (b: ImmutableArrayBuilder<'T>) -> 75 | for x in xs.AsSpan() do 76 | (f x) b 77 | #endif 78 | 79 | member inline __.For(xs: ResizeArray<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 80 | fun (b: ImmutableArrayBuilder<'T>) -> 81 | #if NET5_0_OR_GREATER && !FABLE_COMPILER 82 | for x in CollectionsMarshal.AsSpan xs do 83 | (f x) b 84 | #else 85 | for x in xs do 86 | (f x) b 87 | #endif 88 | 89 | #if !FABLE_COMPILER 90 | member inline __.For(xs: Memory<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 91 | fun (b: ImmutableArrayBuilder<'T>) -> 92 | for x in xs.Span do 93 | (f x) b 94 | 95 | member inline __.For(xs: ReadOnlyMemory<'T>, [] f: 'T -> ImmutableArrayBuilder<'T> -> unit) = 96 | fun (b: ImmutableArrayBuilder<'T>) -> 97 | for x in xs.Span do 98 | (f x) b 99 | #endif 100 | [] 101 | module Builders = 102 | let imm = ImmutableArrayCE() 103 | -------------------------------------------------------------------------------- /src/XParsec/Types.fs: -------------------------------------------------------------------------------- 1 | namespace XParsec 2 | 3 | open System 4 | 5 | /// 6 | /// A type that can be read from, representing the input to a parser. 7 | /// Implement this interface to create your own input types. 8 | /// Use the Reader module for common input types like string, array, etc. 9 | /// 10 | type IReadable<'T, 'U when 'U :> IReadable<'T, 'U>> = 11 | abstract Item: int64 -> 'T with get 12 | abstract TryItem: index: int64 -> 'T voption 13 | abstract SpanSlice: start: int64 * length: int -> ReadOnlySpan<'T> 14 | abstract Length: int64 15 | abstract Slice: newStart: int64 * newLength: int64 -> 'U 16 | 17 | 18 | [] 19 | type ReaderId = internal | ReaderId of int64 20 | 21 | module internal ReaderUtils = 22 | open System.Threading 23 | 24 | let nextId = 25 | let mutable x = 0L 26 | #if FABLE_COMPILER 27 | fun () -> 28 | x <- x + 1L 29 | ReaderId x 30 | #else 31 | fun () -> Interlocked.Increment &x |> ReaderId 32 | #endif 33 | 34 | open ReaderUtils 35 | 36 | [] 37 | type Position<'State> = 38 | { 39 | Id: ReaderId 40 | Index: int64 41 | State: 'State 42 | } 43 | 44 | /// 45 | /// A cursor that tracks the current position in the input and the user state. 46 | /// It is used by the parser to read from the input and manage state. 47 | /// 48 | [] 49 | type Reader<'T, 'State, 'Input, 'InputSlice 50 | when 'Input :> IReadable<'T, 'InputSlice> and 'InputSlice :> IReadable<'T, 'InputSlice>> 51 | (input: 'Input, state: 'State, index: int64) = 52 | 53 | let mutable index = index 54 | let mutable state = state 55 | let id = nextId () 56 | 57 | member _.Id = id 58 | member _.Input = input 59 | 60 | member _.State 61 | with get () = state 62 | and set v = state <- v 63 | 64 | member _.Index 65 | with get () = index 66 | and set v = index <- v 67 | 68 | member _.Position 69 | with get () = 70 | { 71 | Id = id 72 | Index = index 73 | State = state 74 | } 75 | and set (p: Position<'State>) = 76 | if p.Id <> id then 77 | invalidOp "Position id does not match Cursor id" 78 | 79 | index <- p.Index 80 | state <- p.State 81 | 82 | member _.Peek() = input.TryItem(index) 83 | member _.PeekN(count) = input.SpanSlice(index, count) 84 | member _.Length = input.Length 85 | 86 | member _.Skip() = 87 | if index < input.Length then 88 | index <- index + 1L 89 | else 90 | invalidOp "Attempted to skip past end of input" 91 | 92 | member _.SkipN(count) = 93 | if index + count > input.Length then 94 | invalidOp "Attempted to skip past end of input" 95 | else 96 | index <- index + count 97 | 98 | member _.TryRead() = 99 | let x = input.TryItem(index) 100 | 101 | match x with 102 | | ValueSome _ -> index <- index + 1L 103 | | ValueNone -> () 104 | 105 | x 106 | 107 | member _.Current = input.TryItem(index) 108 | member _.AtEnd = index >= input.Length 109 | 110 | member _.Slice(newStart, newLength) = 111 | Reader(input.Slice(index - newStart, newLength), (), 0L) 112 | 113 | member _.Slice(newStart, newLength, newState) = 114 | Reader(input.Slice(index - newStart, newLength), newState, 0L) 115 | 116 | type ErrorType<'T, 'State> = 117 | | Expected of 'T 118 | | ExpectedSeq of 'T seq 119 | | Unexpected of 'T 120 | | UnexpectedSeq of 'T seq 121 | | Message of string 122 | | EndOfInput 123 | | Nested of parent: ErrorType<'T, 'State> * children: ParseError<'T, 'State> list 124 | 125 | and [] ParseError<'T, 'State> = 126 | { 127 | Position: Position<'State> 128 | Errors: ErrorType<'T, 'State> 129 | } 130 | 131 | 132 | [] 133 | type ParseSuccess<'Parsed> = { Parsed: 'Parsed } 134 | 135 | type ParseResult<'Parsed, 'T, 'State> = Result, ParseError<'T, 'State>> 136 | 137 | module ParseSuccess = 138 | let inline create tokens = Ok { Parsed = tokens } 139 | 140 | let inline map f x = { Parsed = f x.Parsed } 141 | 142 | module ParseError = 143 | let inline create error position : ParseResult<'Parsed, 'T, 'State> = 144 | Error { Position = position; Errors = error } 145 | 146 | let inline createNested error children position : ParseResult<'Parsed, 'T, 'State> = 147 | Error 148 | { 149 | Position = position 150 | Errors = Nested(error, children) 151 | } 152 | 153 | let wrongUserState = Message "Unexpected user state." 154 | let shouldConsume = Message "The parser did not consume any input." 155 | let shouldNotConsume = Message "The parser consumed input." 156 | let shouldNotSucceed = Message "The parser succeeded unexpectedly." 157 | let shouldFailInPlace = Message "The parser failed but consumed input." 158 | let unexpectedEnd = Message "Unexpected end of input" 159 | let expectedEnd = Message "Expected end of input" 160 | let refParserInit = Message "RefParser was not initialized." 161 | let expectedAtLeastOne = Message "Expected at least one item." 162 | let zero = Message "" 163 | let allChoicesFailed = Message "All choices failed." 164 | let bothFailed = Message "Both parsers failed." 165 | 166 | type Parser<'Parsed, 'T, 'State, 'Input, 'InputSlice 167 | when 'Input :> IReadable<'T, 'InputSlice> and 'InputSlice :> IReadable<'T, 'InputSlice>> = 168 | Reader<'T, 'State, 'Input, 'InputSlice> -> ParseResult<'Parsed, 'T, 'State> 169 | 170 | type InfiniteLoopException<'State>(pos: Position<'State>, innerException) = 171 | inherit Exception("Infinite loop detected in parser.", innerException) 172 | new(pos: Position<'State>) = InfiniteLoopException(pos, null) 173 | member _.Position = pos 174 | -------------------------------------------------------------------------------- /src/XParsec/XParsec.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;netstandard2.0 4 | true 5 | 6 | 7 | true 8 | true 9 | false 10 | 11 | https://github.com/roboz0r/XParsec 12 | true 13 | 14 | 15 | 16 | $(PackageTags);f#;fable;fable-library;fable-javascript;fable-dotnet 17 | library 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/XParsec/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | ".NETStandard,Version=v2.0": { 5 | "FSharp.Core": { 6 | "type": "Direct", 7 | "requested": "[9.0.201, )", 8 | "resolved": "9.0.201", 9 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 10 | }, 11 | "NETStandard.Library": { 12 | "type": "Direct", 13 | "requested": "[2.0.3, )", 14 | "resolved": "2.0.3", 15 | "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", 16 | "dependencies": { 17 | "Microsoft.NETCore.Platforms": "1.1.0" 18 | } 19 | }, 20 | "System.Collections.Immutable": { 21 | "type": "Direct", 22 | "requested": "[9.0.3, )", 23 | "resolved": "9.0.3", 24 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==", 25 | "dependencies": { 26 | "System.Memory": "4.5.5", 27 | "System.Runtime.CompilerServices.Unsafe": "6.0.0" 28 | } 29 | }, 30 | "Microsoft.NETCore.Platforms": { 31 | "type": "Transitive", 32 | "resolved": "1.1.0", 33 | "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" 34 | }, 35 | "System.Buffers": { 36 | "type": "Transitive", 37 | "resolved": "4.5.1", 38 | "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" 39 | }, 40 | "System.Memory": { 41 | "type": "Transitive", 42 | "resolved": "4.5.5", 43 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", 44 | "dependencies": { 45 | "System.Buffers": "4.5.1", 46 | "System.Numerics.Vectors": "4.4.0", 47 | "System.Runtime.CompilerServices.Unsafe": "4.5.3" 48 | } 49 | }, 50 | "System.Numerics.Vectors": { 51 | "type": "Transitive", 52 | "resolved": "4.4.0", 53 | "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" 54 | }, 55 | "System.Runtime.CompilerServices.Unsafe": { 56 | "type": "Transitive", 57 | "resolved": "6.0.0", 58 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" 59 | } 60 | }, 61 | "net8.0": { 62 | "FSharp.Core": { 63 | "type": "Direct", 64 | "requested": "[9.0.201, )", 65 | "resolved": "9.0.201", 66 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 67 | }, 68 | "Microsoft.NET.ILLink.Tasks": { 69 | "type": "Direct", 70 | "requested": "[8.0.15, )", 71 | "resolved": "8.0.15", 72 | "contentHash": "s4eXlcRGyHeCgFUGQnhq0e/SCHBPp0jOHgMqZg3fQ2OCHJSm1aOUhI6RFWuVIcEb9ig2WgI2kWukk8wu72EbUQ==" 73 | }, 74 | "System.Collections.Immutable": { 75 | "type": "Direct", 76 | "requested": "[9.0.3, )", 77 | "resolved": "9.0.3", 78 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Interactive/Program.fs: -------------------------------------------------------------------------------- 1 | module XParsec.CLArgs.Interactive 2 | 3 | open System 4 | 5 | open XParsec 6 | open XParsec.Parsers 7 | open XParsec.CLArgs 8 | 9 | type Args = 10 | | DumpFile of string 11 | | AllText of bool 12 | 13 | let options = 14 | [ 15 | CLSetting("dump-file", DumpFile, None, true) 16 | CLBoolSetting("all-text", AllText, None, true) 17 | ] 18 | 19 | [] 20 | let main argv = 21 | // printfn "Interactive test for XParsec.CLArgs.\n %s" (String.Join("; ", argv)) 22 | Console.WriteLine("Interactive test for XParsec.CLArgs.\n {0}", (String.Join("; ", argv))) 23 | 24 | let parser = options |> CLParser.ofOptions 25 | 26 | let reader = Reader.ofArray argv () 27 | 28 | match parser reader with 29 | | Ok result -> 30 | let args = result.Parsed 31 | 32 | if args.Length = 0 then 33 | // printfn "No arguments." 34 | Console.WriteLine("No arguments.") 35 | else 36 | for arg in args do 37 | match arg with 38 | | DumpFile file -> 39 | // printfn "Dump file '%s'..." file 40 | Console.WriteLine("Dump file '{0}'...", file) 41 | 42 | if System.IO.File.Exists file then 43 | // printfn "File exists." 44 | Console.WriteLine("File exists.") 45 | 46 | if args.Contains(AllText true) then 47 | // printfn "%s" (System.IO.File.ReadAllText file) 48 | Console.WriteLine("{0}", (System.IO.File.ReadAllText file)) 49 | else 50 | // printfn "File does not exist." 51 | Console.WriteLine("File does not exist.") 52 | | AllText allText -> () 53 | | Error e -> failwithf "%A" e 54 | 55 | 0 56 | -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Interactive/XParsec.CLArgs.Interactive.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | true 6 | true 7 | win-x64 8 | 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Interactive/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Microsoft.DotNet.ILCompiler": { 6 | "type": "Direct", 7 | "requested": "[9.0.4, )", 8 | "resolved": "9.0.4", 9 | "contentHash": "G85txKEuQ8s64BG9Pk3TbmE+cDgKReepnIPNfC1lNks4u2v5SkQhjCuuSAep+H2xtgFpYU7w9LFOF+vVtJZguA==" 10 | }, 11 | "Microsoft.NET.ILLink.Tasks": { 12 | "type": "Direct", 13 | "requested": "[9.0.4, )", 14 | "resolved": "9.0.4", 15 | "contentHash": "xUdlUxiFwXhTYhB4VxKg/IA0+jlZXJPo70LYuMryWbJHdonIpZjw+7DO2B0pWwpXIOs6MlH5WVXPEtfrGEcVZA==" 16 | }, 17 | "xparsec": { 18 | "type": "Project", 19 | "dependencies": { 20 | "FSharp.Core": "[9.0.201, )", 21 | "System.Collections.Immutable": "[9.0.3, )" 22 | } 23 | }, 24 | "xparsec.clargs": { 25 | "type": "Project", 26 | "dependencies": { 27 | "System.Collections.Immutable": "[9.0.3, )", 28 | "XParsec": "[0.1.0, )" 29 | } 30 | }, 31 | "FSharp.Core": { 32 | "type": "CentralTransitive", 33 | "requested": "[9.0.201, )", 34 | "resolved": "9.0.201", 35 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 36 | }, 37 | "System.Collections.Immutable": { 38 | "type": "CentralTransitive", 39 | "requested": "[9.0.3, )", 40 | "resolved": "9.0.3", 41 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 42 | } 43 | }, 44 | "net9.0/win-x64": { 45 | "Microsoft.DotNet.ILCompiler": { 46 | "type": "Direct", 47 | "requested": "[9.0.4, )", 48 | "resolved": "9.0.4", 49 | "contentHash": "G85txKEuQ8s64BG9Pk3TbmE+cDgKReepnIPNfC1lNks4u2v5SkQhjCuuSAep+H2xtgFpYU7w9LFOF+vVtJZguA==", 50 | "dependencies": { 51 | "runtime.win-x64.Microsoft.DotNet.ILCompiler": "9.0.4" 52 | } 53 | }, 54 | "runtime.win-x64.Microsoft.DotNet.ILCompiler": { 55 | "type": "Transitive", 56 | "resolved": "9.0.4", 57 | "contentHash": "eQR6TiScRMept4/YImwHmHiA0O10Fit6Tqm0n9LQFGVoCqoJn2uhhPEXTVV3W3kN/1+vtq6kc+pICxtH60gjaw==" 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Tests/CLArgsParsersTests.fs: -------------------------------------------------------------------------------- 1 | module CLArgsTests 2 | 3 | open System 4 | 5 | open Expecto 6 | 7 | open XParsec 8 | open XParsec.CLArgs 9 | 10 | type TestOptions = 11 | | Flag1 12 | | Flag2 13 | | Setting of string 14 | | BoolSetting of bool 15 | 16 | let testParser options (input, expected) = 17 | let parser = options |> CLParser.ofOptions 18 | 19 | let reader = Reader.ofArray input () 20 | 21 | match parser reader with 22 | | Ok result -> "" |> Expect.equal result.Parsed expected 23 | | Error e -> failwithf "%A" e 24 | 25 | let testMany options inputCases = 26 | let test = testParser options 27 | inputCases |> List.iter test 28 | 29 | 30 | [] 31 | let tests = 32 | testList 33 | "CLArgsParsersTests" 34 | [ 35 | test "Parse Flags" { 36 | let cases = 37 | [ 38 | [||], imm { } 39 | [| "--flag1" |], imm { Flag1 } 40 | [| "--flag2" |], imm { Flag2 } 41 | 42 | [| "--flag1"; "--flag2" |], 43 | imm { 44 | Flag1 45 | Flag2 46 | } 47 | ] 48 | 49 | testMany [ CLFlag("flag1", Flag1, None); CLFlag("flag2", Flag2, None) ] cases 50 | } 51 | 52 | test "Parse Settings" { 53 | let cases = 54 | [ 55 | [||], imm { } 56 | [| "--setting"; "value" |], imm { Setting "value" } 57 | 58 | [| "--setting"; "value"; "--setting"; "value" |], 59 | imm { 60 | Setting "value" 61 | Setting "value" 62 | } 63 | ] 64 | 65 | testMany [ CLSetting("setting", Setting, None, false) ] cases 66 | } 67 | 68 | test "Parse Settings Equals Assignment" { 69 | let cases = 70 | [ 71 | [||], imm { } 72 | [| "--setting=value" |], imm { Setting "value" } 73 | 74 | [| "--setting=value"; "--setting=value" |], 75 | imm { 76 | Setting "value" 77 | Setting "value" 78 | } 79 | ] 80 | 81 | testMany [ CLSetting("setting", Setting, None, true) ] cases 82 | } 83 | 84 | test "Parse Bool Settings" { 85 | let cases = 86 | [ 87 | [||], imm { } 88 | [| "--setting"; "true" |], imm { BoolSetting true } 89 | 90 | [| "--setting"; "true"; "--setting"; "false" |], 91 | imm { 92 | BoolSetting true 93 | BoolSetting false 94 | } 95 | [| "--setting=true"; "--setting=false" |], 96 | imm { 97 | BoolSetting true 98 | BoolSetting false 99 | } 100 | 101 | [| "--setting"; "true"; "--setting"; "false"; "-b"; "true"; "-b=false" |], 102 | imm { 103 | BoolSetting true 104 | BoolSetting false 105 | BoolSetting true 106 | BoolSetting false 107 | } 108 | ] 109 | 110 | testMany [ CLBoolSetting("setting", BoolSetting, Some 'b', true) ] cases 111 | } 112 | ] 113 | -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open Fable.Pyxpecto 4 | #if FABLE_COMPILER 5 | #else 6 | open Expecto 7 | #endif 8 | 9 | // This is possibly the most magic used to make this work. 10 | // Js and ts cannot use `Async.RunSynchronously`, instead they use `Async.StartAsPromise`. 11 | // Here we need the transpiler not to worry about the output type. 12 | #if FABLE_COMPILER && !FABLE_COMPILER_JAVASCRIPT && !FABLE_COMPILER_TYPESCRIPT 13 | let (!!) (any: 'a) = any 14 | #endif 15 | #if FABLE_COMPILER && FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT 16 | open Fable.Core.JsInterop 17 | #endif 18 | 19 | [] 20 | let main argv = 21 | #if FABLE_COMPILER 22 | let all = 23 | testList 24 | "XParsec.Tests" 25 | [ 26 | ParserTests.tests 27 | CombinatorTests.tests 28 | OperatorParsingTests.tests 29 | OperatorParsingTests.tests2 30 | OperatorParsingTests.tests3 31 | ] 32 | 33 | 34 | !! Pyxpecto.runTests [||] all 35 | #else 36 | Tests.runTestsInAssemblyWithCLIArgs [] argv 37 | #endif 38 | -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Tests/XParsec.CLArgs.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/XParsec.CLArgs.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Expecto": { 6 | "type": "Direct", 7 | "requested": "[10.2.2, )", 8 | "resolved": "10.2.2", 9 | "contentHash": "Ul1wNnvM4sugoP5toPmYNwOJgj7fb0MhbEtTXOz/Z68oiUeLWRHdt1+KGQaseENbt8fnJfiK3zfef+/2dTL8Jg==", 10 | "dependencies": { 11 | "FSharp.Core": "7.0.200", 12 | "Mono.Cecil": "[0.11.4, 1.0.0)" 13 | } 14 | }, 15 | "Expecto.FsCheck": { 16 | "type": "Direct", 17 | "requested": "[10.2.2, )", 18 | "resolved": "10.2.2", 19 | "contentHash": "ef5jUN+wXrW1c4YnPtaM16CMnlNoSs04quT4lQ7L9MdD1tPjK25DtJaFC7KUZIEf/n5DrXw+K0CZ9VLp2E/QRw==", 20 | "dependencies": { 21 | "Expecto": "10.2.2", 22 | "FsCheck": "[2.16.5, 3.0.0)" 23 | } 24 | }, 25 | "Fable.Pyxpecto": { 26 | "type": "Direct", 27 | "requested": "[1.2.0, )", 28 | "resolved": "1.2.0", 29 | "contentHash": "I6jt1fCXXOipJ9tocdBstBK/LFOXa74e6v1GzeiqyWS+v0Lk4xH4NuD2f9g1todETt7A20ib7hvQigMNKqN0Aw==", 30 | "dependencies": { 31 | "FSharp.Core": "5.0.0", 32 | "Fable.Core": "4.1.0", 33 | "Fable.Python": "4.3.0" 34 | } 35 | }, 36 | "FSharp.Core": { 37 | "type": "Direct", 38 | "requested": "[9.0.201, )", 39 | "resolved": "9.0.201", 40 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 41 | }, 42 | "Microsoft.NET.Test.Sdk": { 43 | "type": "Direct", 44 | "requested": "[17.13.0, )", 45 | "resolved": "17.13.0", 46 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 47 | "dependencies": { 48 | "Microsoft.CodeCoverage": "17.13.0", 49 | "Microsoft.TestPlatform.TestHost": "17.13.0" 50 | } 51 | }, 52 | "YoloDev.Expecto.TestSdk": { 53 | "type": "Direct", 54 | "requested": "[0.15.3, )", 55 | "resolved": "0.15.3", 56 | "contentHash": "fpHXx4+RD8HrLsxg/NhP+uvqIPzFfoLNoPZ3F8Yi+Nv8DPTHqwdnDyPW9MD+036icExUYiv/qc/rOuSfE0TIOA==", 57 | "dependencies": { 58 | "Expecto": "[10.2.2, 11.0.0)", 59 | "FSharp.Core": "7.0.200", 60 | "Microsoft.Testing.Extensions.VSTestBridge": "1.6.2", 61 | "Microsoft.Testing.Platform.MSBuild": "1.6.2" 62 | } 63 | }, 64 | "Fable.Python": { 65 | "type": "Transitive", 66 | "resolved": "4.3.0", 67 | "contentHash": "KT5PI4NyMtVLDcmDkf5SeqwtFjVO17u27xr45qXfYWG12UePHxGjQJoI16OafIzlEQ6cHfAuRljhZGKIlvOJNQ==", 68 | "dependencies": { 69 | "FSharp.Core": "4.7.2", 70 | "Fable.Core": "[4.1.0, 5.0.0)" 71 | } 72 | }, 73 | "FsCheck": { 74 | "type": "Transitive", 75 | "resolved": "2.16.5", 76 | "contentHash": "Tw6Lb/UBqtmUj5porM1rRcAU5xT9NttSlFTaIQ8gn4Fsw2LiZavN/eMAZqSA63laV51t4HRJJAAGeYSWFFxRfA==", 77 | "dependencies": { 78 | "FSharp.Core": "4.2.3" 79 | } 80 | }, 81 | "Microsoft.ApplicationInsights": { 82 | "type": "Transitive", 83 | "resolved": "2.22.0", 84 | "contentHash": "3AOM9bZtku7RQwHyMEY3tQMrHIgjcfRDa6YQpd/QG2LDGvMydSlL9Di+8LLMt7J2RDdfJ7/2jdYv6yHcMJAnNw==", 85 | "dependencies": { 86 | "System.Diagnostics.DiagnosticSource": "5.0.0" 87 | } 88 | }, 89 | "Microsoft.CodeCoverage": { 90 | "type": "Transitive", 91 | "resolved": "17.13.0", 92 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 93 | }, 94 | "Microsoft.Testing.Extensions.Telemetry": { 95 | "type": "Transitive", 96 | "resolved": "1.6.2", 97 | "contentHash": "40oMlQzyey4jOihY0IpUufSoMYeijYgvrtIxuYmuVx1k5xl271XlP0gwD2DwAKnvmmP0cocou531d6/CB3cCIA==", 98 | "dependencies": { 99 | "Microsoft.ApplicationInsights": "2.22.0", 100 | "Microsoft.Testing.Platform": "1.6.2" 101 | } 102 | }, 103 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": { 104 | "type": "Transitive", 105 | "resolved": "1.6.2", 106 | "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", 107 | "dependencies": { 108 | "Microsoft.Testing.Platform": "1.6.2" 109 | } 110 | }, 111 | "Microsoft.Testing.Extensions.VSTestBridge": { 112 | "type": "Transitive", 113 | "resolved": "1.6.2", 114 | "contentHash": "ZvYa+VDuk39EIqyOZ/IMFSRd/N54zFBnDFmDagFBJt21vZZnSG6l/3CkJX3DvmYmuf5Byj9w7Xf46mkWuur4LQ==", 115 | "dependencies": { 116 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 117 | "Microsoft.Testing.Extensions.Telemetry": "1.6.2", 118 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", 119 | "Microsoft.Testing.Platform": "1.6.2" 120 | } 121 | }, 122 | "Microsoft.Testing.Platform": { 123 | "type": "Transitive", 124 | "resolved": "1.6.2", 125 | "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" 126 | }, 127 | "Microsoft.Testing.Platform.MSBuild": { 128 | "type": "Transitive", 129 | "resolved": "1.6.2", 130 | "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", 131 | "dependencies": { 132 | "Microsoft.Testing.Platform": "1.6.2" 133 | } 134 | }, 135 | "Microsoft.TestPlatform.ObjectModel": { 136 | "type": "Transitive", 137 | "resolved": "17.13.0", 138 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 139 | "dependencies": { 140 | "System.Reflection.Metadata": "1.6.0" 141 | } 142 | }, 143 | "Microsoft.TestPlatform.TestHost": { 144 | "type": "Transitive", 145 | "resolved": "17.13.0", 146 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 147 | "dependencies": { 148 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 149 | "Newtonsoft.Json": "13.0.1" 150 | } 151 | }, 152 | "Mono.Cecil": { 153 | "type": "Transitive", 154 | "resolved": "0.11.4", 155 | "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==" 156 | }, 157 | "Newtonsoft.Json": { 158 | "type": "Transitive", 159 | "resolved": "13.0.1", 160 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 161 | }, 162 | "System.Diagnostics.DiagnosticSource": { 163 | "type": "Transitive", 164 | "resolved": "5.0.0", 165 | "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" 166 | }, 167 | "System.Reflection.Metadata": { 168 | "type": "Transitive", 169 | "resolved": "1.6.0", 170 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 171 | }, 172 | "xparsec": { 173 | "type": "Project", 174 | "dependencies": { 175 | "FSharp.Core": "[9.0.201, )", 176 | "System.Collections.Immutable": "[9.0.3, )" 177 | } 178 | }, 179 | "xparsec.clargs": { 180 | "type": "Project", 181 | "dependencies": { 182 | "System.Collections.Immutable": "[9.0.3, )", 183 | "XParsec": "[0.1.0, )" 184 | } 185 | }, 186 | "Fable.Core": { 187 | "type": "CentralTransitive", 188 | "requested": "[4.5.0, )", 189 | "resolved": "4.5.0", 190 | "contentHash": "ZcX8XN5sQZGCbrS8VYnsa/ynhrCBfpDyqDkTl33GTXOpgfKibxoq0+W0hCSbRzuukVNoLtqGL/B6+8yTNDXbNA==" 191 | }, 192 | "System.Collections.Immutable": { 193 | "type": "CentralTransitive", 194 | "requested": "[9.0.3, )", 195 | "resolved": "9.0.3", 196 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 197 | } 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /test/XParsec.Json.Tests/JsonParserArrayTests.fs: -------------------------------------------------------------------------------- 1 | module JsonParserArrayTests 2 | 3 | open System.Collections.Immutable 4 | 5 | open Expecto 6 | 7 | open XParsec 8 | open XParsec.Json 9 | 10 | [] 11 | let tests = 12 | testList 13 | "JsonParserArrayTests" 14 | [ 15 | test "Parser fragments" { 16 | let input, expected, p = 17 | "\"string\"", JsonValue.String "string", JsonParsers.PString |>> JsonValue.String 18 | 19 | let reader = Reader.ofArray (input.ToCharArray()) () 20 | let result = p reader 21 | 22 | match result with 23 | | Ok result -> "" |> Expect.equal expected result.Parsed 24 | | Error e -> failwithf "%A" e 25 | } 26 | 27 | 28 | test "single values" { 29 | [ "true", JsonValue.True; "false", JsonValue.False; "null", JsonValue.Null ] 30 | |> List.iter (fun (input, expected) -> 31 | let result = JsonParsers.Parser(Reader.ofArray (input.ToCharArray()) ()) 32 | 33 | match result with 34 | | Ok result -> "" |> Expect.equal expected result.Parsed 35 | | Error e -> failwithf "%A" e 36 | ) 37 | } 38 | 39 | 40 | test "numbers" { 41 | [ 42 | "123", JsonValue.Number 123.0 43 | "123.456", JsonValue.Number 123.456 44 | "-123.456", JsonValue.Number -123.456 45 | "123e4", JsonValue.Number 123e4 46 | "123e-4", JsonValue.Number 123e-4 47 | "123e+4", JsonValue.Number 123e4 48 | "123.456e4", JsonValue.Number 123.456e4 49 | "123.456e-4", JsonValue.Number 123.456e-4 50 | "123.456e+4", JsonValue.Number 123.456e4 51 | "-123e4", JsonValue.Number -123e4 52 | "-123e-4", JsonValue.Number -123e-4 53 | "-123e+4", JsonValue.Number -123e4 54 | "-123.456e4", JsonValue.Number -123.456e4 55 | "-123.456e-4", JsonValue.Number -123.456e-4 56 | "-123.456e+4", JsonValue.Number -123.456e4 57 | ] 58 | |> List.iter (fun (input, expected) -> 59 | let result = JsonParsers.Parser(Reader.ofArray (input.ToCharArray()) ()) 60 | 61 | match result with 62 | | Ok result -> "" |> Expect.equal expected result.Parsed 63 | | Error e -> failwithf "%A" e 64 | ) 65 | } 66 | 67 | test "Strings" { 68 | [ 69 | "\"string\"", JsonValue.String "string" 70 | "\"\"", JsonValue.String "" 71 | "\" \"", JsonValue.String " " 72 | "\"\\\"\"", JsonValue.String "\"" 73 | "\"\\\\\"", JsonValue.String "\\" 74 | "\"\\/\"", JsonValue.String "/" 75 | "\"\\b\"", JsonValue.String "\b" 76 | "\"\\f\"", JsonValue.String "\f" 77 | "\"\\n\"", JsonValue.String "\n" 78 | "\"\\r\"", JsonValue.String "\r" 79 | "\"\\t\"", JsonValue.String "\t" 80 | "\"\\u0000\"", JsonValue.String "\u0000" 81 | "\"\\u0001\"", JsonValue.String "\u0001" 82 | "\"\\u0002\"", JsonValue.String "\u0002" 83 | "\"\\uffff\"", JsonValue.String "\uffff" 84 | ] 85 | |> List.iter (fun (input, expected) -> 86 | let result = JsonParsers.Parser(Reader.ofArray (input.ToCharArray()) ()) 87 | 88 | match result with 89 | | Ok result -> "" |> Expect.equal expected result.Parsed 90 | | Error e -> failwithf "%A" e 91 | ) 92 | } 93 | 94 | test "Arrays" { 95 | [ 96 | "[]", JsonValue.Array(ImmutableArray.Empty) 97 | "[ ]", JsonValue.Array(ImmutableArray.Empty) 98 | "[ \t\r\n ]", JsonValue.Array(ImmutableArray.Empty) 99 | "[1, 2, 3]", 100 | JsonValue.Array( 101 | ImmutableArray.CreateRange [ JsonValue.Number 1.0; JsonValue.Number 2.0; JsonValue.Number 3.0 ] 102 | ) 103 | "[true, false, null]", 104 | JsonValue.Array(ImmutableArray.CreateRange [ JsonValue.True; JsonValue.False; JsonValue.Null ]) 105 | "[\"string\", 123, true]", 106 | JsonValue.Array( 107 | ImmutableArray.CreateRange [ JsonValue.String "string"; JsonValue.Number 123.0; JsonValue.True ] 108 | ) 109 | ] 110 | |> List.iter (fun (input, expected) -> 111 | let result = JsonParsers.Parser(Reader.ofArray (input.ToCharArray()) ()) 112 | 113 | match result with 114 | | Ok result -> "" |> Expect.equal expected result.Parsed 115 | | Error e -> failwithf "%A" e 116 | ) 117 | } 118 | 119 | test "Objects" { 120 | [ 121 | "{}", JsonValue.Object(ImmutableArray.Empty) 122 | "{ }", JsonValue.Object(ImmutableArray.Empty) 123 | "{ \t\r\n }", JsonValue.Object(ImmutableArray.Empty) 124 | "{\"key\": 123}", 125 | JsonValue.Object( 126 | ImmutableArray.Create 127 | { 128 | Name = "key" 129 | Value = JsonValue.Number 123.0 130 | } 131 | ) 132 | "{\"key\": 123, \"key2\": \"value\"}", 133 | JsonValue.Object( 134 | ImmutableArray.CreateRange 135 | [ 136 | { 137 | Name = "key" 138 | Value = JsonValue.Number 123.0 139 | } 140 | { 141 | Name = "key2" 142 | Value = JsonValue.String "value" 143 | } 144 | ] 145 | ) 146 | "{\"key\": 123, \"key2\": \"value\", \"key3\": true}", 147 | JsonValue.Object( 148 | ImmutableArray.CreateRange 149 | [ 150 | { 151 | Name = "key" 152 | Value = JsonValue.Number 123.0 153 | } 154 | { 155 | Name = "key2" 156 | Value = JsonValue.String "value" 157 | } 158 | { 159 | Name = "key3" 160 | Value = JsonValue.True 161 | } 162 | ] 163 | ) 164 | ] 165 | |> List.iter (fun (input, expected) -> 166 | let result = JsonParsers.Parser(Reader.ofArray (input.ToCharArray()) ()) 167 | 168 | match result with 169 | | Ok result -> "" |> Expect.equal expected result.Parsed 170 | | Error e -> failwithf "%A" e 171 | ) 172 | } 173 | 174 | ] 175 | -------------------------------------------------------------------------------- /test/XParsec.Json.Tests/JsonParserStringTests.fs: -------------------------------------------------------------------------------- 1 | module JsonParserStringTests 2 | 3 | open System.Collections.Immutable 4 | 5 | open Expecto 6 | 7 | open XParsec 8 | open XParsec.Json 9 | 10 | [] 11 | let tests = 12 | testList 13 | "JsonParserTests" 14 | [ 15 | test "Parser fragments" { 16 | let s, expected, p = 17 | "\"string\"", JsonValue.String "string", JsonParsers.PString |>> JsonValue.String 18 | 19 | let reader = Reader.ofString s () 20 | let result = p reader 21 | 22 | match result with 23 | | Ok result -> "" |> Expect.equal expected result.Parsed 24 | | Error e -> failwithf "%A" e 25 | } 26 | 27 | 28 | test "single values" { 29 | [ "true", JsonValue.True; "false", JsonValue.False; "null", JsonValue.Null ] 30 | |> List.iter (fun (input, expected) -> 31 | let result = JsonParsers.Parser(Reader.ofString input ()) 32 | 33 | match result with 34 | | Ok result -> "" |> Expect.equal expected result.Parsed 35 | | Error e -> failwithf "%A" e 36 | ) 37 | } 38 | 39 | 40 | test "numbers" { 41 | [ 42 | "123", JsonValue.Number 123.0 43 | "123.456", JsonValue.Number 123.456 44 | "-123.456", JsonValue.Number -123.456 45 | "123e4", JsonValue.Number 123e4 46 | "123e-4", JsonValue.Number 123e-4 47 | "123e+4", JsonValue.Number 123e4 48 | "123.456e4", JsonValue.Number 123.456e4 49 | "123.456e-4", JsonValue.Number 123.456e-4 50 | "123.456e+4", JsonValue.Number 123.456e4 51 | "-123e4", JsonValue.Number -123e4 52 | "-123e-4", JsonValue.Number -123e-4 53 | "-123e+4", JsonValue.Number -123e4 54 | "-123.456e4", JsonValue.Number -123.456e4 55 | "-123.456e-4", JsonValue.Number -123.456e-4 56 | "-123.456e+4", JsonValue.Number -123.456e4 57 | ] 58 | |> List.iter (fun (input, expected) -> 59 | let result = JsonParsers.Parser(Reader.ofString input ()) 60 | 61 | match result with 62 | | Ok result -> "" |> Expect.equal expected result.Parsed 63 | | Error e -> failwithf "%A" e 64 | ) 65 | } 66 | 67 | test "Strings" { 68 | [ 69 | "\"string\"", JsonValue.String "string" 70 | "\"\"", JsonValue.String "" 71 | "\" \"", JsonValue.String " " 72 | "\"\\\"\"", JsonValue.String "\"" 73 | "\"\\\\\"", JsonValue.String "\\" 74 | "\"\\/\"", JsonValue.String "/" 75 | "\"\\b\"", JsonValue.String "\b" 76 | "\"\\f\"", JsonValue.String "\f" 77 | "\"\\n\"", JsonValue.String "\n" 78 | "\"\\r\"", JsonValue.String "\r" 79 | "\"\\t\"", JsonValue.String "\t" 80 | "\"\\u0000\"", JsonValue.String "\u0000" 81 | "\"\\u0001\"", JsonValue.String "\u0001" 82 | "\"\\u0002\"", JsonValue.String "\u0002" 83 | "\"\\uffff\"", JsonValue.String "\uffff" 84 | ] 85 | |> List.iter (fun (input, expected) -> 86 | let result = JsonParsers.Parser(Reader.ofString input ()) 87 | 88 | match result with 89 | | Ok result -> "" |> Expect.equal expected result.Parsed 90 | | Error e -> failwithf "%A" e 91 | ) 92 | } 93 | 94 | test "Arrays" { 95 | [ 96 | "[]", JsonValue.Array(ImmutableArray.Empty) 97 | "[ ]", JsonValue.Array(ImmutableArray.Empty) 98 | "[ \t\r\n ]", JsonValue.Array(ImmutableArray.Empty) 99 | "[1, 2, 3]", 100 | JsonValue.Array( 101 | ImmutableArray.CreateRange [ JsonValue.Number 1.0; JsonValue.Number 2.0; JsonValue.Number 3.0 ] 102 | ) 103 | "[true, false, null]", 104 | JsonValue.Array(ImmutableArray.CreateRange [ JsonValue.True; JsonValue.False; JsonValue.Null ]) 105 | "[\"string\", 123, true]", 106 | JsonValue.Array( 107 | ImmutableArray.CreateRange [ JsonValue.String "string"; JsonValue.Number 123.0; JsonValue.True ] 108 | ) 109 | ] 110 | |> List.iter (fun (input, expected) -> 111 | let result = JsonParsers.Parser(Reader.ofString input ()) 112 | 113 | match result with 114 | | Ok result -> "" |> Expect.equal expected result.Parsed 115 | | Error e -> failwithf "%A" e 116 | ) 117 | } 118 | 119 | test "Objects" { 120 | [ 121 | "{}", JsonValue.Object(ImmutableArray.Empty) 122 | "{ }", JsonValue.Object(ImmutableArray.Empty) 123 | "{ \t\r\n }", JsonValue.Object(ImmutableArray.Empty) 124 | "{\"key\": 123}", 125 | JsonValue.Object( 126 | ImmutableArray.Create 127 | { 128 | Name = "key" 129 | Value = JsonValue.Number 123.0 130 | } 131 | ) 132 | "{\"key\": 123, \"key2\": \"value\"}", 133 | JsonValue.Object( 134 | ImmutableArray.CreateRange 135 | [ 136 | { 137 | Name = "key" 138 | Value = JsonValue.Number 123.0 139 | } 140 | { 141 | Name = "key2" 142 | Value = JsonValue.String "value" 143 | } 144 | ] 145 | ) 146 | "{\"key\": 123, \"key2\": \"value\", \"key3\": true}", 147 | JsonValue.Object( 148 | ImmutableArray.CreateRange 149 | [ 150 | { 151 | Name = "key" 152 | Value = JsonValue.Number 123.0 153 | } 154 | { 155 | Name = "key2" 156 | Value = JsonValue.String "value" 157 | } 158 | { 159 | Name = "key3" 160 | Value = JsonValue.True 161 | } 162 | ] 163 | ) 164 | ] 165 | |> List.iter (fun (input, expected) -> 166 | let result = JsonParsers.Parser(Reader.ofString input ()) 167 | 168 | match result with 169 | | Ok result -> "" |> Expect.equal expected result.Parsed 170 | | Error e -> failwithf "%A" e 171 | ) 172 | } 173 | 174 | ] 175 | -------------------------------------------------------------------------------- /test/XParsec.Json.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open Expecto 4 | 5 | [] 6 | let main argv = 7 | Tests.runTestsInAssemblyWithCLIArgs [] argv 8 | -------------------------------------------------------------------------------- /test/XParsec.Json.Tests/XParsec.Json.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/XParsec.Json.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Expecto": { 6 | "type": "Direct", 7 | "requested": "[10.2.2, )", 8 | "resolved": "10.2.2", 9 | "contentHash": "Ul1wNnvM4sugoP5toPmYNwOJgj7fb0MhbEtTXOz/Z68oiUeLWRHdt1+KGQaseENbt8fnJfiK3zfef+/2dTL8Jg==", 10 | "dependencies": { 11 | "FSharp.Core": "7.0.200", 12 | "Mono.Cecil": "[0.11.4, 1.0.0)" 13 | } 14 | }, 15 | "Expecto.FsCheck": { 16 | "type": "Direct", 17 | "requested": "[10.2.2, )", 18 | "resolved": "10.2.2", 19 | "contentHash": "ef5jUN+wXrW1c4YnPtaM16CMnlNoSs04quT4lQ7L9MdD1tPjK25DtJaFC7KUZIEf/n5DrXw+K0CZ9VLp2E/QRw==", 20 | "dependencies": { 21 | "Expecto": "10.2.2", 22 | "FsCheck": "[2.16.5, 3.0.0)" 23 | } 24 | }, 25 | "FSharp.Core": { 26 | "type": "Direct", 27 | "requested": "[9.0.201, )", 28 | "resolved": "9.0.201", 29 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 30 | }, 31 | "Microsoft.NET.Test.Sdk": { 32 | "type": "Direct", 33 | "requested": "[17.13.0, )", 34 | "resolved": "17.13.0", 35 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 36 | "dependencies": { 37 | "Microsoft.CodeCoverage": "17.13.0", 38 | "Microsoft.TestPlatform.TestHost": "17.13.0" 39 | } 40 | }, 41 | "YoloDev.Expecto.TestSdk": { 42 | "type": "Direct", 43 | "requested": "[0.15.3, )", 44 | "resolved": "0.15.3", 45 | "contentHash": "fpHXx4+RD8HrLsxg/NhP+uvqIPzFfoLNoPZ3F8Yi+Nv8DPTHqwdnDyPW9MD+036icExUYiv/qc/rOuSfE0TIOA==", 46 | "dependencies": { 47 | "Expecto": "[10.2.2, 11.0.0)", 48 | "FSharp.Core": "7.0.200", 49 | "Microsoft.Testing.Extensions.VSTestBridge": "1.6.2", 50 | "Microsoft.Testing.Platform.MSBuild": "1.6.2" 51 | } 52 | }, 53 | "FsCheck": { 54 | "type": "Transitive", 55 | "resolved": "2.16.5", 56 | "contentHash": "Tw6Lb/UBqtmUj5porM1rRcAU5xT9NttSlFTaIQ8gn4Fsw2LiZavN/eMAZqSA63laV51t4HRJJAAGeYSWFFxRfA==", 57 | "dependencies": { 58 | "FSharp.Core": "4.2.3" 59 | } 60 | }, 61 | "Microsoft.ApplicationInsights": { 62 | "type": "Transitive", 63 | "resolved": "2.22.0", 64 | "contentHash": "3AOM9bZtku7RQwHyMEY3tQMrHIgjcfRDa6YQpd/QG2LDGvMydSlL9Di+8LLMt7J2RDdfJ7/2jdYv6yHcMJAnNw==", 65 | "dependencies": { 66 | "System.Diagnostics.DiagnosticSource": "5.0.0" 67 | } 68 | }, 69 | "Microsoft.CodeCoverage": { 70 | "type": "Transitive", 71 | "resolved": "17.13.0", 72 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 73 | }, 74 | "Microsoft.Testing.Extensions.Telemetry": { 75 | "type": "Transitive", 76 | "resolved": "1.6.2", 77 | "contentHash": "40oMlQzyey4jOihY0IpUufSoMYeijYgvrtIxuYmuVx1k5xl271XlP0gwD2DwAKnvmmP0cocou531d6/CB3cCIA==", 78 | "dependencies": { 79 | "Microsoft.ApplicationInsights": "2.22.0", 80 | "Microsoft.Testing.Platform": "1.6.2" 81 | } 82 | }, 83 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": { 84 | "type": "Transitive", 85 | "resolved": "1.6.2", 86 | "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", 87 | "dependencies": { 88 | "Microsoft.Testing.Platform": "1.6.2" 89 | } 90 | }, 91 | "Microsoft.Testing.Extensions.VSTestBridge": { 92 | "type": "Transitive", 93 | "resolved": "1.6.2", 94 | "contentHash": "ZvYa+VDuk39EIqyOZ/IMFSRd/N54zFBnDFmDagFBJt21vZZnSG6l/3CkJX3DvmYmuf5Byj9w7Xf46mkWuur4LQ==", 95 | "dependencies": { 96 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 97 | "Microsoft.Testing.Extensions.Telemetry": "1.6.2", 98 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", 99 | "Microsoft.Testing.Platform": "1.6.2" 100 | } 101 | }, 102 | "Microsoft.Testing.Platform": { 103 | "type": "Transitive", 104 | "resolved": "1.6.2", 105 | "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" 106 | }, 107 | "Microsoft.Testing.Platform.MSBuild": { 108 | "type": "Transitive", 109 | "resolved": "1.6.2", 110 | "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", 111 | "dependencies": { 112 | "Microsoft.Testing.Platform": "1.6.2" 113 | } 114 | }, 115 | "Microsoft.TestPlatform.ObjectModel": { 116 | "type": "Transitive", 117 | "resolved": "17.13.0", 118 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 119 | "dependencies": { 120 | "System.Reflection.Metadata": "1.6.0" 121 | } 122 | }, 123 | "Microsoft.TestPlatform.TestHost": { 124 | "type": "Transitive", 125 | "resolved": "17.13.0", 126 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 127 | "dependencies": { 128 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 129 | "Newtonsoft.Json": "13.0.1" 130 | } 131 | }, 132 | "Mono.Cecil": { 133 | "type": "Transitive", 134 | "resolved": "0.11.4", 135 | "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==" 136 | }, 137 | "Newtonsoft.Json": { 138 | "type": "Transitive", 139 | "resolved": "13.0.1", 140 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 141 | }, 142 | "System.Diagnostics.DiagnosticSource": { 143 | "type": "Transitive", 144 | "resolved": "5.0.0", 145 | "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" 146 | }, 147 | "System.Reflection.Metadata": { 148 | "type": "Transitive", 149 | "resolved": "1.6.0", 150 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 151 | }, 152 | "xparsec": { 153 | "type": "Project", 154 | "dependencies": { 155 | "FSharp.Core": "[9.0.201, )", 156 | "System.Collections.Immutable": "[9.0.3, )" 157 | } 158 | }, 159 | "xparsec.json": { 160 | "type": "Project", 161 | "dependencies": { 162 | "XParsec": "[0.1.0, )" 163 | } 164 | }, 165 | "System.Collections.Immutable": { 166 | "type": "CentralTransitive", 167 | "requested": "[9.0.3, )", 168 | "resolved": "9.0.3", 169 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 170 | } 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /test/XParsec.MessagePack.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open Expecto 4 | 5 | [] 6 | let main argv = 7 | Tests.runTestsInAssemblyWithCLIArgs [] argv 8 | -------------------------------------------------------------------------------- /test/XParsec.MessagePack.Tests/MessagePackTests.fs: -------------------------------------------------------------------------------- 1 | module MessagePackTests 2 | 3 | open System 4 | open System.IO 5 | 6 | open Expecto 7 | 8 | open XParsec 9 | 10 | open MessagePack 11 | open MessagePack.ArrayParsers 12 | 13 | 14 | let testParse input format value = 15 | let actualFormat = 16 | let reader = Reader.ofArray input () 17 | pFormat () reader 18 | 19 | let actualValue = 20 | let reader = Reader.ofArray input () 21 | pObject () reader 22 | 23 | match actualFormat with 24 | | Ok f -> "Format" |> Expect.equal f.Parsed format 25 | | Error e -> failwithf "%A" e 26 | 27 | match actualValue with 28 | | Ok v -> "Value" |> Expect.equal v.Parsed value 29 | | Error e -> failwithf "%A" e 30 | 31 | let actualFormat = 32 | let reader = Reader.ofStream (new MemoryStream(input)) 128 () 33 | pFormat () reader 34 | 35 | let actualValue = 36 | let reader = Reader.ofStream (new MemoryStream(input)) 128 () 37 | pObject () reader 38 | 39 | match actualFormat with 40 | | Ok f -> "Format Stream" |> Expect.equal f.Parsed format 41 | | Error e -> failwithf "%A" e 42 | 43 | match actualValue with 44 | | Ok v -> "Value Stream" |> Expect.equal v.Parsed value 45 | | Error e -> failwithf "%A" e 46 | 47 | [] 48 | let tests = 49 | testList 50 | "MessagePackParsing" 51 | [ 52 | test "ParseNil" { 53 | let input, format, value = [| 0xc0uy |], Formats.Nil, MsgPackValue.Nil 54 | testParse input format value 55 | } 56 | test "ParseTrue" { 57 | let input, format, value = [| 0xc3uy |], Formats.True, MsgPackValue.True 58 | testParse input format value 59 | } 60 | test "ParseFalse" { 61 | let input, format, value = [| 0xc2uy |], Formats.False, MsgPackValue.False 62 | testParse input format value 63 | } 64 | test "ParsePositiveFixInt" { 65 | for i in 0uy .. 127uy do 66 | let input, format, value = [| i |], Formats.PositiveFixInt i, MsgPackValue.UInt8 i 67 | testParse input format value 68 | } 69 | test "ParseNegativeFixInt" { 70 | for i in -31y .. -1y do 71 | let b = byte i 72 | let input, format, value = [| b |], Formats.NegativeFixInt b, MsgPackValue.Int8 i 73 | testParse input format value 74 | } 75 | test "ParseUInt8" { 76 | let input, format, value = 77 | [| 0xccuy; 0xffuy |], Formats.UInt8, MsgPackValue.UInt8 255uy 78 | 79 | testParse input format value 80 | } 81 | test "ParseUInt16" { 82 | let input, format, value = 83 | [| 0xcduy; 0xffuy; 0xffuy |], Formats.UInt16, MsgPackValue.UInt16 65535us 84 | 85 | testParse input format value 86 | } 87 | test "ParseUInt32" { 88 | let input, format, value = 89 | [| 0xceuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy |], Formats.UInt32, MsgPackValue.UInt32 4294967295ul 90 | 91 | testParse input format value 92 | } 93 | test "ParseUInt64" { 94 | let input, format, value = 95 | [| 0xcfuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy; 0xffuy |], 96 | Formats.UInt64, 97 | MsgPackValue.UInt64 18446744073709551615UL 98 | 99 | testParse input format value 100 | } 101 | test "ParseInt8" { 102 | let input, format, value = 103 | [| 0xd0uy; 0x80uy |], Formats.Int8, MsgPackValue.Int8 -128y 104 | 105 | testParse input format value 106 | } 107 | test "ParseInt16" { 108 | let input, format, value = 109 | [| 0xd1uy; 0x80uy; 0x00uy |], Formats.Int16, MsgPackValue.Int16 -32768s 110 | 111 | testParse input format value 112 | } 113 | test "ParseInt32" { 114 | let input, format, value = 115 | [| 0xd2uy; 0x80uy; 0x00uy; 0x00uy; 0x00uy |], Formats.Int32, MsgPackValue.Int32 -2147483648 116 | 117 | testParse input format value 118 | } 119 | test "ParseInt64" { 120 | let input, format, value = 121 | [| 0xd3uy; 0x80uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy |], 122 | Formats.Int64, 123 | MsgPackValue.Int64 -9223372036854775808L 124 | 125 | testParse input format value 126 | } 127 | test "ParseFloat32" { 128 | let input, format, value = 129 | [| 0xcauy; 0x3fuy; 0x80uy; 0x00uy; 0x00uy |], Formats.Float32, MsgPackValue.Float32 1.0f 130 | 131 | testParse input format value 132 | } 133 | test "ParseFloat64" { 134 | let input, format, value = 135 | [| 0xcbuy; 0x3fuy; 0xf0uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy |], 136 | Formats.Float64, 137 | MsgPackValue.Float64 1.0 138 | 139 | testParse input format value 140 | } 141 | test "ParseStr8" { 142 | let input, format, value = 143 | [| 0xd9uy; 0x05uy; 0x48uy; 0x65uy; 0x6cuy; 0x6cuy; 0x6fuy |], Formats.Str8, MsgPackValue.Str "Hello" 144 | 145 | testParse input format value 146 | } 147 | test "ParseStr16" { 148 | let input, format, value = 149 | [| 0xdauy; 0x00uy; 0x05uy; 0x48uy; 0x65uy; 0x6cuy; 0x6cuy; 0x6fuy |], 150 | Formats.Str16, 151 | MsgPackValue.Str "Hello" 152 | 153 | testParse input format value 154 | } 155 | test "ParseStr32" { 156 | let input, format, value = 157 | [| 158 | 0xdbuy 159 | 0x00uy 160 | 0x00uy 161 | 0x00uy 162 | 0x05uy 163 | 0x48uy 164 | 0x65uy 165 | 0x6cuy 166 | 0x6cuy 167 | 0x6fuy 168 | |], 169 | Formats.Str32, 170 | MsgPackValue.Str "Hello" 171 | 172 | testParse input format value 173 | } 174 | test "ParseBin8" { 175 | let input, format, value = 176 | [| 0xc4uy; 0x03uy; 0x01uy; 0x02uy; 0x03uy |], 177 | Formats.Bin8, 178 | MsgPackValue.Bin [| 0x01uy; 0x02uy; 0x03uy |] 179 | 180 | testParse input format value 181 | } 182 | test "ParseBin16" { 183 | let input, format, value = 184 | [| 0xc5uy; 0x00uy; 0x03uy; 0x01uy; 0x02uy; 0x03uy |], 185 | Formats.Bin16, 186 | MsgPackValue.Bin [| 0x01uy; 0x02uy; 0x03uy |] 187 | 188 | testParse input format value 189 | } 190 | test "ParseBin32" { 191 | let input, format, value = 192 | [| 0xc6uy; 0x00uy; 0x00uy; 0x00uy; 0x03uy; 0x01uy; 0x02uy; 0x03uy |], 193 | Formats.Bin32, 194 | MsgPackValue.Bin [| 0x01uy; 0x02uy; 0x03uy |] 195 | 196 | testParse input format value 197 | } 198 | ] 199 | -------------------------------------------------------------------------------- /test/XParsec.MessagePack.Tests/XParsec.MessagePack.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/XParsec.MessagePack.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Expecto": { 6 | "type": "Direct", 7 | "requested": "[10.2.2, )", 8 | "resolved": "10.2.2", 9 | "contentHash": "Ul1wNnvM4sugoP5toPmYNwOJgj7fb0MhbEtTXOz/Z68oiUeLWRHdt1+KGQaseENbt8fnJfiK3zfef+/2dTL8Jg==", 10 | "dependencies": { 11 | "FSharp.Core": "7.0.200", 12 | "Mono.Cecil": "[0.11.4, 1.0.0)" 13 | } 14 | }, 15 | "Expecto.FsCheck": { 16 | "type": "Direct", 17 | "requested": "[10.2.2, )", 18 | "resolved": "10.2.2", 19 | "contentHash": "ef5jUN+wXrW1c4YnPtaM16CMnlNoSs04quT4lQ7L9MdD1tPjK25DtJaFC7KUZIEf/n5DrXw+K0CZ9VLp2E/QRw==", 20 | "dependencies": { 21 | "Expecto": "10.2.2", 22 | "FsCheck": "[2.16.5, 3.0.0)" 23 | } 24 | }, 25 | "FSharp.Core": { 26 | "type": "Direct", 27 | "requested": "[9.0.201, )", 28 | "resolved": "9.0.201", 29 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 30 | }, 31 | "Microsoft.NET.Test.Sdk": { 32 | "type": "Direct", 33 | "requested": "[17.13.0, )", 34 | "resolved": "17.13.0", 35 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 36 | "dependencies": { 37 | "Microsoft.CodeCoverage": "17.13.0", 38 | "Microsoft.TestPlatform.TestHost": "17.13.0" 39 | } 40 | }, 41 | "YoloDev.Expecto.TestSdk": { 42 | "type": "Direct", 43 | "requested": "[0.15.3, )", 44 | "resolved": "0.15.3", 45 | "contentHash": "fpHXx4+RD8HrLsxg/NhP+uvqIPzFfoLNoPZ3F8Yi+Nv8DPTHqwdnDyPW9MD+036icExUYiv/qc/rOuSfE0TIOA==", 46 | "dependencies": { 47 | "Expecto": "[10.2.2, 11.0.0)", 48 | "FSharp.Core": "7.0.200", 49 | "Microsoft.Testing.Extensions.VSTestBridge": "1.6.2", 50 | "Microsoft.Testing.Platform.MSBuild": "1.6.2" 51 | } 52 | }, 53 | "FsCheck": { 54 | "type": "Transitive", 55 | "resolved": "2.16.5", 56 | "contentHash": "Tw6Lb/UBqtmUj5porM1rRcAU5xT9NttSlFTaIQ8gn4Fsw2LiZavN/eMAZqSA63laV51t4HRJJAAGeYSWFFxRfA==", 57 | "dependencies": { 58 | "FSharp.Core": "4.2.3" 59 | } 60 | }, 61 | "Microsoft.ApplicationInsights": { 62 | "type": "Transitive", 63 | "resolved": "2.22.0", 64 | "contentHash": "3AOM9bZtku7RQwHyMEY3tQMrHIgjcfRDa6YQpd/QG2LDGvMydSlL9Di+8LLMt7J2RDdfJ7/2jdYv6yHcMJAnNw==", 65 | "dependencies": { 66 | "System.Diagnostics.DiagnosticSource": "5.0.0" 67 | } 68 | }, 69 | "Microsoft.CodeCoverage": { 70 | "type": "Transitive", 71 | "resolved": "17.13.0", 72 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 73 | }, 74 | "Microsoft.Testing.Extensions.Telemetry": { 75 | "type": "Transitive", 76 | "resolved": "1.6.2", 77 | "contentHash": "40oMlQzyey4jOihY0IpUufSoMYeijYgvrtIxuYmuVx1k5xl271XlP0gwD2DwAKnvmmP0cocou531d6/CB3cCIA==", 78 | "dependencies": { 79 | "Microsoft.ApplicationInsights": "2.22.0", 80 | "Microsoft.Testing.Platform": "1.6.2" 81 | } 82 | }, 83 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": { 84 | "type": "Transitive", 85 | "resolved": "1.6.2", 86 | "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", 87 | "dependencies": { 88 | "Microsoft.Testing.Platform": "1.6.2" 89 | } 90 | }, 91 | "Microsoft.Testing.Extensions.VSTestBridge": { 92 | "type": "Transitive", 93 | "resolved": "1.6.2", 94 | "contentHash": "ZvYa+VDuk39EIqyOZ/IMFSRd/N54zFBnDFmDagFBJt21vZZnSG6l/3CkJX3DvmYmuf5Byj9w7Xf46mkWuur4LQ==", 95 | "dependencies": { 96 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 97 | "Microsoft.Testing.Extensions.Telemetry": "1.6.2", 98 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", 99 | "Microsoft.Testing.Platform": "1.6.2" 100 | } 101 | }, 102 | "Microsoft.Testing.Platform": { 103 | "type": "Transitive", 104 | "resolved": "1.6.2", 105 | "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" 106 | }, 107 | "Microsoft.Testing.Platform.MSBuild": { 108 | "type": "Transitive", 109 | "resolved": "1.6.2", 110 | "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", 111 | "dependencies": { 112 | "Microsoft.Testing.Platform": "1.6.2" 113 | } 114 | }, 115 | "Microsoft.TestPlatform.ObjectModel": { 116 | "type": "Transitive", 117 | "resolved": "17.13.0", 118 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 119 | "dependencies": { 120 | "System.Reflection.Metadata": "1.6.0" 121 | } 122 | }, 123 | "Microsoft.TestPlatform.TestHost": { 124 | "type": "Transitive", 125 | "resolved": "17.13.0", 126 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 127 | "dependencies": { 128 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 129 | "Newtonsoft.Json": "13.0.1" 130 | } 131 | }, 132 | "Mono.Cecil": { 133 | "type": "Transitive", 134 | "resolved": "0.11.4", 135 | "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==" 136 | }, 137 | "Newtonsoft.Json": { 138 | "type": "Transitive", 139 | "resolved": "13.0.1", 140 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 141 | }, 142 | "System.Diagnostics.DiagnosticSource": { 143 | "type": "Transitive", 144 | "resolved": "5.0.0", 145 | "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" 146 | }, 147 | "System.Reflection.Metadata": { 148 | "type": "Transitive", 149 | "resolved": "1.6.0", 150 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 151 | }, 152 | "xparsec": { 153 | "type": "Project", 154 | "dependencies": { 155 | "FSharp.Core": "[9.0.201, )", 156 | "System.Collections.Immutable": "[9.0.3, )" 157 | } 158 | }, 159 | "System.Collections.Immutable": { 160 | "type": "CentralTransitive", 161 | "requested": "[9.0.3, )", 162 | "resolved": "9.0.3", 163 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 164 | } 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /test/XParsec.Tests/ByteParsersTests.fs: -------------------------------------------------------------------------------- 1 | module ByteParsersTests 2 | #if !FABLE_COMPILER 3 | open System 4 | open System.Buffers.Binary 5 | 6 | open Expecto 7 | 8 | open XParsec 9 | open XParsec.ByteParsers 10 | 11 | let zeroes = Array.zeroCreate 16 12 | let odds = Array.init 16 (fun i -> 1uy + (byte i)) 13 | let evens = Array.init 16 (fun i -> 2uy + (byte i)) 14 | 15 | let testParser<'T when 'T: equality> p (input, expected) = 16 | let reader = Reader.ofArray input () 17 | 18 | match p reader with 19 | | Ok result -> 20 | "" |> Expect.equal (result.Parsed: 'T) expected 21 | "" |> Expect.equal reader.Index (int64 sizeof<'T>) 22 | | Error e -> failwithf "%A" e 23 | 24 | 25 | [] 26 | let tests = 27 | testList 28 | "ByteParsersTests" 29 | [ 30 | test "pFloat16BE" { 31 | [ 32 | zeroes, Half.Zero 33 | odds, BinaryPrimitives.ReadHalfBigEndian odds 34 | evens, BinaryPrimitives.ReadHalfBigEndian evens 35 | ] 36 | |> List.iter (testParser pFloat16BE) 37 | } 38 | test "pFloat16LE" { 39 | [ 40 | zeroes, Half.Zero 41 | odds, BinaryPrimitives.ReadHalfLittleEndian odds 42 | evens, BinaryPrimitives.ReadHalfLittleEndian evens 43 | ] 44 | |> List.iter (testParser pFloat16LE) 45 | } 46 | test "pFloat32BE" { 47 | [ 48 | zeroes, 0.0f 49 | odds, BinaryPrimitives.ReadSingleBigEndian odds 50 | evens, BinaryPrimitives.ReadSingleBigEndian evens 51 | ] 52 | |> List.iter (testParser pFloat32BE) 53 | } 54 | test "pFloat32LE" { 55 | [ 56 | zeroes, 0.0f 57 | odds, BinaryPrimitives.ReadSingleLittleEndian odds 58 | evens, BinaryPrimitives.ReadSingleLittleEndian evens 59 | ] 60 | |> List.iter (testParser pFloat32LE) 61 | } 62 | test "pFloat64BE" { 63 | [ 64 | zeroes, 0.0 65 | odds, BinaryPrimitives.ReadDoubleBigEndian odds 66 | evens, BinaryPrimitives.ReadDoubleBigEndian evens 67 | ] 68 | |> List.iter (testParser pFloat64BE) 69 | } 70 | test "pFloat64LE" { 71 | [ 72 | zeroes, 0.0 73 | odds, BinaryPrimitives.ReadDoubleLittleEndian odds 74 | evens, BinaryPrimitives.ReadDoubleLittleEndian evens 75 | ] 76 | |> List.iter (testParser pFloat64LE) 77 | } 78 | test "pUInt16BE" { 79 | [ 80 | zeroes, 0us 81 | odds, BinaryPrimitives.ReadUInt16BigEndian odds 82 | evens, BinaryPrimitives.ReadUInt16BigEndian evens 83 | ] 84 | |> List.iter (testParser pUInt16BE) 85 | } 86 | test "pUInt16LE" { 87 | [ 88 | zeroes, 0us 89 | odds, BinaryPrimitives.ReadUInt16LittleEndian odds 90 | evens, BinaryPrimitives.ReadUInt16LittleEndian evens 91 | ] 92 | |> List.iter (testParser pUInt16LE) 93 | } 94 | test "pUInt32BE" { 95 | [ 96 | zeroes, 0u 97 | odds, BinaryPrimitives.ReadUInt32BigEndian odds 98 | evens, BinaryPrimitives.ReadUInt32BigEndian evens 99 | ] 100 | |> List.iter (testParser pUInt32BE) 101 | } 102 | test "pUInt32LE" { 103 | [ 104 | zeroes, 0u 105 | odds, BinaryPrimitives.ReadUInt32LittleEndian odds 106 | evens, BinaryPrimitives.ReadUInt32LittleEndian evens 107 | ] 108 | |> List.iter (testParser pUInt32LE) 109 | } 110 | test "pUInt64BE" { 111 | [ 112 | zeroes, 0UL 113 | odds, BinaryPrimitives.ReadUInt64BigEndian odds 114 | evens, BinaryPrimitives.ReadUInt64BigEndian evens 115 | ] 116 | |> List.iter (testParser pUInt64BE) 117 | } 118 | test "pUInt64LE" { 119 | [ 120 | zeroes, 0UL 121 | odds, BinaryPrimitives.ReadUInt64LittleEndian odds 122 | evens, BinaryPrimitives.ReadUInt64LittleEndian evens 123 | ] 124 | |> List.iter (testParser pUInt64LE) 125 | } 126 | test "pUInt128BE" { 127 | [ 128 | zeroes, UInt128.Zero 129 | odds, BinaryPrimitives.ReadUInt128BigEndian odds 130 | evens, BinaryPrimitives.ReadUInt128BigEndian evens 131 | ] 132 | |> List.iter (testParser pUInt128BE) 133 | } 134 | test "pUInt128LE" { 135 | [ 136 | zeroes, UInt128.Zero 137 | odds, BinaryPrimitives.ReadUInt128LittleEndian odds 138 | evens, BinaryPrimitives.ReadUInt128LittleEndian evens 139 | ] 140 | |> List.iter (testParser pUInt128LE) 141 | } 142 | test "pUIntPtrBE" { 143 | [ 144 | zeroes, UIntPtr.Zero 145 | odds, BinaryPrimitives.ReadUIntPtrBigEndian odds 146 | evens, BinaryPrimitives.ReadUIntPtrBigEndian evens 147 | ] 148 | |> List.iter (testParser pUIntPtrBE) 149 | } 150 | test "pUIntPtrLE" { 151 | [ 152 | zeroes, UIntPtr.Zero 153 | odds, BinaryPrimitives.ReadUIntPtrLittleEndian odds 154 | evens, BinaryPrimitives.ReadUIntPtrLittleEndian evens 155 | ] 156 | |> List.iter (testParser pUIntPtrLE) 157 | } 158 | test "pInt16BE" { 159 | [ 160 | zeroes, 0s 161 | odds, BinaryPrimitives.ReadInt16BigEndian odds 162 | evens, BinaryPrimitives.ReadInt16BigEndian evens 163 | ] 164 | |> List.iter (testParser pInt16BE) 165 | } 166 | test "pInt16LE" { 167 | [ 168 | zeroes, 0s 169 | odds, BinaryPrimitives.ReadInt16LittleEndian odds 170 | evens, BinaryPrimitives.ReadInt16LittleEndian evens 171 | ] 172 | |> List.iter (testParser pInt16LE) 173 | } 174 | test "pInt32BE" { 175 | [ 176 | zeroes, 0 177 | odds, BinaryPrimitives.ReadInt32BigEndian odds 178 | evens, BinaryPrimitives.ReadInt32BigEndian evens 179 | ] 180 | |> List.iter (testParser pInt32BE) 181 | } 182 | test "pInt32LE" { 183 | [ 184 | zeroes, 0 185 | odds, BinaryPrimitives.ReadInt32LittleEndian odds 186 | evens, BinaryPrimitives.ReadInt32LittleEndian evens 187 | ] 188 | |> List.iter (testParser pInt32LE) 189 | } 190 | test "pInt64BE" { 191 | [ 192 | zeroes, 0L 193 | odds, BinaryPrimitives.ReadInt64BigEndian odds 194 | evens, BinaryPrimitives.ReadInt64BigEndian evens 195 | ] 196 | |> List.iter (testParser pInt64BE) 197 | } 198 | test "pInt64LE" { 199 | [ 200 | zeroes, 0L 201 | odds, BinaryPrimitives.ReadInt64LittleEndian odds 202 | evens, BinaryPrimitives.ReadInt64LittleEndian evens 203 | ] 204 | |> List.iter (testParser pInt64LE) 205 | } 206 | test "pInt128BE" { 207 | [ 208 | zeroes, Int128.Zero 209 | odds, BinaryPrimitives.ReadInt128BigEndian odds 210 | evens, BinaryPrimitives.ReadInt128BigEndian evens 211 | ] 212 | |> List.iter (testParser pInt128BE) 213 | } 214 | test "pInt128LE" { 215 | [ 216 | zeroes, Int128.Zero 217 | odds, BinaryPrimitives.ReadInt128LittleEndian odds 218 | evens, BinaryPrimitives.ReadInt128LittleEndian evens 219 | ] 220 | |> List.iter (testParser pInt128LE) 221 | } 222 | test "pIntPtrBE" { 223 | [ 224 | zeroes, IntPtr.Zero 225 | odds, BinaryPrimitives.ReadIntPtrBigEndian odds 226 | evens, BinaryPrimitives.ReadIntPtrBigEndian evens 227 | ] 228 | |> List.iter (testParser pIntPtrBE) 229 | } 230 | test "pIntPtrLE" { 231 | [ 232 | zeroes, IntPtr.Zero 233 | odds, BinaryPrimitives.ReadIntPtrLittleEndian odds 234 | evens, BinaryPrimitives.ReadIntPtrLittleEndian evens 235 | ] 236 | |> List.iter (testParser pIntPtrLE) 237 | } 238 | 239 | test "Composite Read Odds" { 240 | let input = odds 241 | 242 | let p = tuple5 pFloat16BE pUInt16LE pInt16BE pFloat32LE pUInt32BE 243 | 244 | let expected = 245 | let span = input.AsSpan() 246 | 247 | struct (BinaryPrimitives.ReadHalfBigEndian span, 248 | BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(2)), 249 | BinaryPrimitives.ReadInt16BigEndian(span.Slice(4)), 250 | BinaryPrimitives.ReadSingleLittleEndian(span.Slice(6)), 251 | BinaryPrimitives.ReadUInt32BigEndian(span.Slice(10))) 252 | 253 | let reader = Reader.ofArray input () 254 | let result = p reader 255 | 256 | match result with 257 | | Ok result -> 258 | "" |> Expect.equal result.Parsed expected 259 | "" |> Expect.equal reader.Index 14L 260 | | Error e -> failwithf "%A" e 261 | } 262 | 263 | test "Composite Read Evens" { 264 | let input = [| yield! evens; yield! evens |] 265 | 266 | let p = tuple5 pFloat32BE pUInt32LE pInt32BE pFloat64LE pUInt64BE 267 | 268 | let expected = 269 | let span = input.AsSpan() 270 | 271 | struct (BinaryPrimitives.ReadSingleBigEndian span, 272 | BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4)), 273 | BinaryPrimitives.ReadInt32BigEndian(span.Slice(8)), 274 | BinaryPrimitives.ReadDoubleLittleEndian(span.Slice(12)), 275 | BinaryPrimitives.ReadUInt64BigEndian(span.Slice(20))) 276 | 277 | let reader = Reader.ofArray input () 278 | let result = p reader 279 | 280 | match result with 281 | | Ok result -> 282 | "" |> Expect.equal result.Parsed expected 283 | "" |> Expect.equal reader.Index 28L 284 | | Error e -> failwithf "%A" e 285 | } 286 | ] 287 | #endif 288 | -------------------------------------------------------------------------------- /test/XParsec.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | open Fable.Pyxpecto 4 | #if FABLE_COMPILER 5 | #else 6 | open Expecto 7 | #endif 8 | 9 | // This is possibly the most magic used to make this work. 10 | // Js and ts cannot use `Async.RunSynchronously`, instead they use `Async.StartAsPromise`. 11 | // Here we need the transpiler not to worry about the output type. 12 | #if FABLE_COMPILER && !FABLE_COMPILER_JAVASCRIPT && !FABLE_COMPILER_TYPESCRIPT 13 | let (!!) (any: 'a) = any 14 | #endif 15 | #if FABLE_COMPILER && FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT 16 | open Fable.Core.JsInterop 17 | #endif 18 | 19 | [] 20 | let main argv = 21 | #if FABLE_COMPILER 22 | let all = 23 | testList 24 | "XParsec.Tests" 25 | [ 26 | ParserTests.tests 27 | CombinatorTests.tests 28 | OperatorParsingTests.tests 29 | OperatorParsingTests.tests2 30 | OperatorParsingTests.tests3 31 | ErrorFormattingTests.tests 32 | ] 33 | 34 | 35 | !! Pyxpecto.runTests [||] all 36 | #else 37 | Tests.runTestsInAssemblyWithCLIArgs [] argv 38 | #endif 39 | -------------------------------------------------------------------------------- /test/XParsec.Tests/ParserTests.fs: -------------------------------------------------------------------------------- 1 | module ParserTests 2 | 3 | #if FABLE_COMPILER 4 | open Fable.Pyxpecto 5 | #else 6 | open Expecto 7 | #endif 8 | 9 | open XParsec 10 | open XParsec.Parsers 11 | 12 | #if !FABLE_COMPILER 13 | [] 14 | #endif 15 | let tests = 16 | testList 17 | "ParserTests" 18 | [ 19 | test "PReturn" { 20 | let input = "input" 21 | 22 | let p = preturn true 23 | let reader = Reader.ofString input () 24 | let result = p reader 25 | 26 | match result with 27 | | Ok result -> 28 | "" |> Expect.isTrue result.Parsed 29 | "" |> Expect.equal reader.Index 0L 30 | | Error e -> failwithf "%A" e 31 | } 32 | 33 | test "PZero" { 34 | let input = "input" 35 | 36 | let p = pzero 37 | let reader = Reader.ofString input () 38 | let result = p reader 39 | 40 | match result with 41 | | Ok result -> "Parser should fail" |> Expect.isFalse true 42 | | Error e -> 43 | "" |> Expect.equal e.Position.Index 0L 44 | "" |> Expect.equal e.Errors ParseError.zero 45 | "" |> Expect.equal reader.Index 0L 46 | } 47 | 48 | test "UserState" { 49 | let input = "input" 50 | 51 | let p = 52 | parser { 53 | let! state = getUserState 54 | do! setUserState "state" 55 | 56 | do! 57 | updateUserState ( 58 | function 59 | | "state" -> "STATE" 60 | | _ -> failwith "Invalid state" 61 | ) 62 | 63 | do! userStateSatisfies (fun s -> s = "STATE") 64 | return state 65 | } 66 | 67 | let reader = Reader.ofString input "state" 68 | let result = p reader 69 | 70 | match result with 71 | | Ok result -> 72 | "" |> Expect.equal result.Parsed "state" 73 | "" |> Expect.equal reader.Index 0L 74 | "" |> Expect.equal reader.State "STATE" 75 | | Error e -> failwithf "%A" e 76 | } 77 | 78 | test "EoF" { 79 | let input = "" 80 | 81 | let p = eof 82 | let reader = Reader.ofString input () 83 | let result = p reader 84 | 85 | match result with 86 | | Ok result -> "" |> Expect.equal reader.Index 0L 87 | | Error e -> failwithf "%A" e 88 | } 89 | 90 | test "pId" { 91 | let input = "input" 92 | 93 | let p = pid 94 | let reader = Reader.ofString input () 95 | let result = p reader 96 | 97 | match result with 98 | | Ok result -> 99 | "" |> Expect.equal result.Parsed 'i' 100 | "" |> Expect.equal reader.Index 1L 101 | | Error e -> failwithf "%A" e 102 | } 103 | 104 | test "Satisfy" { 105 | let input = "input" 106 | 107 | let p = satisfy (fun c -> c = 'i') 108 | let reader = Reader.ofString input () 109 | let result = p reader 110 | 111 | match result with 112 | | Ok result -> 113 | "" |> Expect.equal result.Parsed 'i' 114 | "" |> Expect.equal reader.Index 1L 115 | | Error e -> failwithf "%A" e 116 | } 117 | 118 | test "SatisfyL" { 119 | let input = "input" 120 | 121 | let p = satisfyL (fun c -> c = 'i') "Expected 'i'" 122 | let reader = Reader.ofString input () 123 | let result = p reader 124 | 125 | match result with 126 | | Ok result -> 127 | "" |> Expect.equal result.Parsed 'i' 128 | "" |> Expect.equal reader.Index 1L 129 | | Error e -> failwithf "%A" e 130 | } 131 | 132 | test "ItemReturn" { 133 | let input = "input" 134 | 135 | let p = itemReturn 'i' true 136 | let reader = Reader.ofString input () 137 | let result = p reader 138 | 139 | match result with 140 | | Ok result -> 141 | "" |> Expect.isTrue result.Parsed 142 | "" |> Expect.equal reader.Index 1L 143 | | Error e -> failwithf "%A" e 144 | } 145 | 146 | test "AnyOf" { 147 | let input = "input" 148 | 149 | let p = anyOf [ 'i'; 'n' ] 150 | let reader = Reader.ofString input () 151 | let result = (p .>>. p) reader 152 | 153 | match result with 154 | | Ok result -> 155 | "" |> Expect.equal result.Parsed ('i', 'n') 156 | "" |> Expect.equal reader.Index 2L 157 | | Error e -> failwithf "%A" e 158 | } 159 | 160 | test "SkipAnyOf" { 161 | let input = "input" 162 | 163 | let p = skipAnyOf [ 'i'; 'n' ] 164 | let reader = Reader.ofString input () 165 | let result = (p .>>. p) reader 166 | 167 | match result with 168 | | Ok result -> 169 | "" |> Expect.equal result.Parsed ((), ()) 170 | "" |> Expect.equal reader.Index 2L 171 | | Error e -> failwithf "%A" e 172 | } 173 | 174 | test "NoneOf" { 175 | let input = "input" 176 | 177 | let p = noneOf "nput" 178 | let reader = Reader.ofString input () 179 | let result = p reader 180 | 181 | match result with 182 | | Ok result -> 183 | "" |> Expect.equal result.Parsed 'i' 184 | "" |> Expect.equal reader.Index 1L 185 | | Error e -> failwithf "%A" e 186 | } 187 | 188 | test "SkipNoneOf" { 189 | let input = "input" 190 | 191 | let p = skipNoneOf "nput" 192 | let reader = Reader.ofString input () 193 | let result = p reader 194 | 195 | match result with 196 | | Ok result -> 197 | "" |> Expect.equal result.Parsed () 198 | "" |> Expect.equal reader.Index 1L 199 | | Error e -> failwithf "%A" e 200 | } 201 | 202 | test "AnyInRange" { 203 | let input = "input" 204 | 205 | let p = anyInRange 'a' 'z' 206 | let reader = Reader.ofString input () 207 | let result = (p .>>. p) reader 208 | 209 | match result with 210 | | Ok result -> 211 | "" |> Expect.equal result.Parsed ('i', 'n') 212 | "" |> Expect.equal reader.Index 2L 213 | | Error e -> failwithf "%A" e 214 | } 215 | 216 | test "SkipAnyInRange" { 217 | let input = "input" 218 | 219 | let p = skipAnyInRange 'a' 'z' 220 | let reader = Reader.ofString input () 221 | let result = (p .>>. p) reader 222 | 223 | match result with 224 | | Ok result -> 225 | "" |> Expect.equal result.Parsed ((), ()) 226 | "" |> Expect.equal reader.Index 2L 227 | | Error e -> failwithf "%A" e 228 | } 229 | 230 | test "PSeq" { 231 | let input = "input" 232 | 233 | let p = pseq "input" 234 | let reader = Reader.ofString input () 235 | let result = p reader 236 | 237 | match result with 238 | | Ok result -> 239 | #if FABLE_COMPILER 240 | for i in 0..4 do 241 | "" |> Expect.equal (result.Parsed[i]) (input[i]) 242 | #else 243 | "" |> Expect.sequenceEqual result.Parsed "input" 244 | #endif 245 | "" |> Expect.equal reader.Index 5L 246 | | Error e -> failwithf "%A" e 247 | } 248 | 249 | test "PSeqReturn" { 250 | let input = "input" 251 | 252 | let p = pseqReturn "input" true 253 | let reader = Reader.ofString input () 254 | let result = p reader 255 | 256 | match result with 257 | | Ok result -> 258 | "" |> Expect.isTrue result.Parsed 259 | "" |> Expect.equal reader.Index 5L 260 | | Error e -> failwithf "%A" e 261 | } 262 | 263 | test "RefParser" { 264 | let input = "input" 265 | 266 | let p = RefParser(fun reader -> pseq "input" reader) 267 | let reader = Reader.ofString input () 268 | let result = p.Parser reader 269 | 270 | match result with 271 | | Ok result -> 272 | #if FABLE_COMPILER 273 | for i in 0..4 do 274 | "" |> Expect.equal (result.Parsed[i]) (input[i]) 275 | #else 276 | "" |> Expect.sequenceEqual result.Parsed "input" 277 | #endif 278 | "" |> Expect.equal reader.Index 5L 279 | | Error e -> failwithf "%A" e 280 | } 281 | ] 282 | -------------------------------------------------------------------------------- /test/XParsec.Tests/XParsec.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net9.0 5 | false 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/XParsec.Tests/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "dependencies": { 4 | "net9.0": { 5 | "Expecto": { 6 | "type": "Direct", 7 | "requested": "[10.2.2, )", 8 | "resolved": "10.2.2", 9 | "contentHash": "Ul1wNnvM4sugoP5toPmYNwOJgj7fb0MhbEtTXOz/Z68oiUeLWRHdt1+KGQaseENbt8fnJfiK3zfef+/2dTL8Jg==", 10 | "dependencies": { 11 | "FSharp.Core": "7.0.200", 12 | "Mono.Cecil": "[0.11.4, 1.0.0)" 13 | } 14 | }, 15 | "Expecto.FsCheck": { 16 | "type": "Direct", 17 | "requested": "[10.2.2, )", 18 | "resolved": "10.2.2", 19 | "contentHash": "ef5jUN+wXrW1c4YnPtaM16CMnlNoSs04quT4lQ7L9MdD1tPjK25DtJaFC7KUZIEf/n5DrXw+K0CZ9VLp2E/QRw==", 20 | "dependencies": { 21 | "Expecto": "10.2.2", 22 | "FsCheck": "[2.16.5, 3.0.0)" 23 | } 24 | }, 25 | "Fable.Pyxpecto": { 26 | "type": "Direct", 27 | "requested": "[1.2.0, )", 28 | "resolved": "1.2.0", 29 | "contentHash": "I6jt1fCXXOipJ9tocdBstBK/LFOXa74e6v1GzeiqyWS+v0Lk4xH4NuD2f9g1todETt7A20ib7hvQigMNKqN0Aw==", 30 | "dependencies": { 31 | "FSharp.Core": "5.0.0", 32 | "Fable.Core": "4.1.0", 33 | "Fable.Python": "4.3.0" 34 | } 35 | }, 36 | "FSharp.Core": { 37 | "type": "Direct", 38 | "requested": "[9.0.201, )", 39 | "resolved": "9.0.201", 40 | "contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw==" 41 | }, 42 | "Microsoft.NET.Test.Sdk": { 43 | "type": "Direct", 44 | "requested": "[17.13.0, )", 45 | "resolved": "17.13.0", 46 | "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", 47 | "dependencies": { 48 | "Microsoft.CodeCoverage": "17.13.0", 49 | "Microsoft.TestPlatform.TestHost": "17.13.0" 50 | } 51 | }, 52 | "YoloDev.Expecto.TestSdk": { 53 | "type": "Direct", 54 | "requested": "[0.15.3, )", 55 | "resolved": "0.15.3", 56 | "contentHash": "fpHXx4+RD8HrLsxg/NhP+uvqIPzFfoLNoPZ3F8Yi+Nv8DPTHqwdnDyPW9MD+036icExUYiv/qc/rOuSfE0TIOA==", 57 | "dependencies": { 58 | "Expecto": "[10.2.2, 11.0.0)", 59 | "FSharp.Core": "7.0.200", 60 | "Microsoft.Testing.Extensions.VSTestBridge": "1.6.2", 61 | "Microsoft.Testing.Platform.MSBuild": "1.6.2" 62 | } 63 | }, 64 | "Fable.Python": { 65 | "type": "Transitive", 66 | "resolved": "4.3.0", 67 | "contentHash": "KT5PI4NyMtVLDcmDkf5SeqwtFjVO17u27xr45qXfYWG12UePHxGjQJoI16OafIzlEQ6cHfAuRljhZGKIlvOJNQ==", 68 | "dependencies": { 69 | "FSharp.Core": "4.7.2", 70 | "Fable.Core": "[4.1.0, 5.0.0)" 71 | } 72 | }, 73 | "FsCheck": { 74 | "type": "Transitive", 75 | "resolved": "2.16.5", 76 | "contentHash": "Tw6Lb/UBqtmUj5porM1rRcAU5xT9NttSlFTaIQ8gn4Fsw2LiZavN/eMAZqSA63laV51t4HRJJAAGeYSWFFxRfA==", 77 | "dependencies": { 78 | "FSharp.Core": "4.2.3" 79 | } 80 | }, 81 | "Microsoft.ApplicationInsights": { 82 | "type": "Transitive", 83 | "resolved": "2.22.0", 84 | "contentHash": "3AOM9bZtku7RQwHyMEY3tQMrHIgjcfRDa6YQpd/QG2LDGvMydSlL9Di+8LLMt7J2RDdfJ7/2jdYv6yHcMJAnNw==", 85 | "dependencies": { 86 | "System.Diagnostics.DiagnosticSource": "5.0.0" 87 | } 88 | }, 89 | "Microsoft.CodeCoverage": { 90 | "type": "Transitive", 91 | "resolved": "17.13.0", 92 | "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" 93 | }, 94 | "Microsoft.Testing.Extensions.Telemetry": { 95 | "type": "Transitive", 96 | "resolved": "1.6.2", 97 | "contentHash": "40oMlQzyey4jOihY0IpUufSoMYeijYgvrtIxuYmuVx1k5xl271XlP0gwD2DwAKnvmmP0cocou531d6/CB3cCIA==", 98 | "dependencies": { 99 | "Microsoft.ApplicationInsights": "2.22.0", 100 | "Microsoft.Testing.Platform": "1.6.2" 101 | } 102 | }, 103 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": { 104 | "type": "Transitive", 105 | "resolved": "1.6.2", 106 | "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", 107 | "dependencies": { 108 | "Microsoft.Testing.Platform": "1.6.2" 109 | } 110 | }, 111 | "Microsoft.Testing.Extensions.VSTestBridge": { 112 | "type": "Transitive", 113 | "resolved": "1.6.2", 114 | "contentHash": "ZvYa+VDuk39EIqyOZ/IMFSRd/N54zFBnDFmDagFBJt21vZZnSG6l/3CkJX3DvmYmuf5Byj9w7Xf46mkWuur4LQ==", 115 | "dependencies": { 116 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 117 | "Microsoft.Testing.Extensions.Telemetry": "1.6.2", 118 | "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", 119 | "Microsoft.Testing.Platform": "1.6.2" 120 | } 121 | }, 122 | "Microsoft.Testing.Platform": { 123 | "type": "Transitive", 124 | "resolved": "1.6.2", 125 | "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" 126 | }, 127 | "Microsoft.Testing.Platform.MSBuild": { 128 | "type": "Transitive", 129 | "resolved": "1.6.2", 130 | "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", 131 | "dependencies": { 132 | "Microsoft.Testing.Platform": "1.6.2" 133 | } 134 | }, 135 | "Microsoft.TestPlatform.ObjectModel": { 136 | "type": "Transitive", 137 | "resolved": "17.13.0", 138 | "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", 139 | "dependencies": { 140 | "System.Reflection.Metadata": "1.6.0" 141 | } 142 | }, 143 | "Microsoft.TestPlatform.TestHost": { 144 | "type": "Transitive", 145 | "resolved": "17.13.0", 146 | "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", 147 | "dependencies": { 148 | "Microsoft.TestPlatform.ObjectModel": "17.13.0", 149 | "Newtonsoft.Json": "13.0.1" 150 | } 151 | }, 152 | "Mono.Cecil": { 153 | "type": "Transitive", 154 | "resolved": "0.11.4", 155 | "contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ==" 156 | }, 157 | "Newtonsoft.Json": { 158 | "type": "Transitive", 159 | "resolved": "13.0.1", 160 | "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" 161 | }, 162 | "System.Diagnostics.DiagnosticSource": { 163 | "type": "Transitive", 164 | "resolved": "5.0.0", 165 | "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" 166 | }, 167 | "System.Reflection.Metadata": { 168 | "type": "Transitive", 169 | "resolved": "1.6.0", 170 | "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" 171 | }, 172 | "xparsec": { 173 | "type": "Project", 174 | "dependencies": { 175 | "FSharp.Core": "[9.0.201, )", 176 | "System.Collections.Immutable": "[9.0.3, )" 177 | } 178 | }, 179 | "Fable.Core": { 180 | "type": "CentralTransitive", 181 | "requested": "[4.5.0, )", 182 | "resolved": "4.5.0", 183 | "contentHash": "ZcX8XN5sQZGCbrS8VYnsa/ynhrCBfpDyqDkTl33GTXOpgfKibxoq0+W0hCSbRzuukVNoLtqGL/B6+8yTNDXbNA==" 184 | }, 185 | "System.Collections.Immutable": { 186 | "type": "CentralTransitive", 187 | "requested": "[9.0.3, )", 188 | "resolved": "9.0.3", 189 | "contentHash": "CfYi30Pbpw9aGpXVLcoGt+yM0egrE8M1DUOOwdAWOAQ2QZkXkSgjonqZYvtKdxUF1XJaLCp0OdTXueeAeUHSlQ==" 190 | } 191 | } 192 | } 193 | } --------------------------------------------------------------------------------