From 94d6c7f10c787927faacae179365815e57b82889 Mon Sep 17 00:00:00 2001 From: rameel Date: Sun, 20 Sep 2026 21:39:08 +0500 Subject: [PATCH] repository: normalize line endings with Git attributes --- .editorconfig | 148 +- .gitattributes | 1 + .github/workflows/publish.yml | 80 +- .github/workflows/test.yml | 116 +- .gitignore | 74 +- Directory.Build.props | 10 +- Directory.Packages.props | 32 +- LICENSE | 18 +- README.md | 522 ++-- benchmarks/Data/medium.json | 634 ++--- benchmarks/Data/small.json | 92 +- benchmarks/EmailBenchmark.cs | 124 +- benchmarks/ExpressionBenchmark.cs | 198 +- benchmarks/JsonBenchmark.cs | 120 +- benchmarks/LiteralsBenchmark.cs | 92 +- benchmarks/Parsers/ParlotParsers.cs | 298 +- benchmarks/Parsers/PidginParsers.cs | 298 +- benchmarks/Parsers/RamstackParsers.cs | 50 +- benchmarks/Program.cs | 6 +- benchmarks/Ramstack.Parsing.Benchmarks.csproj | 72 +- samples/CalcExpr/ExpressionParser.cs | 98 +- samples/CalcExpr/README.md | 86 +- samples/Json/JsonParser.cs | 120 +- samples/Json/README.md | 62 +- samples/Samples.csproj | 30 +- samples/TinyC/Node.cs | 278 +- samples/TinyC/README.md | 290 +- samples/TinyC/TinyCParser.cs | 516 ++-- samples/TinyC/examples/example1.c | 42 +- samples/TinyC/examples/example2.c | 22 +- samples/TinyC/examples/example3.c | 52 +- samples/TinyC/examples/example4.c | 62 +- samples/Utilities/IndentedStringBuilder.cs | 152 +- src/Ramstack.Parsing/Argument.cs | 604 ++-- src/Ramstack.Parsing/Character.cs | 174 +- .../Collections/ArrayBuilder.cs | 426 +-- .../Collections/ArrayBuilderDebugView.cs | 28 +- src/Ramstack.Parsing/DeferredParser.cs | 100 +- src/Ramstack.Parsing/DiagnosticState.cs | 34 +- src/Ramstack.Parsing/ErrorSet.cs | 178 +- src/Ramstack.Parsing/FatalErrorException.cs | 14 +- .../GeneralUnicodeCategory.cs | 564 ++-- src/Ramstack.Parsing/ICharClassSupport.cs | 30 +- src/Ramstack.Parsing/Literal.Char.cs | 48 +- .../Literal.EscapeSequence.cs | 398 +-- src/Ramstack.Parsing/Literal.Number.cs | 652 ++--- src/Ramstack.Parsing/Literal.String.cs | 412 +-- .../Literal.UnicodeEscapeSequence.cs | 214 +- src/Ramstack.Parsing/Literal.cs | 12 +- src/Ramstack.Parsing/MapFunc.cs | 56 +- src/Ramstack.Parsing/Match.cs | 202 +- src/Ramstack.Parsing/NumberKind.cs | 76 +- src/Ramstack.Parsing/OptionalValue.cs | 60 +- src/Ramstack.Parsing/ParseContext.cs | 568 ++-- src/Ramstack.Parsing/ParseResult.cs | 78 +- src/Ramstack.Parsing/Parser.And.cs | 118 +- src/Ramstack.Parsing/Parser.Any.cs | 114 +- src/Ramstack.Parsing/Parser.As.cs | 110 +- src/Ramstack.Parsing/Parser.Between.cs | 180 +- src/Ramstack.Parsing/Parser.Char.cs | 176 +- src/Ramstack.Parsing/Parser.Choice.cs | 448 +-- src/Ramstack.Parsing/Parser.DefaultOnFail.cs | 156 +- src/Ramstack.Parsing/Parser.Deferred.cs | 50 +- src/Ramstack.Parsing/Parser.Do.Generated.cs | 822 +++--- src/Ramstack.Parsing/Parser.Do.cs | 104 +- src/Ramstack.Parsing/Parser.Do.tt | 140 +- src/Ramstack.Parsing/Parser.Eof.cs | 76 +- src/Ramstack.Parsing/Parser.Eol.cs | 116 +- src/Ramstack.Parsing/Parser.Error.cs | 84 +- src/Ramstack.Parsing/Parser.Fail.cs | 94 +- src/Ramstack.Parsing/Parser.Fold.cs | 340 +-- src/Ramstack.Parsing/Parser.Map.cs | 202 +- src/Ramstack.Parsing/Parser.Not.cs | 34 +- src/Ramstack.Parsing/Parser.Optional.cs | 130 +- src/Ramstack.Parsing/Parser.Or.cs | 36 +- src/Ramstack.Parsing/Parser.Regex.cs | 160 +- src/Ramstack.Parsing/Parser.Repeat.cs | 1204 ++++---- src/Ramstack.Parsing/Parser.Return.cs | 84 +- src/Ramstack.Parsing/Parser.S.cs | 98 +- src/Ramstack.Parsing/Parser.Separated.cs | 328 +-- src/Ramstack.Parsing/Parser.Seq.Generated.cs | 2496 ++++++++--------- src/Ramstack.Parsing/Parser.Seq.tt | 276 +- src/Ramstack.Parsing/Parser.Set.cs | 1656 +++++------ src/Ramstack.Parsing/Parser.String.cs | 778 ++--- src/Ramstack.Parsing/Parser.Text.cs | 30 +- src/Ramstack.Parsing/Parser.Then.cs | 102 +- src/Ramstack.Parsing/Parser.ThenIgnore.cs | 114 +- .../Parser.UnicodeCategory.cs | 72 +- src/Ramstack.Parsing/Parser.Until.cs | 280 +- src/Ramstack.Parsing/Parser.Void.cs | 76 +- src/Ramstack.Parsing/Parser.cs | 12 +- src/Ramstack.Parsing/Parser`1.cs | 270 +- src/Ramstack.Parsing/PositionBookmark.cs | 40 +- .../Properties/AssemblyInfo.cs | 2 +- .../Properties/Autogenerated.ttinclude | 114 +- .../Properties/Compatibility.cs | 176 +- src/Ramstack.Parsing/Ramstack.Parsing.csproj | 196 +- src/Ramstack.Parsing/ThrowHelper.cs | 24 +- src/Ramstack.Parsing/Unit.cs | 38 +- src/Ramstack.Parsing/Utilities/BitVector`1.cs | 178 +- src/Ramstack.Parsing/Utilities/CharClass.cs | 648 ++--- .../Utilities/CharClassElement.cs | 276 +- .../Utilities/CharClassExtensions.cs | 802 +++--- .../Utilities/CharClassParser.cs | 440 +-- .../Utilities/CharClassRange.cs | 386 +-- .../Utilities/CharClassUnicodeCategory.cs | 336 +-- src/Ramstack.Parsing/Utilities/CharMap.cs | 136 +- src/Ramstack.Parsing/Utilities/ListFactory.cs | 136 +- .../Utilities/StringBuffer.cs | 386 +-- src/Ramstack.Parsing/Utilities/TextHelper.cs | 88 +- .../Ramstack.Parsing.Tests/CharacterTests.cs | 58 +- .../Collections/ArrayBuilderTests.cs | 216 +- .../LiteralTests.Char.cs | 62 +- .../LiteralTests.EscapeSequence.cs | 48 +- .../LiteralTests.Number.cs | 286 +- .../LiteralTests.String.cs | 124 +- .../LiteralTests.UnicodeEscapeSequence.cs | 96 +- tests/Ramstack.Parsing.Tests/LiteralTests.cs | 8 +- .../ParsersTests.And.cs | 36 +- .../ParsersTests.Any.cs | 38 +- .../Ramstack.Parsing.Tests/ParsersTests.As.cs | 28 +- .../ParsersTests.Between.cs | 86 +- .../ParsersTests.Char.cs | 100 +- .../ParsersTests.Choice.cs | 100 +- .../ParsersTests.Choice_Opt.cs | 306 +- .../ParsersTests.DefaultOnFail.cs | 76 +- .../ParsersTests.Eof.cs | 40 +- .../ParsersTests.Eol.cs | 56 +- .../ParsersTests.Fail.cs | 30 +- .../ParsersTests.Fold.cs | 454 +-- .../ParsersTests.Not.cs | 36 +- .../ParsersTests.Optional.cs | 42 +- .../ParsersTests.Regex.cs | 76 +- .../ParsersTests.Repeat.cs | 160 +- .../ParsersTests.Repeat_Opt.cs | 454 +-- .../Ramstack.Parsing.Tests/ParsersTests.S.cs | 44 +- .../ParsersTests.Separated.cs | 672 ++--- .../ParsersTests.Seq.cs | 588 ++-- .../ParsersTests.Set_Opt.cs | 436 +-- .../ParsersTests.String.cs | 270 +- .../ParsersTests.Then.cs | 38 +- .../ParsersTests.ThenIgnore.cs | 38 +- .../ParsersTests.UnicodeCategory.cs | 90 +- .../ParsersTests.Until.cs | 40 +- tests/Ramstack.Parsing.Tests/ParsersTests.cs | 40 +- .../Ramstack.Parsing.Tests.csproj | 70 +- .../Scenarios/ComplexTests.cs | 166 +- .../Scenarios/JsonTests.cs | 320 +-- .../Scenarios/SimpleCalcTests.cs | 232 +- .../SimdConfigurationTests.cs | 58 +- .../Utilities/CharClassParserTests.cs | 194 +- .../Utilities/StringBufferTests.cs | 190 +- 152 files changed, 16097 insertions(+), 16096 deletions(-) create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig index 1a2db97..6236ee8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,74 +1,74 @@ -root = true - -[*] -charset = utf-8 -indent_size = 4 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.{yml,props,targets,csproj}] -indent_size = 2 - -# IDE0079: Remove unnecessary suppression -dotnet_remove_unnecessary_suppression_exclusions = category:ReSharper - -# IDE0079: Remove unnecessary suppression -# dotnet_diagnostic.IDE0079.severity = none - -# CA2255: The 'ModuleInitializer' attribute should not be used in libraries -dotnet_diagnostic.CA2255.severity = none - -# CS8600: Converting null literal or possible null value to non-nullable type. -dotnet_diagnostic.CS8600.severity = error - -# CS8601: Possible null reference assignment. -dotnet_diagnostic.CS8601.severity = error - -# CS8602: Dereference of a possibly null reference. -dotnet_diagnostic.CS8602.severity = error - -# CS8603: Possible null reference return. -dotnet_diagnostic.CS8603.severity = error - -# CS8604: Possible null reference argument. -dotnet_diagnostic.CS8604.severity = error - -# CS8610: Nullability of reference types in type of parameter doesn't match overridden member. -dotnet_diagnostic.CS8610.severity = error - -# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member. -dotnet_diagnostic.CS8613.severity = error - -# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member. -dotnet_diagnostic.CS8614.severity = error - -# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. -dotnet_diagnostic.CS8618.severity = error - -# CS8619: Nullability of reference types in value doesn't match target type. -dotnet_diagnostic.CS8619.severity = error - -# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types. -dotnet_diagnostic.CS8620.severity = error - -# CS8625: Cannot convert null literal to non-nullable reference type. -dotnet_diagnostic.CS8625.severity = error - -# CS8634: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. -dotnet_diagnostic.CS8634.severity = error - -# CS8643: Nullability of reference types in explicit interface specifier doesn't match interface implemented by the type. -dotnet_diagnostic.CS8643.severity = error - -# CS8653: A default expression introduces a null value for a type parameter. -dotnet_diagnostic.CS8653.severity = error - -# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. -dotnet_diagnostic.CS8714.severity = error - -# IDE0059: Unnecessary assigment of a value -dotnet_diagnostic.IDE0059.severity = none - -# IDE0060: Remove unused parameter -dotnet_diagnostic.IDE0060.severity = none +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{yml,props,targets,csproj}] +indent_size = 2 + +# IDE0079: Remove unnecessary suppression +dotnet_remove_unnecessary_suppression_exclusions = category:ReSharper + +# IDE0079: Remove unnecessary suppression +# dotnet_diagnostic.IDE0079.severity = none + +# CA2255: The 'ModuleInitializer' attribute should not be used in libraries +dotnet_diagnostic.CA2255.severity = none + +# CS8600: Converting null literal or possible null value to non-nullable type. +dotnet_diagnostic.CS8600.severity = error + +# CS8601: Possible null reference assignment. +dotnet_diagnostic.CS8601.severity = error + +# CS8602: Dereference of a possibly null reference. +dotnet_diagnostic.CS8602.severity = error + +# CS8603: Possible null reference return. +dotnet_diagnostic.CS8603.severity = error + +# CS8604: Possible null reference argument. +dotnet_diagnostic.CS8604.severity = error + +# CS8610: Nullability of reference types in type of parameter doesn't match overridden member. +dotnet_diagnostic.CS8610.severity = error + +# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member. +dotnet_diagnostic.CS8613.severity = error + +# CS8614: Nullability of reference types in type of parameter doesn't match implicitly implemented member. +dotnet_diagnostic.CS8614.severity = error + +# CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. +dotnet_diagnostic.CS8618.severity = error + +# CS8619: Nullability of reference types in value doesn't match target type. +dotnet_diagnostic.CS8619.severity = error + +# CS8620: Argument cannot be used for parameter due to differences in the nullability of reference types. +dotnet_diagnostic.CS8620.severity = error + +# CS8625: Cannot convert null literal to non-nullable reference type. +dotnet_diagnostic.CS8625.severity = error + +# CS8634: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +dotnet_diagnostic.CS8634.severity = error + +# CS8643: Nullability of reference types in explicit interface specifier doesn't match interface implemented by the type. +dotnet_diagnostic.CS8643.severity = error + +# CS8653: A default expression introduces a null value for a type parameter. +dotnet_diagnostic.CS8653.severity = error + +# CS8714: The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'notnull' constraint. +dotnet_diagnostic.CS8714.severity = error + +# IDE0059: Unnecessary assigment of a value +dotnet_diagnostic.IDE0059.severity = none + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 88a7708..d0da5db 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,40 +1,40 @@ -name: Publish - -on: - push: - tags: - - "[0-9]+.[0-9]+.[0-9]+" - - "[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+" - -permissions: - contents: write - -jobs: - publish: - if: github.repository == 'rameel/ramstack.parsing' - name: Publish packages - runs-on: ubuntu-latest - - steps: - - name: Install .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 9.0.x - - - name: Checkout - uses: actions/checkout@v4 - - - name: Build Solution - run: dotnet build -c Release -p:SkipInternalsVisibleTo=true - - - name: Create NuGet Packages - run: dotnet pack -c Release -o ./nuget --no-build - - - name: Publish NuGet Packages - run: dotnet nuget push ./nuget/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate -s https://api.nuget.org/v3/index.json - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: true - files: nuget/* +name: Publish + +on: + push: + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+" + +permissions: + contents: write + +jobs: + publish: + if: github.repository == 'rameel/ramstack.parsing' + name: Publish packages + runs-on: ubuntu-latest + + steps: + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Checkout + uses: actions/checkout@v4 + + - name: Build Solution + run: dotnet build -c Release -p:SkipInternalsVisibleTo=true + + - name: Create NuGet Packages + run: dotnet pack -c Release -o ./nuget --no-build + + - name: Publish NuGet Packages + run: dotnet nuget push ./nuget/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate -s https://api.nuget.org/v3/index.json + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: nuget/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e86448d..c9294e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,58 +1,58 @@ -name: Build & Test - -on: - pull_request: - branches: [main] - -jobs: - build-and-test: - name: build-and-test-${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, ubuntu-24.04-arm] - - steps: - - name: Install .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 6.x - 7.x - 8.x - 9.x - - - name: Checkout - uses: actions/checkout@v4 - - - name: Build (Debug) - run: dotnet build -c Debug - - - name: Build (Release) - run: dotnet build -c Release - - - name: Test (Debug) - run: dotnet test -c Debug --no-build - - - name: Test (Release) - run: dotnet test -c Release --no-build - - - name: Test (Debug, AVX2=0) - env: - DOTNET_EnableAVX2: "0" - run: dotnet test -c Debug --no-build - - - name: Test (Release, AVX2=0) - env: - DOTNET_EnableAVX2: "0" - run: dotnet test -c Release --no-build - - - name: Test (Debug, HWIntrinsic=0) - env: - DOTNET_EnableHWIntrinsic: "0" - run: dotnet test -c Debug --no-build - - - name: Test (Release, HWIntrinsic=0) - env: - DOTNET_EnableHWIntrinsic: "0" - run: dotnet test -c Release --no-build +name: Build & Test + +on: + pull_request: + branches: [main] + +jobs: + build-and-test: + name: build-and-test-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm] + + steps: + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.x + 7.x + 8.x + 9.x + + - name: Checkout + uses: actions/checkout@v4 + + - name: Build (Debug) + run: dotnet build -c Debug + + - name: Build (Release) + run: dotnet build -c Release + + - name: Test (Debug) + run: dotnet test -c Debug --no-build + + - name: Test (Release) + run: dotnet test -c Release --no-build + + - name: Test (Debug, AVX2=0) + env: + DOTNET_EnableAVX2: "0" + run: dotnet test -c Debug --no-build + + - name: Test (Release, AVX2=0) + env: + DOTNET_EnableAVX2: "0" + run: dotnet test -c Release --no-build + + - name: Test (Debug, HWIntrinsic=0) + env: + DOTNET_EnableHWIntrinsic: "0" + run: dotnet test -c Debug --no-build + + - name: Test (Release, HWIntrinsic=0) + env: + DOTNET_EnableHWIntrinsic: "0" + run: dotnet test -c Release --no-build diff --git a/.gitignore b/.gitignore index 4f81b1b..bd6adb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,37 @@ -#Ignore thumbnails created by windows -[Tt]humbs.db - -#Ignore files build by Visual Studio -*.obj -*.exe -*.pdb -*.user -*.patch -*.aps -*.pch -*.vspscc -*_i.c -*_p.c -*.ncb -*.suo -*.tlb -*.tlh -*.bak -*.cache -*.ilk -*.log -*.lib -*.sbr -*.cs.ide -[Dd]ebug*/ -[Rr]elease*/ -bin/ -obj/ -.vs/ -.vscode/ -*.ReSharper -*.ReSharper.user -[Tt]est[Rr]esult* - -#Ignore Rider/Idea files -.idea/ +#Ignore thumbnails created by windows +[Tt]humbs.db + +#Ignore files build by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.patch +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +*.lib +*.sbr +*.cs.ide +[Dd]ebug*/ +[Rr]elease*/ +bin/ +obj/ +.vs/ +.vscode/ +*.ReSharper +*.ReSharper.user +[Tt]est[Rr]esult* + +#Ignore Rider/Idea files +.idea/ diff --git a/Directory.Build.props b/Directory.Build.props index bf61882..ed14ea7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - - - true - - + + + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props index b706e27..c8fe71c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,17 +1,17 @@ - - - - true - - - - - - - - - - - - + + + + true + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 6666765..92e3431 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ -MIT License - -Copyright 2024 Rameel (https://github.com/rameel) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +MIT License + +Copyright 2024 Rameel (https://github.com/rameel) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 6e4e25f..5413317 100644 --- a/README.md +++ b/README.md @@ -1,261 +1,261 @@ -# Ramstack.Parsing -[![NuGet](https://img.shields.io/nuget/v/Ramstack.Parsing.svg)](https://nuget.org/packages/Ramstack.Parsing) -[![MIT](https://img.shields.io/github/license/rameel/ramstack.parsing)](https://github.com/rameel/ramstack.parsing/blob/main/LICENSE) - -A fast, lightweight parser combinator library for .NET. - -## Getting Started - -To add the `Ramstack.Parsing` [NuGet package](https://www.nuget.org/packages/Ramstack.Parsing) to your project, run: -```shell -dotnet add package Ramstack.Parsing -``` - -## Usage - -Here's how to define a simple expression parser that parses and evaluates mathematical expressions in one step: - -```csharp -public static Parser Calc { get; } = CreateParser(); - -private static Parser CreateParser() -{ - // Grammar: - // ---------------------------------------- - // Start : S Sum $ - // Sum : Product ([+-] S Product)* - // Product : Unary ([*/] S Unary)* - // Unary : '-'? S Primary - // Primary : Parenthesis / Value - // Parenthesis : '(' S Sum ')' S - // Value : Number S - // S : [ \r\n\t]* - - var sum = Deferred(); - var value = Literal.Number().ThenIgnore(S); - - var parenthesis = sum.Between( - Seq(L('('), S), - Seq(L(')'), S) - ); - - var primary = parenthesis.Or(value); - - var unary = Seq( - L('-').Optional(), - S, - primary - ).Do((u, _, d) => u.HasValue ? -d : d); - - var product = unary.Fold( - OneOf("*/").ThenIgnore(S), - (l, r, op) => op == '*' ? l * r : l / r); - - sum.Parser = product.Fold( - OneOf("+-").ThenIgnore(S), - (l, r, op) => op == '+' ? l + r : l - r); - - return sum.Between(S, Eof); -} -``` - -Use the parser as follows: - -```csharp -var result = Calc.Parse(expression); -if (result.Success) -{ - var value = result.Value; - ... -} -else -{ - Console.WriteLine(result.ErrorMessage); - - // - // result.ToString() returns the parsed value as a string, - // or an error message if parsing fails. - // - // Console.WriteLine(result); - // -} -``` - -The parser provides detailed error messages when the input does not conform to the specified grammar. -For example, when parsing the expression `"1 + ("`, the parser expects `'-'`, `'('`, or a `number` after the opening parenthesis. -The error message clearly indicates this: - -``` -(1:6) Expected '-', '(' or number -``` - -In addition to the main `Parse` method, there is also a lightweight `bool TryParse(ReadOnlySpan source, out T value)` method. -This method suppresses diagnostic messages, returning only the parsing success status and the parsed value if successful. - -If detailed error messages are not required, using `TryParse` is the preferred choice, as it reduces memory allocations -and slightly improves performance by avoiding the overhead associated with collecting diagnostic data. - -## Documentation - -- Documentation can be found in [docs/documentation.md](docs/documentation.md) (Work in Progress). -- Additional usage samples, see the [samples/Samples](samples) directory. - -## Performance - -`Ramstack.Parsing` stands out as an exceptionally fast and efficient parsing library, outperforming many alternatives in both speed and memory usage. - -### Benchmarks: Regex - -Here's a comparison between a regex-based email matcher and an equivalent parser combinator implementation: - -**Regular Expression:** -```csharp -[GeneratedRegex(@"^[\w.+-]+@[\w-]+\.\w{2,}$")] -private static partial Regex EmailRegexGenerated(); -``` - -**Parser**: -```csharp -var parser = Seq( - Choice(LetterOrDigit, OneOf("_-+.")).OneOrMore(), - L('@'), - Choice(LetterOrDigit, L('-')).OneOrMore(), - L('.'), - LetterOrDigit.AtLeast(2) -).Text(); -``` -The library supports nearly full character class definitions from regular expressions, allowing grammars to be written in a similar way -as it written for regular expression. Optimizations ensure that both definitions are internally normalized to an equivalent representation: -```csharp -var parser = Seq( - Set("\\w+.-").OneOrMore(), - L('@'), - Set("\\w-").OneOrMore(), - L('.'), - Set("\\w").AtLeast(2) -).Text(); -``` - -The benchmark results show that `Ramstack.Parsing` outperforms even highly optimized Regex implementations, -including compiled and source-generated regexes. - -``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores -.NET SDK 9.0.200-preview.0.25057.12 - [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - - -| Method | Mean | Error | StdDev | Op/s | Gen0 | Allocated | -|---------------- |------------:|---------:|---------:|-------------:|-------:|----------:| -| Ramstack | 34.47 ns | 0.073 ns | 0.065 ns | 29,008,779.8 | - | - | -| Regex | 107.35 ns | 0.125 ns | 0.117 ns | 9,315,658.6 | - | - | -| Regex:Compiled | 44.63 ns | 0.093 ns | 0.083 ns | 22,408,327.6 | - | - | -| Regex:Generated | 41.85 ns | 0.282 ns | 0.250 ns | 23,893,269.4 | - | - | -| Parlot | 295.05 ns | 0.601 ns | 0.533 ns | 3,389,302.6 | 0.0229 | 384 B | -| Parlot:Compiled | 228.98 ns | 0.446 ns | 0.417 ns | 4,367,155.0 | 0.0091 | 152 B | -| Pidgin | 1,602.00 ns | 9.016 ns | 8.434 ns | 624,219.5 | 0.0572 | 976 B | -``` - -In this specific scenario, the library simply checks for a match against the email pattern, similar to what `Regex.IsMatch` does, -without extracting the match result itself. Like with regular expressions, this approach avoids unnecessary memory allocations. - -### Benchmarks: Expressions - -This benchmarks tests 2 expressions: -- **Small**: `2.5 * 4.7 - 8.1 / 3` -- **Large**: `((3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5)) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2)` - -``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores -.NET SDK 9.0.200-preview.0.25057.12 - [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - - -| Method | Mean | Error | StdDev | Op/s | Gen0 | Allocated | -|---------------------- |------------:|----------:|----------:|------------:|-------:|----------:| -| Ramstack:Large | 1,600.2 ns | 9.54 ns | 7.96 ns | 624,925.9 | - | - | -| Ramstack:Diag:Large | 1,699.6 ns | 3.53 ns | 3.13 ns | 588,379.8 | 0.0057 | 104 B | -| Parlot:Large | 3,520.1 ns | 11.08 ns | 10.37 ns | 284,081.7 | 0.1984 | 3344 B | -| Parlot:Compiled:Large | 3,356.7 ns | 7.08 ns | 6.27 ns | 297,910.3 | 0.1984 | 3344 B | -| Pidgin:Large | 39,197.3 ns | 150.13 ns | 140.43 ns | 25,511.9 | 0.2441 | 4464 B | -| | | | | | | | -| Ramstack:Small | 209.5 ns | 0.28 ns | 0.25 ns | 4,772,894.8 | - | - | -| Ramstack:Diag:Small | 260.6 ns | 0.57 ns | 0.50 ns | 3,838,032.3 | 0.0062 | 104 B | -| Parlot:Small | 410.7 ns | 1.46 ns | 1.36 ns | 2,434,653.7 | 0.0391 | 656 B | -| Parlot:Compiled:Small | 385.1 ns | 1.22 ns | 0.95 ns | 2,596,891.9 | 0.0391 | 656 B | -| Pidgin:Small | 4,322.2 ns | 18.28 ns | 17.10 ns | 231,363.9 | 0.0381 | 736 B | -``` - -- `Ramstack` diagnostic messages disabled. -- `Ramstack:Diag` includes diagnostic messages. - -Even with diagnostics enabled, `Ramstack.Parsing` outperforms other popular libraries. - -### Benchmarks: JSON - -This benchmark evaluates JSON parsing performance, including object model construction, across different file sizes. - -- **Small**: 1.16KB -- **Medium**: 11KB -- **Large**: `twitter.json` (617KB) from [serde-rs/json-benchmark](https://github.com/serde-rs/json-benchmark) - -``` -BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) -AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores -.NET SDK 9.0.200-preview.0.25057.12 - [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 - - -| Method | Input | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Gen2 | Allocated | -|----------------- |------- |--------------:|-----------:|-----------:|-----------:|---------:|---------:|---------:|-----------:| -| Ramstack | big | 1,307.929 us | 12.6067 us | 11.7923 us | 764.57 | 152.3438 | 130.8594 | - | 2498.88 KB | -| Parlot | big | 3,102.953 us | 9.3441 us | 8.7405 us | 322.27 | 156.2500 | 132.8125 | - | 2554.06 KB | -| Parlot:Compiled | big | 3,078.931 us | 7.4028 us | 5.7796 us | 324.79 | 156.2500 | 132.8125 | - | 2554.06 KB | -| Pidgin | big | 16,675.788 us | 40.7050 us | 36.0839 us | 59.97 | 156.2500 | 93.7500 | - | 2570.33 KB | -| Newtonsoft.Json | big | 3,878.753 us | 18.3372 us | 16.2554 us | 257.81 | 343.7500 | 320.3125 | - | 5638.63 KB | -| System.Text.Json | big | 1,492.658 us | 10.2347 us | 9.0728 us | 669.95 | 216.7969 | 216.7969 | 216.7969 | 963.67 KB | - -| Ramstack | medium | 20.163 us | 0.0968 us | 0.0906 us | 49,595.14 | 2.6245 | 0.2747 | - | 42.94 KB | -| Parlot | medium | 72.128 us | 0.1193 us | 0.1058 us | 13,864.18 | 2.5635 | 0.2441 | - | 43.09 KB | -| Parlot:Compiled | medium | 70.931 us | 0.2758 us | 0.2445 us | 14,098.14 | 2.5635 | 0.2441 | - | 43.09 KB | -| Pidgin | medium | 266.283 us | 0.4172 us | 0.3698 us | 3,755.41 | 2.4414 | - | - | 44.45 KB | -| Newtonsoft.Json | medium | 52.036 us | 0.4550 us | 0.4257 us | 19,217.47 | 6.0425 | 1.6479 | - | 99.44 KB | -| System.Text.Json | medium | 24.471 us | 0.0968 us | 0.0756 us | 40,865.15 | 1.0376 | - | - | 17.03 KB | - -| Ramstack | small | 3.016 us | 0.0064 us | 0.0053 us | 331,565.28 | 0.3815 | 0.0038 | - | 6.24 KB | -| Parlot | small | 6.723 us | 0.0313 us | 0.0292 us | 148,732.16 | 0.3891 | - | - | 6.39 KB | -| Parlot:Compiled | small | 6.661 us | 0.0210 us | 0.0175 us | 150,120.62 | 0.3891 | - | - | 6.39 KB | -| Pidgin | small | 33.369 us | 0.3909 us | 0.3656 us | 29,968.03 | 0.3662 | - | - | 6.49 KB | -| Newtonsoft.Json | small | 7.940 us | 0.0226 us | 0.0201 us | 125,951.32 | 1.0529 | 0.0458 | - | 17.26 KB | -| System.Text.Json | small | 3.477 us | 0.0133 us | 0.0111 us | 287,598.87 | 0.1373 | - | - | 2.25 KB | -``` - -`Ramstack.Parsing` demonstrated superior performance, outperforming other parser libraries by at least 2-3 times, -and even exceeded specialized JSON parsers such as `Newtonsoft.Json` and `System.Text.Json` in terms of both speed and memory efficiency. - -### Benchmarked Libraries - -1. [x] Parlot 1.3.2 -2. [x] Pidgin 3.3.0 -3. [x] Newtonsoft.Json 13.0.3 -4. [x] System.Text.Json 9.0.0 - -## Supported Versions - -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | - -## Contributions - -Bug reports and contributions are welcome. - -## License - -This package is released under the **MIT License**. -See the [LICENSE](https://github.com/rameel/ramstack.parsing/blob/main/LICENSE) file for details. +# Ramstack.Parsing +[![NuGet](https://img.shields.io/nuget/v/Ramstack.Parsing.svg)](https://nuget.org/packages/Ramstack.Parsing) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.parsing)](https://github.com/rameel/ramstack.parsing/blob/main/LICENSE) + +A fast, lightweight parser combinator library for .NET. + +## Getting Started + +To add the `Ramstack.Parsing` [NuGet package](https://www.nuget.org/packages/Ramstack.Parsing) to your project, run: +```shell +dotnet add package Ramstack.Parsing +``` + +## Usage + +Here's how to define a simple expression parser that parses and evaluates mathematical expressions in one step: + +```csharp +public static Parser Calc { get; } = CreateParser(); + +private static Parser CreateParser() +{ + // Grammar: + // ---------------------------------------- + // Start : S Sum $ + // Sum : Product ([+-] S Product)* + // Product : Unary ([*/] S Unary)* + // Unary : '-'? S Primary + // Primary : Parenthesis / Value + // Parenthesis : '(' S Sum ')' S + // Value : Number S + // S : [ \r\n\t]* + + var sum = Deferred(); + var value = Literal.Number().ThenIgnore(S); + + var parenthesis = sum.Between( + Seq(L('('), S), + Seq(L(')'), S) + ); + + var primary = parenthesis.Or(value); + + var unary = Seq( + L('-').Optional(), + S, + primary + ).Do((u, _, d) => u.HasValue ? -d : d); + + var product = unary.Fold( + OneOf("*/").ThenIgnore(S), + (l, r, op) => op == '*' ? l * r : l / r); + + sum.Parser = product.Fold( + OneOf("+-").ThenIgnore(S), + (l, r, op) => op == '+' ? l + r : l - r); + + return sum.Between(S, Eof); +} +``` + +Use the parser as follows: + +```csharp +var result = Calc.Parse(expression); +if (result.Success) +{ + var value = result.Value; + ... +} +else +{ + Console.WriteLine(result.ErrorMessage); + + // + // result.ToString() returns the parsed value as a string, + // or an error message if parsing fails. + // + // Console.WriteLine(result); + // +} +``` + +The parser provides detailed error messages when the input does not conform to the specified grammar. +For example, when parsing the expression `"1 + ("`, the parser expects `'-'`, `'('`, or a `number` after the opening parenthesis. +The error message clearly indicates this: + +``` +(1:6) Expected '-', '(' or number +``` + +In addition to the main `Parse` method, there is also a lightweight `bool TryParse(ReadOnlySpan source, out T value)` method. +This method suppresses diagnostic messages, returning only the parsing success status and the parsed value if successful. + +If detailed error messages are not required, using `TryParse` is the preferred choice, as it reduces memory allocations +and slightly improves performance by avoiding the overhead associated with collecting diagnostic data. + +## Documentation + +- Documentation can be found in [docs/documentation.md](docs/documentation.md) (Work in Progress). +- Additional usage samples, see the [samples/Samples](samples) directory. + +## Performance + +`Ramstack.Parsing` stands out as an exceptionally fast and efficient parsing library, outperforming many alternatives in both speed and memory usage. + +### Benchmarks: Regex + +Here's a comparison between a regex-based email matcher and an equivalent parser combinator implementation: + +**Regular Expression:** +```csharp +[GeneratedRegex(@"^[\w.+-]+@[\w-]+\.\w{2,}$")] +private static partial Regex EmailRegexGenerated(); +``` + +**Parser**: +```csharp +var parser = Seq( + Choice(LetterOrDigit, OneOf("_-+.")).OneOrMore(), + L('@'), + Choice(LetterOrDigit, L('-')).OneOrMore(), + L('.'), + LetterOrDigit.AtLeast(2) +).Text(); +``` +The library supports nearly full character class definitions from regular expressions, allowing grammars to be written in a similar way +as it written for regular expression. Optimizations ensure that both definitions are internally normalized to an equivalent representation: +```csharp +var parser = Seq( + Set("\\w+.-").OneOrMore(), + L('@'), + Set("\\w-").OneOrMore(), + L('.'), + Set("\\w").AtLeast(2) +).Text(); +``` + +The benchmark results show that `Ramstack.Parsing` outperforms even highly optimized Regex implementations, +including compiled and source-generated regexes. + +``` +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores +.NET SDK 9.0.200-preview.0.25057.12 + [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + + +| Method | Mean | Error | StdDev | Op/s | Gen0 | Allocated | +|---------------- |------------:|---------:|---------:|-------------:|-------:|----------:| +| Ramstack | 34.47 ns | 0.073 ns | 0.065 ns | 29,008,779.8 | - | - | +| Regex | 107.35 ns | 0.125 ns | 0.117 ns | 9,315,658.6 | - | - | +| Regex:Compiled | 44.63 ns | 0.093 ns | 0.083 ns | 22,408,327.6 | - | - | +| Regex:Generated | 41.85 ns | 0.282 ns | 0.250 ns | 23,893,269.4 | - | - | +| Parlot | 295.05 ns | 0.601 ns | 0.533 ns | 3,389,302.6 | 0.0229 | 384 B | +| Parlot:Compiled | 228.98 ns | 0.446 ns | 0.417 ns | 4,367,155.0 | 0.0091 | 152 B | +| Pidgin | 1,602.00 ns | 9.016 ns | 8.434 ns | 624,219.5 | 0.0572 | 976 B | +``` + +In this specific scenario, the library simply checks for a match against the email pattern, similar to what `Regex.IsMatch` does, +without extracting the match result itself. Like with regular expressions, this approach avoids unnecessary memory allocations. + +### Benchmarks: Expressions + +This benchmarks tests 2 expressions: +- **Small**: `2.5 * 4.7 - 8.1 / 3` +- **Large**: `((3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5)) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2)` + +``` +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores +.NET SDK 9.0.200-preview.0.25057.12 + [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + + +| Method | Mean | Error | StdDev | Op/s | Gen0 | Allocated | +|---------------------- |------------:|----------:|----------:|------------:|-------:|----------:| +| Ramstack:Large | 1,600.2 ns | 9.54 ns | 7.96 ns | 624,925.9 | - | - | +| Ramstack:Diag:Large | 1,699.6 ns | 3.53 ns | 3.13 ns | 588,379.8 | 0.0057 | 104 B | +| Parlot:Large | 3,520.1 ns | 11.08 ns | 10.37 ns | 284,081.7 | 0.1984 | 3344 B | +| Parlot:Compiled:Large | 3,356.7 ns | 7.08 ns | 6.27 ns | 297,910.3 | 0.1984 | 3344 B | +| Pidgin:Large | 39,197.3 ns | 150.13 ns | 140.43 ns | 25,511.9 | 0.2441 | 4464 B | +| | | | | | | | +| Ramstack:Small | 209.5 ns | 0.28 ns | 0.25 ns | 4,772,894.8 | - | - | +| Ramstack:Diag:Small | 260.6 ns | 0.57 ns | 0.50 ns | 3,838,032.3 | 0.0062 | 104 B | +| Parlot:Small | 410.7 ns | 1.46 ns | 1.36 ns | 2,434,653.7 | 0.0391 | 656 B | +| Parlot:Compiled:Small | 385.1 ns | 1.22 ns | 0.95 ns | 2,596,891.9 | 0.0391 | 656 B | +| Pidgin:Small | 4,322.2 ns | 18.28 ns | 17.10 ns | 231,363.9 | 0.0381 | 736 B | +``` + +- `Ramstack` diagnostic messages disabled. +- `Ramstack:Diag` includes diagnostic messages. + +Even with diagnostics enabled, `Ramstack.Parsing` outperforms other popular libraries. + +### Benchmarks: JSON + +This benchmark evaluates JSON parsing performance, including object model construction, across different file sizes. + +- **Small**: 1.16KB +- **Medium**: 11KB +- **Large**: `twitter.json` (617KB) from [serde-rs/json-benchmark](https://github.com/serde-rs/json-benchmark) + +``` +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3037) +AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores +.NET SDK 9.0.200-preview.0.25057.12 + [Host] : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.1 (9.0.124.61010), X64 RyuJIT AVX2 + + +| Method | Input | Mean | Error | StdDev | Op/s | Gen0 | Gen1 | Gen2 | Allocated | +|----------------- |------- |--------------:|-----------:|-----------:|-----------:|---------:|---------:|---------:|-----------:| +| Ramstack | big | 1,307.929 us | 12.6067 us | 11.7923 us | 764.57 | 152.3438 | 130.8594 | - | 2498.88 KB | +| Parlot | big | 3,102.953 us | 9.3441 us | 8.7405 us | 322.27 | 156.2500 | 132.8125 | - | 2554.06 KB | +| Parlot:Compiled | big | 3,078.931 us | 7.4028 us | 5.7796 us | 324.79 | 156.2500 | 132.8125 | - | 2554.06 KB | +| Pidgin | big | 16,675.788 us | 40.7050 us | 36.0839 us | 59.97 | 156.2500 | 93.7500 | - | 2570.33 KB | +| Newtonsoft.Json | big | 3,878.753 us | 18.3372 us | 16.2554 us | 257.81 | 343.7500 | 320.3125 | - | 5638.63 KB | +| System.Text.Json | big | 1,492.658 us | 10.2347 us | 9.0728 us | 669.95 | 216.7969 | 216.7969 | 216.7969 | 963.67 KB | + +| Ramstack | medium | 20.163 us | 0.0968 us | 0.0906 us | 49,595.14 | 2.6245 | 0.2747 | - | 42.94 KB | +| Parlot | medium | 72.128 us | 0.1193 us | 0.1058 us | 13,864.18 | 2.5635 | 0.2441 | - | 43.09 KB | +| Parlot:Compiled | medium | 70.931 us | 0.2758 us | 0.2445 us | 14,098.14 | 2.5635 | 0.2441 | - | 43.09 KB | +| Pidgin | medium | 266.283 us | 0.4172 us | 0.3698 us | 3,755.41 | 2.4414 | - | - | 44.45 KB | +| Newtonsoft.Json | medium | 52.036 us | 0.4550 us | 0.4257 us | 19,217.47 | 6.0425 | 1.6479 | - | 99.44 KB | +| System.Text.Json | medium | 24.471 us | 0.0968 us | 0.0756 us | 40,865.15 | 1.0376 | - | - | 17.03 KB | + +| Ramstack | small | 3.016 us | 0.0064 us | 0.0053 us | 331,565.28 | 0.3815 | 0.0038 | - | 6.24 KB | +| Parlot | small | 6.723 us | 0.0313 us | 0.0292 us | 148,732.16 | 0.3891 | - | - | 6.39 KB | +| Parlot:Compiled | small | 6.661 us | 0.0210 us | 0.0175 us | 150,120.62 | 0.3891 | - | - | 6.39 KB | +| Pidgin | small | 33.369 us | 0.3909 us | 0.3656 us | 29,968.03 | 0.3662 | - | - | 6.49 KB | +| Newtonsoft.Json | small | 7.940 us | 0.0226 us | 0.0201 us | 125,951.32 | 1.0529 | 0.0458 | - | 17.26 KB | +| System.Text.Json | small | 3.477 us | 0.0133 us | 0.0111 us | 287,598.87 | 0.1373 | - | - | 2.25 KB | +``` + +`Ramstack.Parsing` demonstrated superior performance, outperforming other parser libraries by at least 2-3 times, +and even exceeded specialized JSON parsers such as `Newtonsoft.Json` and `System.Text.Json` in terms of both speed and memory efficiency. + +### Benchmarked Libraries + +1. [x] Parlot 1.3.2 +2. [x] Pidgin 3.3.0 +3. [x] Newtonsoft.Json 13.0.3 +4. [x] System.Text.Json 9.0.0 + +## Supported Versions + +| | Version | +|------|------------| +| .NET | 6, 7, 8, 9 | + +## Contributions + +Bug reports and contributions are welcome. + +## License + +This package is released under the **MIT License**. +See the [LICENSE](https://github.com/rameel/ramstack.parsing/blob/main/LICENSE) file for details. diff --git a/benchmarks/Data/medium.json b/benchmarks/Data/medium.json index 158d45b..434c145 100644 --- a/benchmarks/Data/medium.json +++ b/benchmarks/Data/medium.json @@ -1,317 +1,317 @@ -[ - { - "id": "6782fb719850cc2a7e8f5687", - "index": 0, - "guid": "39941455-532c-4c27-8952-7f44f7288123", - "isActive": false, - "balance": "$3,257.80", - "picture": "http://placehold.it/32x32", - "age": 22, - "eyeColor": "brown", - "name": "Susanna Kidd", - "gender": "female", - "company": "ISOLOGICS", - "email": "susannakidd@isologics.com", - "phone": "+1 (827) 420-2998", - "address": "723 Lafayette Avenue, Sterling, Arkansas, 790", - "about": "Magna ad minim qui non minim esse et cupidatat elit occaecat eiusmod fugiat dolore. Officia nisi nostrud duis nostrud et ipsum laboris. Commodo laboris ex deserunt sunt ex et mollit eiusmod labore do. Eu ipsum laboris occaecat laborum ex sit occaecat et. Qui est eiusmod labore adipisicing labore tempor do pariatur id dolor reprehenderit cillum. Non veniam incididunt Lorem reprehenderit incididunt. Magna consequat dolor incididunt et non quis ea elit ipsum anim ex magna.", - "registered": "2017-09-23T04:51:42 -06:00", - "latitude": -67.71782, - "longitude": 27.337068, - "tags": [ - "officia", - "irure", - "reprehenderit", - "proident", - "deserunt", - "laborum", - "duis" - ], - "friends": [ - { - "id": 0, - "name": "Fleming Chang" - }, - { - "id": 1, - "name": "Horton Alvarado" - }, - { - "id": 2, - "name": "Rosales Stanley" - } - ], - "greeting": "Hello, Susanna Kidd! You have 8 unread messages.", - "favoriteFruit": "apple" - }, - { - "id": "6782fb71927cb4b18a76c5eb", - "index": 1, - "guid": "fa3bf5db-3caa-43c9-821b-cd5e126384a3", - "isActive": false, - "balance": "$2,378.85", - "picture": "http://placehold.it/32x32", - "age": 36, - "eyeColor": "brown", - "name": "Letha Navarro", - "gender": "female", - "company": "DYMI", - "email": "lethanavarro@dymi.com", - "phone": "+1 (992) 537-3991", - "address": "912 Cadman Plaza, Byrnedale, South Dakota, 1069", - "about": "Sunt esse amet sit ad nulla enim reprehenderit veniam dolore tempor nostrud dolore. Officia laborum id aliqua sint nulla excepteur aliqua. Nisi ad duis ullamco magna ea amet nostrud eu laboris minim quis voluptate occaecat voluptate.", - "registered": "2022-12-28T03:49:31 -05:00", - "latitude": 86.859753, - "longitude": 179.675296, - "tags": [ - "aliqua", - "veniam", - "consequat", - "mollit", - "eu", - "veniam", - "do" - ], - "friends": [ - { - "id": 0, - "name": "Carson Garner" - }, - { - "id": 1, - "name": "Curtis Bird" - }, - { - "id": 2, - "name": "Tisha Robbins" - } - ], - "greeting": "Hello, Letha Navarro! You have 1 unread messages.", - "favoriteFruit": "strawberry" - }, - { - "id": "6782fb716669f6d74a495c08", - "index": 2, - "guid": "5fd0dbb8-790a-4ece-80ca-c7ed3d96d2e7", - "isActive": false, - "balance": "$3,941.05", - "picture": "http://placehold.it/32x32", - "age": 33, - "eyeColor": "blue", - "name": "Kane Solomon", - "gender": "male", - "company": "INTRADISK", - "email": "kanesolomon@intradisk.com", - "phone": "+1 (873) 534-2143", - "address": "275 McKibben Street, Bangor, Federated States Of Micronesia, 4521", - "about": "Consectetur ex irure dolor sunt adipisicing. Commodo do qui laboris magna non minim fugiat commodo. Laborum qui cupidatat exercitation laboris sint velit nisi cupidatat eu ipsum pariatur.", - "registered": "2024-02-07T07:04:09 -05:00", - "latitude": 69.139384, - "longitude": -51.308371, - "tags": [ - "ex", - "ad", - "aliqua", - "consectetur", - "proident", - "mollit", - "et" - ], - "friends": [ - { - "id": 0, - "name": "Nora Pickett" - }, - { - "id": 1, - "name": "Billie Levine" - }, - { - "id": 2, - "name": "Shields Davenport" - } - ], - "greeting": "Hello, Kane Solomon! You have 7 unread messages.", - "favoriteFruit": "banana" - }, - { - "id": "6782fb710d1e6597d6a463f7", - "index": 3, - "guid": "fa0456c4-c686-435e-9a22-9a2b299368f5", - "isActive": true, - "balance": "$1,216.21", - "picture": "http://placehold.it/32x32", - "age": 27, - "eyeColor": "blue", - "name": "Orr Hill", - "gender": "male", - "company": "FURNITECH", - "email": "orrhill@furnitech.com", - "phone": "+1 (823) 455-3555", - "address": "152 Hornell Loop, Cawood, Alaska, 5220", - "about": "Labore dolor sint est culpa Lorem dolore veniam consequat. Officia Lorem eu labore esse esse proident sint consequat Lorem labore esse. In nostrud sint aute commodo ex proident. Nulla labore eiusmod in aliqua in ad. Eiusmod laborum dolor ex est velit incididunt et amet.", - "registered": "2021-08-22T10:59:13 -05:00", - "latitude": 53.896296, - "longitude": -45.046902, - "tags": [ - "labore", - "nisi", - "cupidatat", - "excepteur", - "ex", - "Lorem", - "do" - ], - "friends": [ - { - "id": 0, - "name": "Ophelia Villarreal" - }, - { - "id": 1, - "name": "Velazquez Pena" - }, - { - "id": 2, - "name": "Pena Gaines" - } - ], - "greeting": "Hello, Orr Hill! You have 1 unread messages.", - "favoriteFruit": "apple" - }, - { - "id": "6782fb71eb7d28d8981ff9b1", - "index": 4, - "guid": "167c126b-71c9-4ece-9b1d-83a593e7a703", - "isActive": false, - "balance": "$2,812.13", - "picture": "http://placehold.it/32x32", - "age": 31, - "eyeColor": "brown", - "name": "Francisca Beard", - "gender": "female", - "company": "PHOTOBIN", - "email": "franciscabeard@photobin.com", - "phone": "+1 (817) 443-3427", - "address": "821 Hunterfly Place, Mathews, Iowa, 7047", - "about": "Magna ex Lorem dolore pariatur laboris ex. Fugiat id ad do occaecat sint ullamco commodo aliquip laborum commodo nisi exercitation Lorem occaecat. Quis excepteur laboris Lorem sint labore aute consectetur quis sint. Qui Lorem aute labore deserunt exercitation excepteur id consectetur eiusmod nisi in.", - "registered": "2014-10-04T04:47:30 -06:00", - "latitude": -12.564841, - "longitude": -81.998082, - "tags": [ - "pariatur", - "sint", - "sit", - "anim", - "deserunt", - "quis", - "sunt" - ], - "friends": [ - { - "id": 0, - "name": "Day Wheeler" - }, - { - "id": 1, - "name": "Juana Martin" - }, - { - "id": 2, - "name": "Wells Workman" - } - ], - "greeting": "Hello, Francisca Beard! You have 2 unread messages.", - "favoriteFruit": "apple" - }, - { - "id": "6782fb71accf35c389e2bf14", - "index": 5, - "guid": "760e7769-0e65-4583-b3b8-4dee5401be8c", - "isActive": true, - "balance": "$3,776.56", - "picture": "http://placehold.it/32x32", - "age": 40, - "eyeColor": "blue", - "name": "Valentine Sellers", - "gender": "male", - "company": "EXOSPACE", - "email": "valentinesellers@exospace.com", - "phone": "+1 (918) 428-2199", - "address": "566 Robert Street, Boykin, Utah, 346", - "about": "Commodo sint aute esse aute. Id duis aliquip deserunt incididunt Lorem deserunt velit qui esse cillum. Quis occaecat eiusmod esse laboris pariatur fugiat nisi exercitation enim ex nisi. Aliqua magna cupidatat pariatur proident ipsum labore. Irure commodo qui eu dolore aliqua incididunt cillum nostrud.", - "registered": "2020-03-29T02:44:28 -05:00", - "latitude": 82.01843, - "longitude": 60.297166, - "tags": [ - "nulla", - "quis", - "pariatur", - "esse", - "irure", - "eiusmod", - "nisi" - ], - "friends": [ - { - "id": 0, - "name": "Rosemarie Delaney" - }, - { - "id": 1, - "name": "Sheila Wade" - }, - { - "id": 2, - "name": "Lawson Castillo" - } - ], - "greeting": "Hello, Valentine Sellers! You have 7 unread messages.", - "favoriteFruit": "strawberry" - }, - { - "id": "6782fb71f5a8b906ba29ba8a", - "index": 6, - "guid": "1875e892-73d7-4fd1-b472-4760e5c84a19", - "isActive": true, - "balance": "$1,861.10", - "picture": "http://placehold.it/32x32", - "age": 37, - "eyeColor": "brown", - "name": "Lee Vaughn", - "gender": "male", - "company": "TEMORAK", - "email": "leevaughn@temorak.com", - "phone": "+1 (947) 509-3320", - "address": "429 Overbaugh Place, Baker, South Carolina, 4905", - "about": "Dolore dolor id nisi est elit elit cupidatat velit ut. Labore irure consequat ullamco consequat culpa aliqua voluptate Lorem do. Excepteur ea et mollit voluptate duis ad mollit occaecat nostrud nisi.", - "registered": "2014-12-25T09:54:15 -06:00", - "latitude": -18.166264, - "longitude": -20.595374, - "tags": [ - "id", - "minim", - "velit", - "ex", - "commodo", - "ullamco", - "nostrud" - ], - "friends": [ - { - "id": 0, - "name": "Reese Herring" - }, - { - "id": 1, - "name": "Patel Jarvis" - }, - { - "id": 2, - "name": "Blair Lewis" - } - ], - "greeting": "Hello, Lee Vaughn! You have 4 unread messages.", - "favoriteFruit": "banana" - } -] +[ + { + "id": "6782fb719850cc2a7e8f5687", + "index": 0, + "guid": "39941455-532c-4c27-8952-7f44f7288123", + "isActive": false, + "balance": "$3,257.80", + "picture": "http://placehold.it/32x32", + "age": 22, + "eyeColor": "brown", + "name": "Susanna Kidd", + "gender": "female", + "company": "ISOLOGICS", + "email": "susannakidd@isologics.com", + "phone": "+1 (827) 420-2998", + "address": "723 Lafayette Avenue, Sterling, Arkansas, 790", + "about": "Magna ad minim qui non minim esse et cupidatat elit occaecat eiusmod fugiat dolore. Officia nisi nostrud duis nostrud et ipsum laboris. Commodo laboris ex deserunt sunt ex et mollit eiusmod labore do. Eu ipsum laboris occaecat laborum ex sit occaecat et. Qui est eiusmod labore adipisicing labore tempor do pariatur id dolor reprehenderit cillum. Non veniam incididunt Lorem reprehenderit incididunt. Magna consequat dolor incididunt et non quis ea elit ipsum anim ex magna.", + "registered": "2017-09-23T04:51:42 -06:00", + "latitude": -67.71782, + "longitude": 27.337068, + "tags": [ + "officia", + "irure", + "reprehenderit", + "proident", + "deserunt", + "laborum", + "duis" + ], + "friends": [ + { + "id": 0, + "name": "Fleming Chang" + }, + { + "id": 1, + "name": "Horton Alvarado" + }, + { + "id": 2, + "name": "Rosales Stanley" + } + ], + "greeting": "Hello, Susanna Kidd! You have 8 unread messages.", + "favoriteFruit": "apple" + }, + { + "id": "6782fb71927cb4b18a76c5eb", + "index": 1, + "guid": "fa3bf5db-3caa-43c9-821b-cd5e126384a3", + "isActive": false, + "balance": "$2,378.85", + "picture": "http://placehold.it/32x32", + "age": 36, + "eyeColor": "brown", + "name": "Letha Navarro", + "gender": "female", + "company": "DYMI", + "email": "lethanavarro@dymi.com", + "phone": "+1 (992) 537-3991", + "address": "912 Cadman Plaza, Byrnedale, South Dakota, 1069", + "about": "Sunt esse amet sit ad nulla enim reprehenderit veniam dolore tempor nostrud dolore. Officia laborum id aliqua sint nulla excepteur aliqua. Nisi ad duis ullamco magna ea amet nostrud eu laboris minim quis voluptate occaecat voluptate.", + "registered": "2022-12-28T03:49:31 -05:00", + "latitude": 86.859753, + "longitude": 179.675296, + "tags": [ + "aliqua", + "veniam", + "consequat", + "mollit", + "eu", + "veniam", + "do" + ], + "friends": [ + { + "id": 0, + "name": "Carson Garner" + }, + { + "id": 1, + "name": "Curtis Bird" + }, + { + "id": 2, + "name": "Tisha Robbins" + } + ], + "greeting": "Hello, Letha Navarro! You have 1 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "id": "6782fb716669f6d74a495c08", + "index": 2, + "guid": "5fd0dbb8-790a-4ece-80ca-c7ed3d96d2e7", + "isActive": false, + "balance": "$3,941.05", + "picture": "http://placehold.it/32x32", + "age": 33, + "eyeColor": "blue", + "name": "Kane Solomon", + "gender": "male", + "company": "INTRADISK", + "email": "kanesolomon@intradisk.com", + "phone": "+1 (873) 534-2143", + "address": "275 McKibben Street, Bangor, Federated States Of Micronesia, 4521", + "about": "Consectetur ex irure dolor sunt adipisicing. Commodo do qui laboris magna non minim fugiat commodo. Laborum qui cupidatat exercitation laboris sint velit nisi cupidatat eu ipsum pariatur.", + "registered": "2024-02-07T07:04:09 -05:00", + "latitude": 69.139384, + "longitude": -51.308371, + "tags": [ + "ex", + "ad", + "aliqua", + "consectetur", + "proident", + "mollit", + "et" + ], + "friends": [ + { + "id": 0, + "name": "Nora Pickett" + }, + { + "id": 1, + "name": "Billie Levine" + }, + { + "id": 2, + "name": "Shields Davenport" + } + ], + "greeting": "Hello, Kane Solomon! You have 7 unread messages.", + "favoriteFruit": "banana" + }, + { + "id": "6782fb710d1e6597d6a463f7", + "index": 3, + "guid": "fa0456c4-c686-435e-9a22-9a2b299368f5", + "isActive": true, + "balance": "$1,216.21", + "picture": "http://placehold.it/32x32", + "age": 27, + "eyeColor": "blue", + "name": "Orr Hill", + "gender": "male", + "company": "FURNITECH", + "email": "orrhill@furnitech.com", + "phone": "+1 (823) 455-3555", + "address": "152 Hornell Loop, Cawood, Alaska, 5220", + "about": "Labore dolor sint est culpa Lorem dolore veniam consequat. Officia Lorem eu labore esse esse proident sint consequat Lorem labore esse. In nostrud sint aute commodo ex proident. Nulla labore eiusmod in aliqua in ad. Eiusmod laborum dolor ex est velit incididunt et amet.", + "registered": "2021-08-22T10:59:13 -05:00", + "latitude": 53.896296, + "longitude": -45.046902, + "tags": [ + "labore", + "nisi", + "cupidatat", + "excepteur", + "ex", + "Lorem", + "do" + ], + "friends": [ + { + "id": 0, + "name": "Ophelia Villarreal" + }, + { + "id": 1, + "name": "Velazquez Pena" + }, + { + "id": 2, + "name": "Pena Gaines" + } + ], + "greeting": "Hello, Orr Hill! You have 1 unread messages.", + "favoriteFruit": "apple" + }, + { + "id": "6782fb71eb7d28d8981ff9b1", + "index": 4, + "guid": "167c126b-71c9-4ece-9b1d-83a593e7a703", + "isActive": false, + "balance": "$2,812.13", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "brown", + "name": "Francisca Beard", + "gender": "female", + "company": "PHOTOBIN", + "email": "franciscabeard@photobin.com", + "phone": "+1 (817) 443-3427", + "address": "821 Hunterfly Place, Mathews, Iowa, 7047", + "about": "Magna ex Lorem dolore pariatur laboris ex. Fugiat id ad do occaecat sint ullamco commodo aliquip laborum commodo nisi exercitation Lorem occaecat. Quis excepteur laboris Lorem sint labore aute consectetur quis sint. Qui Lorem aute labore deserunt exercitation excepteur id consectetur eiusmod nisi in.", + "registered": "2014-10-04T04:47:30 -06:00", + "latitude": -12.564841, + "longitude": -81.998082, + "tags": [ + "pariatur", + "sint", + "sit", + "anim", + "deserunt", + "quis", + "sunt" + ], + "friends": [ + { + "id": 0, + "name": "Day Wheeler" + }, + { + "id": 1, + "name": "Juana Martin" + }, + { + "id": 2, + "name": "Wells Workman" + } + ], + "greeting": "Hello, Francisca Beard! You have 2 unread messages.", + "favoriteFruit": "apple" + }, + { + "id": "6782fb71accf35c389e2bf14", + "index": 5, + "guid": "760e7769-0e65-4583-b3b8-4dee5401be8c", + "isActive": true, + "balance": "$3,776.56", + "picture": "http://placehold.it/32x32", + "age": 40, + "eyeColor": "blue", + "name": "Valentine Sellers", + "gender": "male", + "company": "EXOSPACE", + "email": "valentinesellers@exospace.com", + "phone": "+1 (918) 428-2199", + "address": "566 Robert Street, Boykin, Utah, 346", + "about": "Commodo sint aute esse aute. Id duis aliquip deserunt incididunt Lorem deserunt velit qui esse cillum. Quis occaecat eiusmod esse laboris pariatur fugiat nisi exercitation enim ex nisi. Aliqua magna cupidatat pariatur proident ipsum labore. Irure commodo qui eu dolore aliqua incididunt cillum nostrud.", + "registered": "2020-03-29T02:44:28 -05:00", + "latitude": 82.01843, + "longitude": 60.297166, + "tags": [ + "nulla", + "quis", + "pariatur", + "esse", + "irure", + "eiusmod", + "nisi" + ], + "friends": [ + { + "id": 0, + "name": "Rosemarie Delaney" + }, + { + "id": 1, + "name": "Sheila Wade" + }, + { + "id": 2, + "name": "Lawson Castillo" + } + ], + "greeting": "Hello, Valentine Sellers! You have 7 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "id": "6782fb71f5a8b906ba29ba8a", + "index": 6, + "guid": "1875e892-73d7-4fd1-b472-4760e5c84a19", + "isActive": true, + "balance": "$1,861.10", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "brown", + "name": "Lee Vaughn", + "gender": "male", + "company": "TEMORAK", + "email": "leevaughn@temorak.com", + "phone": "+1 (947) 509-3320", + "address": "429 Overbaugh Place, Baker, South Carolina, 4905", + "about": "Dolore dolor id nisi est elit elit cupidatat velit ut. Labore irure consequat ullamco consequat culpa aliqua voluptate Lorem do. Excepteur ea et mollit voluptate duis ad mollit occaecat nostrud nisi.", + "registered": "2014-12-25T09:54:15 -06:00", + "latitude": -18.166264, + "longitude": -20.595374, + "tags": [ + "id", + "minim", + "velit", + "ex", + "commodo", + "ullamco", + "nostrud" + ], + "friends": [ + { + "id": 0, + "name": "Reese Herring" + }, + { + "id": 1, + "name": "Patel Jarvis" + }, + { + "id": 2, + "name": "Blair Lewis" + } + ], + "greeting": "Hello, Lee Vaughn! You have 4 unread messages.", + "favoriteFruit": "banana" + } +] diff --git a/benchmarks/Data/small.json b/benchmarks/Data/small.json index d5c359c..42835f3 100644 --- a/benchmarks/Data/small.json +++ b/benchmarks/Data/small.json @@ -1,46 +1,46 @@ -{ - "order_id": "40b9a8bc-f679-48ef-92f6-0271295c7daa", - "customer": { - "first_name": "Alex", - "last_name": "Johnson", - "email": "alex.johnson@example.com", - "phone": "+1-555-123-4567" - }, - "shipping_address": { - "street": "123 Main Street", - "house": "16", - "apartment": "7", - "city": "New York", - "zip_code": "10001", - "country": "USA" - }, - "items": [ - { - "item_id": "101", - "name": "Smartphone XYZ", - "quantity": 1, - "price": 699.99 - }, - { - "item_id": "202", - "name": "Phone Case XYZ", - "quantity": 1, - "price": 29.99 - }, - { - "item_id": "303", - "name": "Screen Protector XYZ", - "quantity": 2, - "price": 14.99 - } - ], - "payment": { - "method": "credit_card", - "status": "paid", - "transaction_id": "txn_123456789" - }, - "order_total": 759.95, - "status": "shipped", - "created_at": "2024-08-15T14:45:00Z", - "shipped_at": "2024-08-16T10:00:00Z" -} +{ + "order_id": "40b9a8bc-f679-48ef-92f6-0271295c7daa", + "customer": { + "first_name": "Alex", + "last_name": "Johnson", + "email": "alex.johnson@example.com", + "phone": "+1-555-123-4567" + }, + "shipping_address": { + "street": "123 Main Street", + "house": "16", + "apartment": "7", + "city": "New York", + "zip_code": "10001", + "country": "USA" + }, + "items": [ + { + "item_id": "101", + "name": "Smartphone XYZ", + "quantity": 1, + "price": 699.99 + }, + { + "item_id": "202", + "name": "Phone Case XYZ", + "quantity": 1, + "price": 29.99 + }, + { + "item_id": "303", + "name": "Screen Protector XYZ", + "quantity": 2, + "price": 14.99 + } + ], + "payment": { + "method": "credit_card", + "status": "paid", + "transaction_id": "txn_123456789" + }, + "order_total": 759.95, + "status": "shipped", + "created_at": "2024-08-15T14:45:00Z", + "shipped_at": "2024-08-16T10:00:00Z" +} diff --git a/benchmarks/EmailBenchmark.cs b/benchmarks/EmailBenchmark.cs index 1b844b9..1936fea 100644 --- a/benchmarks/EmailBenchmark.cs +++ b/benchmarks/EmailBenchmark.cs @@ -1,62 +1,62 @@ -using System.Text.RegularExpressions; - -using BenchmarkDotNet.Attributes; - -using Pidgin; - -using Ramstack.Parsing.Benchmarks.Parsers; - -namespace Ramstack.Parsing.Benchmarks; - -[MemoryDiagnoser] -[OperationsPerSecond] -public partial class EmailBenchmark -{ - private static readonly string Email = "development.team-2021@example.com"; - - private static readonly Regex EmailRegex = new Regex(@"^[\w._+-]+@[\w-]+\.\w{2,}$"); - private static readonly Regex EmailRegexCompiled = new Regex((@"^[\w._+-]+@[\w-]+\.\w{2,}$"), RegexOptions.Compiled); - - [GeneratedRegex(@"^[\w._+-]+@[\w-]+\.\w{2,}$")] - private static partial Regex EmailRegexGenerated(); - - [GlobalSetup] - public void Setup() - { - if (!EmailRegex.IsMatch(Email)) throw new Exception(nameof(EmailRegex)); - if (!EmailRegexCompiled.IsMatch(Email)) throw new Exception(nameof(EmailRegexCompiled)); - if (!EmailRegexGenerated().IsMatch(Email)) throw new Exception(nameof(EmailRegexGenerated)); - if (!RamstackParsers.EmailParser.TryParse(Email, out _)) throw new Exception("RamstackParsers.EmailParser"); - if (!ParlotParsers.EmailParser.TryParse(Email, out _)) throw new Exception("ParlotParsers.EmailParser"); - if (!ParlotParsers.EmailParserCompiled.TryParse(Email, out _)) throw new Exception("ParlotParsers.EmailParserCompiled"); - PidginParsers.EmailParser.ParseOrThrow(Email); - } - - [Benchmark(Description = "Ramstack")] - public bool RamstackEmail() => - RamstackParsers.EmailVoidParser.TryParse(Email, out _); - - [Benchmark(Description = "Regex")] - public bool RegexEmail() => - EmailRegex.IsMatch(Email); - - [Benchmark(Description = "Regex:Compiled")] - public bool RegexEmailCompiled() => - EmailRegexCompiled.IsMatch(Email); - - [Benchmark(Description = "Regex:Generated")] - public bool RegexEmailGenerated() => - EmailRegexGenerated().IsMatch(Email); - - [Benchmark(Description = "Parlot")] - public bool ParlotEmail() => - ParlotParsers.EmailParser.TryParse(Email, out _); - - [Benchmark(Description = "Parlot:Compiled")] - public bool ParlotEmailCompiled() => - ParlotParsers.EmailParserCompiled.TryParse(Email, out _); - - [Benchmark(Description = "Pidgin")] - public bool PidginEmail() => - PidginParsers.EmailParser.Parse(Email).Success; -} +using System.Text.RegularExpressions; + +using BenchmarkDotNet.Attributes; + +using Pidgin; + +using Ramstack.Parsing.Benchmarks.Parsers; + +namespace Ramstack.Parsing.Benchmarks; + +[MemoryDiagnoser] +[OperationsPerSecond] +public partial class EmailBenchmark +{ + private static readonly string Email = "development.team-2021@example.com"; + + private static readonly Regex EmailRegex = new Regex(@"^[\w._+-]+@[\w-]+\.\w{2,}$"); + private static readonly Regex EmailRegexCompiled = new Regex((@"^[\w._+-]+@[\w-]+\.\w{2,}$"), RegexOptions.Compiled); + + [GeneratedRegex(@"^[\w._+-]+@[\w-]+\.\w{2,}$")] + private static partial Regex EmailRegexGenerated(); + + [GlobalSetup] + public void Setup() + { + if (!EmailRegex.IsMatch(Email)) throw new Exception(nameof(EmailRegex)); + if (!EmailRegexCompiled.IsMatch(Email)) throw new Exception(nameof(EmailRegexCompiled)); + if (!EmailRegexGenerated().IsMatch(Email)) throw new Exception(nameof(EmailRegexGenerated)); + if (!RamstackParsers.EmailParser.TryParse(Email, out _)) throw new Exception("RamstackParsers.EmailParser"); + if (!ParlotParsers.EmailParser.TryParse(Email, out _)) throw new Exception("ParlotParsers.EmailParser"); + if (!ParlotParsers.EmailParserCompiled.TryParse(Email, out _)) throw new Exception("ParlotParsers.EmailParserCompiled"); + PidginParsers.EmailParser.ParseOrThrow(Email); + } + + [Benchmark(Description = "Ramstack")] + public bool RamstackEmail() => + RamstackParsers.EmailVoidParser.TryParse(Email, out _); + + [Benchmark(Description = "Regex")] + public bool RegexEmail() => + EmailRegex.IsMatch(Email); + + [Benchmark(Description = "Regex:Compiled")] + public bool RegexEmailCompiled() => + EmailRegexCompiled.IsMatch(Email); + + [Benchmark(Description = "Regex:Generated")] + public bool RegexEmailGenerated() => + EmailRegexGenerated().IsMatch(Email); + + [Benchmark(Description = "Parlot")] + public bool ParlotEmail() => + ParlotParsers.EmailParser.TryParse(Email, out _); + + [Benchmark(Description = "Parlot:Compiled")] + public bool ParlotEmailCompiled() => + ParlotParsers.EmailParserCompiled.TryParse(Email, out _); + + [Benchmark(Description = "Pidgin")] + public bool PidginEmail() => + PidginParsers.EmailParser.Parse(Email).Success; +} diff --git a/benchmarks/ExpressionBenchmark.cs b/benchmarks/ExpressionBenchmark.cs index 96428c7..5a9c36b 100644 --- a/benchmarks/ExpressionBenchmark.cs +++ b/benchmarks/ExpressionBenchmark.cs @@ -1,99 +1,99 @@ -using System.Diagnostics.CodeAnalysis; - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; - -using Pidgin; - -using Ramstack.Parsing.Benchmarks.Parsers; - -namespace Ramstack.Parsing.Benchmarks; - -[MemoryDiagnoser] -[OperationsPerSecond] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -public class ExpressionBenchmark -{ - [SuppressMessage("ReSharper", "ArrangeRedundantParentheses")] - private const double Large = ((3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5)) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2); - private const double Small = 2.5 * 4.7 - 8.1 / 3; - - private const string SmallExpression = "2.5 * 4.7 - 8.1 / 3"; - private const string LargeExpression = "(( 3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5) ) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2)"; - - [GlobalSetup] - [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] - public void Setup() - { - if (RamstackParsers.ExpressionParser.Parse(SmallExpression).Value != Small) throw new Exception("Ramstack: Small"); - if (RamstackParsers.ExpressionParser.Parse(LargeExpression).Value != Large) throw new Exception("Ramstack: Large"); - - if (ParlotParsers.ExpressionParser.Parse(SmallExpression) != Small) throw new Exception("Parlot: Small"); - if (ParlotParsers.ExpressionParser.Parse(LargeExpression) != Large) throw new Exception("Parlot: Large"); - - if (PidginParsers.ExpressionParser.Parse(SmallExpression).Value != Small) throw new Exception("Pidgin: Small"); - if (PidginParsers.ExpressionParser.Parse(LargeExpression).Value != Large) throw new Exception("Pidgin: Large"); - } - - [BenchmarkCategory("Small")] - [Benchmark(Description = "Ramstack:Small")] - public double RamstackSmall() - { - RamstackParsers.ExpressionParser.TryParse(SmallExpression, out var result); - return result; - } - - [BenchmarkCategory("Small")] - [Benchmark(Description = "Ramstack:Diag:Small")] - public double RamstackSmall_Diagnostics() => - RamstackParsers.ExpressionParser.Parse(SmallExpression).Value; - - [BenchmarkCategory("Large")] - [Benchmark(Description = "Ramstack:Large")] - public double RamstackLarge() - { - RamstackParsers.ExpressionParser.TryParse(LargeExpression, out var result); - return result; - } - - [BenchmarkCategory("Large")] - [Benchmark(Description = "Ramstack:Diag:Large")] - public double RamstackLarge_Diagnostics() => - RamstackParsers.ExpressionParser.Parse(LargeExpression).Value; - - [BenchmarkCategory("Small")] - [Benchmark(Description = "Parlot:Small")] - public double ParlotSmall() => - ParlotParsers.ExpressionParser.Parse(SmallExpression); - - [BenchmarkCategory("Small")] - [Benchmark(Description = "Parlot:Compiled:Small")] - public double ParlotSmallCompiled() => - ParlotParsers.ExpressionParserCompiled.Parse(SmallExpression); - - [BenchmarkCategory("Large")] - [Benchmark(Description = "Parlot:Large")] - public double ParlotLarge() => - ParlotParsers.ExpressionParser.Parse(LargeExpression); - - [BenchmarkCategory("Large")] - [Benchmark(Description = "Parlot:Compiled:Large")] - public double ParlotLargeCompiled() => - ParlotParsers.ExpressionParserCompiled.Parse(LargeExpression); - - [BenchmarkCategory("Small")] - [Benchmark(Description = "Pidgin:Small")] - public double PidginSmall() - { - var result = PidginParsers.ExpressionParser.Parse(SmallExpression); - return result.Success ? result.Value : 0; - } - - [BenchmarkCategory("Large")] - [Benchmark(Description = "Pidgin:Large")] - public double PidginLarge() - { - var result = PidginParsers.ExpressionParser.Parse(LargeExpression); - return result.Success ? result.Value : 0; - } -} +using System.Diagnostics.CodeAnalysis; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; + +using Pidgin; + +using Ramstack.Parsing.Benchmarks.Parsers; + +namespace Ramstack.Parsing.Benchmarks; + +[MemoryDiagnoser] +[OperationsPerSecond] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +public class ExpressionBenchmark +{ + [SuppressMessage("ReSharper", "ArrangeRedundantParentheses")] + private const double Large = ((3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5)) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2); + private const double Small = 2.5 * 4.7 - 8.1 / 3; + + private const string SmallExpression = "2.5 * 4.7 - 8.1 / 3"; + private const string LargeExpression = "(( 3.14159 * 2.3 * 4) / (1.5 - 0.3) + (2.71828 * 2.5) ) * ((8.1 * 3.2 + 4.7) / 2.1 - ((15.7 + 2.3 * 4) / (1.5 - 0.3))) - (((3.5 * 1.41421) * (2.71828 + 3.14159)) / 2)"; + + [GlobalSetup] + [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] + public void Setup() + { + if (RamstackParsers.ExpressionParser.Parse(SmallExpression).Value != Small) throw new Exception("Ramstack: Small"); + if (RamstackParsers.ExpressionParser.Parse(LargeExpression).Value != Large) throw new Exception("Ramstack: Large"); + + if (ParlotParsers.ExpressionParser.Parse(SmallExpression) != Small) throw new Exception("Parlot: Small"); + if (ParlotParsers.ExpressionParser.Parse(LargeExpression) != Large) throw new Exception("Parlot: Large"); + + if (PidginParsers.ExpressionParser.Parse(SmallExpression).Value != Small) throw new Exception("Pidgin: Small"); + if (PidginParsers.ExpressionParser.Parse(LargeExpression).Value != Large) throw new Exception("Pidgin: Large"); + } + + [BenchmarkCategory("Small")] + [Benchmark(Description = "Ramstack:Small")] + public double RamstackSmall() + { + RamstackParsers.ExpressionParser.TryParse(SmallExpression, out var result); + return result; + } + + [BenchmarkCategory("Small")] + [Benchmark(Description = "Ramstack:Diag:Small")] + public double RamstackSmall_Diagnostics() => + RamstackParsers.ExpressionParser.Parse(SmallExpression).Value; + + [BenchmarkCategory("Large")] + [Benchmark(Description = "Ramstack:Large")] + public double RamstackLarge() + { + RamstackParsers.ExpressionParser.TryParse(LargeExpression, out var result); + return result; + } + + [BenchmarkCategory("Large")] + [Benchmark(Description = "Ramstack:Diag:Large")] + public double RamstackLarge_Diagnostics() => + RamstackParsers.ExpressionParser.Parse(LargeExpression).Value; + + [BenchmarkCategory("Small")] + [Benchmark(Description = "Parlot:Small")] + public double ParlotSmall() => + ParlotParsers.ExpressionParser.Parse(SmallExpression); + + [BenchmarkCategory("Small")] + [Benchmark(Description = "Parlot:Compiled:Small")] + public double ParlotSmallCompiled() => + ParlotParsers.ExpressionParserCompiled.Parse(SmallExpression); + + [BenchmarkCategory("Large")] + [Benchmark(Description = "Parlot:Large")] + public double ParlotLarge() => + ParlotParsers.ExpressionParser.Parse(LargeExpression); + + [BenchmarkCategory("Large")] + [Benchmark(Description = "Parlot:Compiled:Large")] + public double ParlotLargeCompiled() => + ParlotParsers.ExpressionParserCompiled.Parse(LargeExpression); + + [BenchmarkCategory("Small")] + [Benchmark(Description = "Pidgin:Small")] + public double PidginSmall() + { + var result = PidginParsers.ExpressionParser.Parse(SmallExpression); + return result.Success ? result.Value : 0; + } + + [BenchmarkCategory("Large")] + [Benchmark(Description = "Pidgin:Large")] + public double PidginLarge() + { + var result = PidginParsers.ExpressionParser.Parse(LargeExpression); + return result.Success ? result.Value : 0; + } +} diff --git a/benchmarks/JsonBenchmark.cs b/benchmarks/JsonBenchmark.cs index eeb92cd..31910be 100644 --- a/benchmarks/JsonBenchmark.cs +++ b/benchmarks/JsonBenchmark.cs @@ -1,60 +1,60 @@ -using BenchmarkDotNet.Attributes; - -using Ramstack.Parsing.Benchmarks.Parsers; - -namespace Ramstack.Parsing.Benchmarks; - -[MemoryDiagnoser] -[OperationsPerSecond] -public class JsonBenchmark -{ - public static readonly string JsonBig = File.ReadAllText("Data/twitter.json"); - public static readonly string JsonMedium = File.ReadAllText("Data/medium.json"); - public static readonly string JsonSmall = File.ReadAllText("Data/small.json"); - - public IEnumerable Source - { - get - { - yield return new JsonString(JsonSmall.TrimEnd(), "small"); - yield return new JsonString(JsonMedium.TrimEnd(), "medium"); - yield return new JsonString(JsonBig.TrimEnd(), "big"); - } - } - - [ParamsSource(nameof(Source))] - public JsonString Input { get; set; } - - [Benchmark(Description = "Ramstack")] - public object? RamstackJson() - { - RamstackParsers.JsonParser.TryParse(Input.Value, out var result); - return result; - } - - [Benchmark(Description = "Parlot")] - public object? ParlotJson() => - ParlotParsers.JsonParser.Parse(Input.Value); - - [Benchmark(Description = "Parlot:Compiled")] - public object? ParlotJsonCompiled() => - ParlotParsers.JsonParserCompiled.Parse(Input.Value); - - [Benchmark(Description = "Pidgin")] - public object? PidginJson() => - PidginParsers.JsonParser.Parse(Input.Value); - - [Benchmark(Description = "Newtonsoft.Json")] - public object? NewtonsoftJson() => - Newtonsoft.Json.JsonConvert.DeserializeObject(Input.Value); - - [Benchmark(Description = "System.Text.Json")] - public object? SystemTextJson() => - System.Text.Json.JsonSerializer.Deserialize(Input.Value); - - public readonly struct JsonString(string json, string name) - { - public string Value => json; - public override string ToString() => name; - } -} +using BenchmarkDotNet.Attributes; + +using Ramstack.Parsing.Benchmarks.Parsers; + +namespace Ramstack.Parsing.Benchmarks; + +[MemoryDiagnoser] +[OperationsPerSecond] +public class JsonBenchmark +{ + public static readonly string JsonBig = File.ReadAllText("Data/twitter.json"); + public static readonly string JsonMedium = File.ReadAllText("Data/medium.json"); + public static readonly string JsonSmall = File.ReadAllText("Data/small.json"); + + public IEnumerable Source + { + get + { + yield return new JsonString(JsonSmall.TrimEnd(), "small"); + yield return new JsonString(JsonMedium.TrimEnd(), "medium"); + yield return new JsonString(JsonBig.TrimEnd(), "big"); + } + } + + [ParamsSource(nameof(Source))] + public JsonString Input { get; set; } + + [Benchmark(Description = "Ramstack")] + public object? RamstackJson() + { + RamstackParsers.JsonParser.TryParse(Input.Value, out var result); + return result; + } + + [Benchmark(Description = "Parlot")] + public object? ParlotJson() => + ParlotParsers.JsonParser.Parse(Input.Value); + + [Benchmark(Description = "Parlot:Compiled")] + public object? ParlotJsonCompiled() => + ParlotParsers.JsonParserCompiled.Parse(Input.Value); + + [Benchmark(Description = "Pidgin")] + public object? PidginJson() => + PidginParsers.JsonParser.Parse(Input.Value); + + [Benchmark(Description = "Newtonsoft.Json")] + public object? NewtonsoftJson() => + Newtonsoft.Json.JsonConvert.DeserializeObject(Input.Value); + + [Benchmark(Description = "System.Text.Json")] + public object? SystemTextJson() => + System.Text.Json.JsonSerializer.Deserialize(Input.Value); + + public readonly struct JsonString(string json, string name) + { + public string Value => json; + public override string ToString() => name; + } +} diff --git a/benchmarks/LiteralsBenchmark.cs b/benchmarks/LiteralsBenchmark.cs index dd101b2..959ac42 100644 --- a/benchmarks/LiteralsBenchmark.cs +++ b/benchmarks/LiteralsBenchmark.cs @@ -1,46 +1,46 @@ -using System.Text.RegularExpressions; - -using BenchmarkDotNet.Attributes; - -namespace Ramstack.Parsing.Benchmarks; - -[MemoryDiagnoser] -[OperationsPerSecond] -public partial class LiteralsBenchmark -{ - private const string RegexExpr = "^(abstract|as|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while)$"; - - [GeneratedRegex(RegexExpr)] - private static partial Regex LiteralsRegexGenerated(); - private static readonly Regex LiteralsRegexCompiled = new Regex(RegexExpr, RegexOptions.Compiled); - private static readonly Regex LiteralsRegex = new Regex(RegexExpr); - - private static readonly Parser RamstackLiteralParser = Parser.OneOf(RegexExpr[1..^1].Split('|')).Void(); - - [Params("sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch")] - // [Params( - // "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", - // "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", - // "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", - // "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", - // "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", - // "virtual", "void", "volatile", "while" - // )] - public string Input { get; set; } = null!; - - [Benchmark(Description = "Regex")] - public bool RegexEmail() => - LiteralsRegex.IsMatch(Input); - - [Benchmark(Description = "RegexCompiled")] - public bool RegexEmailCompiled() => - LiteralsRegexCompiled.IsMatch(Input); - - [Benchmark(Description = "RegexGenerated")] - public bool RegexEmailGenerated() => - LiteralsRegexGenerated().IsMatch(Input); - - [Benchmark(Description = "Ramstack.Parsing")] - public bool RamstackParsing() => - RamstackLiteralParser.TryParse(Input, out _); -} +using System.Text.RegularExpressions; + +using BenchmarkDotNet.Attributes; + +namespace Ramstack.Parsing.Benchmarks; + +[MemoryDiagnoser] +[OperationsPerSecond] +public partial class LiteralsBenchmark +{ + private const string RegexExpr = "^(abstract|as|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|do|double|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|internal|is|lock|long|namespace|new|null|object|operator|out|override|params|private|protected|public|readonly|ref|return|sbyte|sealed|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|virtual|void|volatile|while)$"; + + [GeneratedRegex(RegexExpr)] + private static partial Regex LiteralsRegexGenerated(); + private static readonly Regex LiteralsRegexCompiled = new Regex(RegexExpr, RegexOptions.Compiled); + private static readonly Regex LiteralsRegex = new Regex(RegexExpr); + + private static readonly Parser RamstackLiteralParser = Parser.OneOf(RegexExpr[1..^1].Split('|')).Void(); + + [Params("sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch")] + // [Params( + // "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", "decimal", "default", + // "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach", "goto", + // "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long", "namespace", "new", "null", "object", "operator", "out", + // "override", "params", "private", "protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", + // "static", "string", "struct", "switch", "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", + // "virtual", "void", "volatile", "while" + // )] + public string Input { get; set; } = null!; + + [Benchmark(Description = "Regex")] + public bool RegexEmail() => + LiteralsRegex.IsMatch(Input); + + [Benchmark(Description = "RegexCompiled")] + public bool RegexEmailCompiled() => + LiteralsRegexCompiled.IsMatch(Input); + + [Benchmark(Description = "RegexGenerated")] + public bool RegexEmailGenerated() => + LiteralsRegexGenerated().IsMatch(Input); + + [Benchmark(Description = "Ramstack.Parsing")] + public bool RamstackParsing() => + RamstackLiteralParser.TryParse(Input, out _); +} diff --git a/benchmarks/Parsers/ParlotParsers.cs b/benchmarks/Parsers/ParlotParsers.cs index 75831ed..e3164c9 100644 --- a/benchmarks/Parsers/ParlotParsers.cs +++ b/benchmarks/Parsers/ParlotParsers.cs @@ -1,149 +1,149 @@ -using Parlot; -using Parlot.Fluent; - -using static Parlot.Fluent.Parsers; - -namespace Ramstack.Parsing.Benchmarks.Parsers; - -public static class ParlotParsers -{ - public static readonly Parlot.Fluent.Parser EmailParser = CreateEmailParser(); - public static readonly Parlot.Fluent.Parser EmailParserCompiled = EmailParser.Compile(); - public static readonly Parlot.Fluent.Parser ExpressionParser = CreateExpressionParser(); - public static readonly Parlot.Fluent.Parser ExpressionParserCompiled = ExpressionParser.Compile(); - public static readonly Parlot.Fluent.Parser JsonParser = CreateJsonParser(); - public static readonly Parlot.Fluent.Parser JsonParserCompiled = JsonParser.Compile(); - - private static Parlot.Fluent.Parser CreateEmailParser() - { - var dot = Literals.Char('.'); - var plus = Literals.Char('+'); - var minus = Literals.Char('-'); - var at = Literals.Char('@'); - var wordChar = Literals.Pattern(char.IsLetterOrDigit).Then(_ => 'w'); - var wordDotPlusMinus = OneOrMany(OneOf(wordChar, dot, plus, minus)); - var wordDotMinus = OneOrMany(OneOf(wordChar, dot, minus)); - var wordMinus = OneOrMany(OneOf(wordChar, minus)); - return Capture(wordDotPlusMinus.And(at).And(wordMinus).And(dot).And(wordDotMinus.Eof())); - } - - private static Parlot.Fluent.Parser CreateExpressionParser() - { - /* - * Grammar: - * The top declaration has a lower priority than the lower one. - * - * additive => multiplicative ( ( "-" | "+" ) multiplicative )* ; - * multiplicative => unary ( ( "/" | "*" ) unary )* ; - * unary => ( "-" ) unary - * | primary ; - * primary => NUMBER - * | "(" expression ")" ; - */ - - // The Deferred helper creates a parser that can be referenced by others before it is defined - var expression = Deferred(); - - var number = Terms.Number(NumberOptions.Float); - var divided = Terms.Char('/'); - var times = Terms.Char('*'); - var minus = Terms.Char('-'); - var plus = Terms.Char('+'); - var openParen = Terms.Char('('); - var closeParen = Terms.Char(')'); - - // "(" expression ")" - var groupExpression = Between(openParen, expression, closeParen).Named("group"); - - // primary => NUMBER | "(" expression ")"; - var primary = number.Or(groupExpression).Named("primary"); - - // ( "-" ) unary | primary; - var unary = primary.Unary((minus, x => -x)).Named("unary"); - - // multiplicative => unary ( ( "/" | "*" ) unary )* ; - var multiplicative = unary.LeftAssociative( - (divided, (a, b) => a / b), - (times, (a, b) => a * b) - ).Named("multiplicative"); - - // additive => multiplicative(("-" | "+") multiplicative) * ; - var additive = multiplicative.LeftAssociative( - (plus, (a, b) => a + b), - (minus, (a, b) => a - b) - ).Named("additive"); - - expression.Parser = additive; - return expression.Named("expression"); - } - - private static Parlot.Fluent.Parser CreateJsonParser() - { - var value = Deferred(); - - var colon = Terms.Char(':'); - var comma = Terms.Char(','); - - var text = Terms - .String(StringLiteralQuotes.Double) - .Then(object? (v) => v.ToString()); - - var number = Terms - .Number(NumberOptions.Float) - .Then(object? (v) => v); - - var boolTrue = Terms - .Text("true") - .Then(object? (_) => true); - - var boolFalse = Terms - .Text("false") - .Then(object? (_) => false); - - var nullValue = Terms - .Text("null") - .Then(object? (_) => null); - - var emptyArray = - Terms.Char('[') - .And(Terms.Char(']')) - .Then(object? (_) => new List()); - - var array = - Between( - Terms.Char('['), - Separated(comma, value), - Terms.Char(']') - ).Then(object? (list) => list); - - var member = - text - .And(colon) - .And(value) - .Then(member => KeyValuePair.Create( - member.Item1!.ToString()!, - member.Item3)); - - var emptyMap = - Terms.Char('{') - .And(Terms.Char('}')) - .Then(object? (_) => new Dictionary()); - - var map = - Between( - Terms.Char('{'), - Separated(comma, member), - Terms.Char('}') - ).Then(object? (members) => members.ToDictionary()); - - return value.Parser = text - .Or(number) - .Or(boolTrue) - .Or(boolFalse) - .Or(nullValue) - .Or(emptyArray) - .Or(array) - .Or(emptyMap) - .Or(map); - } -} +using Parlot; +using Parlot.Fluent; + +using static Parlot.Fluent.Parsers; + +namespace Ramstack.Parsing.Benchmarks.Parsers; + +public static class ParlotParsers +{ + public static readonly Parlot.Fluent.Parser EmailParser = CreateEmailParser(); + public static readonly Parlot.Fluent.Parser EmailParserCompiled = EmailParser.Compile(); + public static readonly Parlot.Fluent.Parser ExpressionParser = CreateExpressionParser(); + public static readonly Parlot.Fluent.Parser ExpressionParserCompiled = ExpressionParser.Compile(); + public static readonly Parlot.Fluent.Parser JsonParser = CreateJsonParser(); + public static readonly Parlot.Fluent.Parser JsonParserCompiled = JsonParser.Compile(); + + private static Parlot.Fluent.Parser CreateEmailParser() + { + var dot = Literals.Char('.'); + var plus = Literals.Char('+'); + var minus = Literals.Char('-'); + var at = Literals.Char('@'); + var wordChar = Literals.Pattern(char.IsLetterOrDigit).Then(_ => 'w'); + var wordDotPlusMinus = OneOrMany(OneOf(wordChar, dot, plus, minus)); + var wordDotMinus = OneOrMany(OneOf(wordChar, dot, minus)); + var wordMinus = OneOrMany(OneOf(wordChar, minus)); + return Capture(wordDotPlusMinus.And(at).And(wordMinus).And(dot).And(wordDotMinus.Eof())); + } + + private static Parlot.Fluent.Parser CreateExpressionParser() + { + /* + * Grammar: + * The top declaration has a lower priority than the lower one. + * + * additive => multiplicative ( ( "-" | "+" ) multiplicative )* ; + * multiplicative => unary ( ( "/" | "*" ) unary )* ; + * unary => ( "-" ) unary + * | primary ; + * primary => NUMBER + * | "(" expression ")" ; + */ + + // The Deferred helper creates a parser that can be referenced by others before it is defined + var expression = Deferred(); + + var number = Terms.Number(NumberOptions.Float); + var divided = Terms.Char('/'); + var times = Terms.Char('*'); + var minus = Terms.Char('-'); + var plus = Terms.Char('+'); + var openParen = Terms.Char('('); + var closeParen = Terms.Char(')'); + + // "(" expression ")" + var groupExpression = Between(openParen, expression, closeParen).Named("group"); + + // primary => NUMBER | "(" expression ")"; + var primary = number.Or(groupExpression).Named("primary"); + + // ( "-" ) unary | primary; + var unary = primary.Unary((minus, x => -x)).Named("unary"); + + // multiplicative => unary ( ( "/" | "*" ) unary )* ; + var multiplicative = unary.LeftAssociative( + (divided, (a, b) => a / b), + (times, (a, b) => a * b) + ).Named("multiplicative"); + + // additive => multiplicative(("-" | "+") multiplicative) * ; + var additive = multiplicative.LeftAssociative( + (plus, (a, b) => a + b), + (minus, (a, b) => a - b) + ).Named("additive"); + + expression.Parser = additive; + return expression.Named("expression"); + } + + private static Parlot.Fluent.Parser CreateJsonParser() + { + var value = Deferred(); + + var colon = Terms.Char(':'); + var comma = Terms.Char(','); + + var text = Terms + .String(StringLiteralQuotes.Double) + .Then(object? (v) => v.ToString()); + + var number = Terms + .Number(NumberOptions.Float) + .Then(object? (v) => v); + + var boolTrue = Terms + .Text("true") + .Then(object? (_) => true); + + var boolFalse = Terms + .Text("false") + .Then(object? (_) => false); + + var nullValue = Terms + .Text("null") + .Then(object? (_) => null); + + var emptyArray = + Terms.Char('[') + .And(Terms.Char(']')) + .Then(object? (_) => new List()); + + var array = + Between( + Terms.Char('['), + Separated(comma, value), + Terms.Char(']') + ).Then(object? (list) => list); + + var member = + text + .And(colon) + .And(value) + .Then(member => KeyValuePair.Create( + member.Item1!.ToString()!, + member.Item3)); + + var emptyMap = + Terms.Char('{') + .And(Terms.Char('}')) + .Then(object? (_) => new Dictionary()); + + var map = + Between( + Terms.Char('{'), + Separated(comma, member), + Terms.Char('}') + ).Then(object? (members) => members.ToDictionary()); + + return value.Parser = text + .Or(number) + .Or(boolTrue) + .Or(boolFalse) + .Or(nullValue) + .Or(emptyArray) + .Or(array) + .Or(emptyMap) + .Or(map); + } +} diff --git a/benchmarks/Parsers/PidginParsers.cs b/benchmarks/Parsers/PidginParsers.cs index b19a917..7e3359e 100644 --- a/benchmarks/Parsers/PidginParsers.cs +++ b/benchmarks/Parsers/PidginParsers.cs @@ -1,149 +1,149 @@ -using System.Globalization; - -using Pidgin; -using Pidgin.Expression; - -using static Pidgin.Parser; -using static Pidgin.Parser; - -namespace Ramstack.Parsing.Benchmarks.Parsers; - -public static class PidginParsers -{ - public static readonly Parser ExpressionParser = ExprParserImpl.Instance; - public static readonly Parser EmailParser = - from local in OneOf(LetterOrDigit, Char('_'), Char('.'), Char('+'), Char('-')).AtLeastOnceString() - from at in Char('@') - from domain in OneOf(LetterOrDigit, Char('-')).AtLeastOnceString() - from dot in Char('.') - from tld in Letter.AtLeastOnceString().Where(t => t.Length >= 2) - select $"{local}@{domain}.{tld}"; - - private static class ExprParserImpl - { - private static Parser Tok(Parser token) => Try(token).Before(SkipWhitespaces); - private static Parser Tok(string token) => Tok(String(token)); - private static Parser Parenthesized(Parser parser) => parser.Between(Tok("("), Tok(")")); - private static Parser> Binary(Parser op) => - op.Select>(type => (l, r) => - { - return type switch - { - '+' => l + r, - '-' => l - r, - '*' => l * r, - _ => l / r - }; - } - ); - - private static Parser> Unary(Parser op) => op.Select>(_ => d => -d); - private static readonly Parser> Add = Binary(Tok("+").ThenReturn('+')); - private static readonly Parser> Sub = Binary(Tok("-").ThenReturn('-')); - private static readonly Parser> Mul = Binary(Tok("*").ThenReturn('*')); - private static readonly Parser> Div = Binary(Tok("/").ThenReturn('/')); - private static readonly Parser> Neg = Unary(Tok("-").ThenReturn('-')); - private static readonly Parser Literal = Tok(Real).Labelled("decimal literal"); - - public static readonly Parser Instance = Pidgin.Expression.ExpressionParser.Build( - expr => ( - OneOf( - Literal, - Parenthesized(expr).Labelled("parenthesized expression") - ), - [ - Operator.Prefix(Neg), - Operator.InfixL(Mul).And(Operator.InfixL(Div)), - Operator.InfixL(Add).And(Operator.InfixL(Sub)) - ] - ) - ).Labelled("expression"); - } - - public static class JsonParser - { - private static readonly Parser LBrace = Char('{'); - private static readonly Parser RBrace = Char('}'); - private static readonly Parser LBracket = Char('['); - private static readonly Parser RBracket = Char(']'); - private static readonly Parser Quote = Char('"'); - private static readonly Parser Colon = Char(':'); - private static readonly Parser ColonWhitespace = - Colon.Between(SkipWhitespaces); - - private static readonly Parser Comma = Char(','); - private static readonly Parser HexDigit = - Token(c => c - is >= '0' and <= '9' - or >= 'A' and <= 'F' - or >= 'a' and <= 'f'); - - private static readonly Parser String = - Rec(() => - OneOf( - AnyCharExcept('"', '\\'), - Char('\\') - .Then( - OneOf( - Char('"').Select (_ => '"'), - Char('\\').Select(_ => '\\'), - Char('/').Select (_ => '/'), - Char('b').Select (_ => '\b'), - Char('f').Select (_ => '\f'), - Char('n').Select (_ => '\n'), - Char('r').Select (_ => '\r'), - Char('t').Select (_ => '\t'), - Char('u').Then(HexDigit.Repeat(4) - .Select(h => (char)int.Parse(new string(h.ToArray()), NumberStyles.HexNumber))) - ) - ) - ).ManyString().Between(Quote) - ); - private static readonly Parser JsonString = - String.Select(s => s); - - private static readonly Parser JsonNumber = - Real.Select(n => n); - - private static readonly Parser JsonPrimitive = - String("true") - .Or(String("false")) - .Or(String("null")) - .Select(s => - { - object? r = null; - if (s.Length >= 4 && s[0] != 'n') - r = s[0] == 't'; - - return r; - }); - - private static readonly Parser Json = - OneOf( - JsonString, - JsonNumber, - JsonPrimitive, - Rec(() => JsonArray!), - Rec(() => JsonObject!) - ); - - private static readonly Parser JsonArray = - Json.Between(SkipWhitespaces) - .Separated(Comma) - .Between(LBracket, RBracket) - .Select(v => v as List ?? v.ToList()); - - private static readonly Parser> JsonMember = - String - .Before(ColonWhitespace) - .Then(Json, (name, val) => new KeyValuePair(name, val)); - - private static readonly Parser JsonObject = - JsonMember.Between(SkipWhitespaces) - .Separated(Comma) - .Between(LBrace, RBrace) - .Select(members => members.ToDictionary()); - - public static object? Parse(string input) => Json.ParseOrThrow(input); - } -} +using System.Globalization; + +using Pidgin; +using Pidgin.Expression; + +using static Pidgin.Parser; +using static Pidgin.Parser; + +namespace Ramstack.Parsing.Benchmarks.Parsers; + +public static class PidginParsers +{ + public static readonly Parser ExpressionParser = ExprParserImpl.Instance; + public static readonly Parser EmailParser = + from local in OneOf(LetterOrDigit, Char('_'), Char('.'), Char('+'), Char('-')).AtLeastOnceString() + from at in Char('@') + from domain in OneOf(LetterOrDigit, Char('-')).AtLeastOnceString() + from dot in Char('.') + from tld in Letter.AtLeastOnceString().Where(t => t.Length >= 2) + select $"{local}@{domain}.{tld}"; + + private static class ExprParserImpl + { + private static Parser Tok(Parser token) => Try(token).Before(SkipWhitespaces); + private static Parser Tok(string token) => Tok(String(token)); + private static Parser Parenthesized(Parser parser) => parser.Between(Tok("("), Tok(")")); + private static Parser> Binary(Parser op) => + op.Select>(type => (l, r) => + { + return type switch + { + '+' => l + r, + '-' => l - r, + '*' => l * r, + _ => l / r + }; + } + ); + + private static Parser> Unary(Parser op) => op.Select>(_ => d => -d); + private static readonly Parser> Add = Binary(Tok("+").ThenReturn('+')); + private static readonly Parser> Sub = Binary(Tok("-").ThenReturn('-')); + private static readonly Parser> Mul = Binary(Tok("*").ThenReturn('*')); + private static readonly Parser> Div = Binary(Tok("/").ThenReturn('/')); + private static readonly Parser> Neg = Unary(Tok("-").ThenReturn('-')); + private static readonly Parser Literal = Tok(Real).Labelled("decimal literal"); + + public static readonly Parser Instance = Pidgin.Expression.ExpressionParser.Build( + expr => ( + OneOf( + Literal, + Parenthesized(expr).Labelled("parenthesized expression") + ), + [ + Operator.Prefix(Neg), + Operator.InfixL(Mul).And(Operator.InfixL(Div)), + Operator.InfixL(Add).And(Operator.InfixL(Sub)) + ] + ) + ).Labelled("expression"); + } + + public static class JsonParser + { + private static readonly Parser LBrace = Char('{'); + private static readonly Parser RBrace = Char('}'); + private static readonly Parser LBracket = Char('['); + private static readonly Parser RBracket = Char(']'); + private static readonly Parser Quote = Char('"'); + private static readonly Parser Colon = Char(':'); + private static readonly Parser ColonWhitespace = + Colon.Between(SkipWhitespaces); + + private static readonly Parser Comma = Char(','); + private static readonly Parser HexDigit = + Token(c => c + is >= '0' and <= '9' + or >= 'A' and <= 'F' + or >= 'a' and <= 'f'); + + private static readonly Parser String = + Rec(() => + OneOf( + AnyCharExcept('"', '\\'), + Char('\\') + .Then( + OneOf( + Char('"').Select (_ => '"'), + Char('\\').Select(_ => '\\'), + Char('/').Select (_ => '/'), + Char('b').Select (_ => '\b'), + Char('f').Select (_ => '\f'), + Char('n').Select (_ => '\n'), + Char('r').Select (_ => '\r'), + Char('t').Select (_ => '\t'), + Char('u').Then(HexDigit.Repeat(4) + .Select(h => (char)int.Parse(new string(h.ToArray()), NumberStyles.HexNumber))) + ) + ) + ).ManyString().Between(Quote) + ); + private static readonly Parser JsonString = + String.Select(s => s); + + private static readonly Parser JsonNumber = + Real.Select(n => n); + + private static readonly Parser JsonPrimitive = + String("true") + .Or(String("false")) + .Or(String("null")) + .Select(s => + { + object? r = null; + if (s.Length >= 4 && s[0] != 'n') + r = s[0] == 't'; + + return r; + }); + + private static readonly Parser Json = + OneOf( + JsonString, + JsonNumber, + JsonPrimitive, + Rec(() => JsonArray!), + Rec(() => JsonObject!) + ); + + private static readonly Parser JsonArray = + Json.Between(SkipWhitespaces) + .Separated(Comma) + .Between(LBracket, RBracket) + .Select(v => v as List ?? v.ToList()); + + private static readonly Parser> JsonMember = + String + .Before(ColonWhitespace) + .Then(Json, (name, val) => new KeyValuePair(name, val)); + + private static readonly Parser JsonObject = + JsonMember.Between(SkipWhitespaces) + .Separated(Comma) + .Between(LBrace, RBrace) + .Select(members => members.ToDictionary()); + + public static object? Parse(string input) => Json.ParseOrThrow(input); + } +} diff --git a/benchmarks/Parsers/RamstackParsers.cs b/benchmarks/Parsers/RamstackParsers.cs index d7946d9..4e78098 100644 --- a/benchmarks/Parsers/RamstackParsers.cs +++ b/benchmarks/Parsers/RamstackParsers.cs @@ -1,25 +1,25 @@ -using static Ramstack.Parsing.Character; -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing.Benchmarks.Parsers; - -public static class RamstackParsers -{ - public static readonly Parser EmailParser = CreateEmailParser(); - public static readonly Parser EmailVoidParser = EmailParser.Void(); - public static readonly Parser ExpressionParser = Samples.CalcExpr.ExpressionParser.Parser; - public static readonly Parser JsonParser = Samples.Json.JsonParser.Parser; - - private static Parser CreateEmailParser() - { - var parser = Seq( - Choice(LetterOrDigit, OneOf("_.+-")).OneOrMore(), - L('@'), - Choice(LetterOrDigit, L('-')).OneOrMore(), - L('.'), - LetterOrDigit.AtLeast(2) - ).Text(); - - return parser; - } -} +using static Ramstack.Parsing.Character; +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing.Benchmarks.Parsers; + +public static class RamstackParsers +{ + public static readonly Parser EmailParser = CreateEmailParser(); + public static readonly Parser EmailVoidParser = EmailParser.Void(); + public static readonly Parser ExpressionParser = Samples.CalcExpr.ExpressionParser.Parser; + public static readonly Parser JsonParser = Samples.Json.JsonParser.Parser; + + private static Parser CreateEmailParser() + { + var parser = Seq( + Choice(LetterOrDigit, OneOf("_.+-")).OneOrMore(), + L('@'), + Choice(LetterOrDigit, L('-')).OneOrMore(), + L('.'), + LetterOrDigit.AtLeast(2) + ).Text(); + + return parser; + } +} diff --git a/benchmarks/Program.cs b/benchmarks/Program.cs index cfd4a15..c9a0467 100644 --- a/benchmarks/Program.cs +++ b/benchmarks/Program.cs @@ -1,3 +1,3 @@ -using BenchmarkDotNet.Running; - -BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); +using BenchmarkDotNet.Running; + +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); diff --git a/benchmarks/Ramstack.Parsing.Benchmarks.csproj b/benchmarks/Ramstack.Parsing.Benchmarks.csproj index 181a157..894df48 100644 --- a/benchmarks/Ramstack.Parsing.Benchmarks.csproj +++ b/benchmarks/Ramstack.Parsing.Benchmarks.csproj @@ -1,36 +1,36 @@ - - - - Exe - net9.0 - enable - enable - latest - false - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + + + + Exe + net9.0 + enable + enable + latest + false + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/samples/CalcExpr/ExpressionParser.cs b/samples/CalcExpr/ExpressionParser.cs index 0adc04c..22334f1 100644 --- a/samples/CalcExpr/ExpressionParser.cs +++ b/samples/CalcExpr/ExpressionParser.cs @@ -1,49 +1,49 @@ -using System.Diagnostics.CodeAnalysis; - -using Ramstack.Parsing; - -using static Ramstack.Parsing.Parser; - -namespace Samples.CalcExpr; - -public static class ExpressionParser -{ - public static readonly Parser Parser = CreateExpressionParser(); - - [SuppressMessage("ReSharper", "InconsistentNaming")] - private static Parser CreateExpressionParser() - { - var sum_expr = - Deferred(); - - var number_expr = - Literal.Number("number").ThenIgnore(S); - - var parenthesis_expr = - sum_expr.Between( - Seq(L('('), S), - Seq(L(')'), S)); - - var primary_expr = - parenthesis_expr.Or(number_expr); - - var unary_expr = - Seq( - L('-').Optional(), - S, - primary_expr - ).Do((u, _, d) => u.HasValue ? -d : d); - - var mul_expr = - unary_expr.Fold( - OneOf("*/").ThenIgnore(S), - (l, r, o) => o == '*' ? l * r : l / r); - - sum_expr.Parser = - mul_expr.Fold( - OneOf("+-").ThenIgnore(S), - (l, r, o) => o == '+' ? l + r : l - r); - - return sum_expr.Between(S, Eof); - } -} +using System.Diagnostics.CodeAnalysis; + +using Ramstack.Parsing; + +using static Ramstack.Parsing.Parser; + +namespace Samples.CalcExpr; + +public static class ExpressionParser +{ + public static readonly Parser Parser = CreateExpressionParser(); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static Parser CreateExpressionParser() + { + var sum_expr = + Deferred(); + + var number_expr = + Literal.Number("number").ThenIgnore(S); + + var parenthesis_expr = + sum_expr.Between( + Seq(L('('), S), + Seq(L(')'), S)); + + var primary_expr = + parenthesis_expr.Or(number_expr); + + var unary_expr = + Seq( + L('-').Optional(), + S, + primary_expr + ).Do((u, _, d) => u.HasValue ? -d : d); + + var mul_expr = + unary_expr.Fold( + OneOf("*/").ThenIgnore(S), + (l, r, o) => o == '*' ? l * r : l / r); + + sum_expr.Parser = + mul_expr.Fold( + OneOf("+-").ThenIgnore(S), + (l, r, o) => o == '+' ? l + r : l - r); + + return sum_expr.Between(S, Eof); + } +} diff --git a/samples/CalcExpr/README.md b/samples/CalcExpr/README.md index e8b2859..37b43ae 100644 --- a/samples/CalcExpr/README.md +++ b/samples/CalcExpr/README.md @@ -1,43 +1,43 @@ -# Simple Calc - -This project implements a simple mathematical expression parser. - -## Simple Expression Grammar - -```sh -start - = S sum_expr EOF - ; - -sum_expr - = mul_expr (S [+-] mul_expr)* - ; - -mul_expr - = unary_expr (S [*/] unary_expr)* - ; - -unary_expr - = S "-"? primary_expr - ; - -primary_expr - = parenthesis_expr / number_expr - ; - -parenthesis_expr - = S "(" Sum S ")" - ; - -number_expr - = S [0-9]+ - ; - -S - = [ \t\r\n]* - ; - -EOF - = $ - ; -``` +# Simple Calc + +This project implements a simple mathematical expression parser. + +## Simple Expression Grammar + +```sh +start + = S sum_expr EOF + ; + +sum_expr + = mul_expr (S [+-] mul_expr)* + ; + +mul_expr + = unary_expr (S [*/] unary_expr)* + ; + +unary_expr + = S "-"? primary_expr + ; + +primary_expr + = parenthesis_expr / number_expr + ; + +parenthesis_expr + = S "(" Sum S ")" + ; + +number_expr + = S [0-9]+ + ; + +S + = [ \t\r\n]* + ; + +EOF + = $ + ; +``` diff --git a/samples/Json/JsonParser.cs b/samples/Json/JsonParser.cs index 2ade580..b7290d7 100644 --- a/samples/Json/JsonParser.cs +++ b/samples/Json/JsonParser.cs @@ -1,60 +1,60 @@ -using Ramstack.Parsing; - -using static Ramstack.Parsing.Literal; -using static Ramstack.Parsing.Parser; - -namespace Samples.Json; - -public static class JsonParser -{ - public static readonly Parser Parser = CreateJsonParser(); - - private static Parser CreateJsonParser() - { - var value = - Deferred(); - - var @string = - DoubleQuotedString.Do(object? (s) => s); - - var number = - Number().Do(object? (n) => n); - - var primitive = OneOf(["true", "false", "null"]).Do(s => - { - object? r = null; - if (s.Length != 0 && s[0] != 'n') - r = s[0] == 't'; - return r; - }); - - var array = value - .Separated(Seq(L(','), S)) - .Between( - Seq(L('['), S), - Seq(L(']'), S)) - .Do(object? (list) => list); - - var member = Seq( - DoubleQuotedString, S, L(':'), S, value - ).Do((name, _, _, _, v) => KeyValuePair.Create(name, v)); - - var @object = member - .Separated(Seq(L(','), S)) - .Between( - Seq(L('{'), S), - Seq(L('}'), S)) - .Do(object? (members) => new Dictionary(members)); - - value.Parser = - Choice( - @string, - number, - primitive, - array, - @object - ).ThenIgnore(S); - - return S.Then(value); - } -} +using Ramstack.Parsing; + +using static Ramstack.Parsing.Literal; +using static Ramstack.Parsing.Parser; + +namespace Samples.Json; + +public static class JsonParser +{ + public static readonly Parser Parser = CreateJsonParser(); + + private static Parser CreateJsonParser() + { + var value = + Deferred(); + + var @string = + DoubleQuotedString.Do(object? (s) => s); + + var number = + Number().Do(object? (n) => n); + + var primitive = OneOf(["true", "false", "null"]).Do(s => + { + object? r = null; + if (s.Length != 0 && s[0] != 'n') + r = s[0] == 't'; + return r; + }); + + var array = value + .Separated(Seq(L(','), S)) + .Between( + Seq(L('['), S), + Seq(L(']'), S)) + .Do(object? (list) => list); + + var member = Seq( + DoubleQuotedString, S, L(':'), S, value + ).Do((name, _, _, _, v) => KeyValuePair.Create(name, v)); + + var @object = member + .Separated(Seq(L(','), S)) + .Between( + Seq(L('{'), S), + Seq(L('}'), S)) + .Do(object? (members) => new Dictionary(members)); + + value.Parser = + Choice( + @string, + number, + primitive, + array, + @object + ).ThenIgnore(S); + + return S.Then(value); + } +} diff --git a/samples/Json/README.md b/samples/Json/README.md index f6cf9ee..9bf1a9f 100644 --- a/samples/Json/README.md +++ b/samples/Json/README.md @@ -1,31 +1,31 @@ -# JSON Parser - -This project implements a simple JSON parser. - -## JSON Grammar - -```sh -start - = value $ - ; - -value - = (object / array / string / number / "true" / "false" / "null") S - ; - -object - = "{" S (member ("," S member)*)? "}" S - ; - -member - = string ":" S value - ; - -array - = "[" S (value ("," S value)*)? "]" S - ; - -S - = [ \t\r\n]* - ; -``` +# JSON Parser + +This project implements a simple JSON parser. + +## JSON Grammar + +```sh +start + = value $ + ; + +value + = (object / array / string / number / "true" / "false" / "null") S + ; + +object + = "{" S (member ("," S member)*)? "}" S + ; + +member + = string ":" S value + ; + +array + = "[" S (value ("," S value)*)? "]" S + ; + +S + = [ \t\r\n]* + ; +``` diff --git a/samples/Samples.csproj b/samples/Samples.csproj index dda17f3..0b032c5 100644 --- a/samples/Samples.csproj +++ b/samples/Samples.csproj @@ -1,15 +1,15 @@ - - - - net6.0;net7.0;net8.0 - enable - enable - latest - false - - - - - - - + + + + net6.0;net7.0;net8.0 + enable + enable + latest + false + + + + + + + diff --git a/samples/TinyC/Node.cs b/samples/TinyC/Node.cs index 1b79e6e..1e9f4dd 100644 --- a/samples/TinyC/Node.cs +++ b/samples/TinyC/Node.cs @@ -1,139 +1,139 @@ -using Samples.Utilities; - -namespace Samples.TinyC; - -public abstract record Node -{ - public static Node Empty() => new BlockNode([]); - public static Node Number(int value) => new NumberNode(value); - public static Node Variable(string name) => new VariableNode(name); - public static Node If(Node test, Node ifTrue, Node ifFalse) => new IfNode(test, Block(ifTrue), Block(ifFalse)); - public static Node Ternary(Node test, Node ifTrue, Node ifFalse) => new TernaryNode(test, ifTrue, ifFalse); - public static Node WhileLoop(Node test, Node body) => new WhileLoopNode(test, Block(body)); - public static Node DoWhileLoop(Node test, Node body) => new DoWhileLoopNode(test, Block(body)); - public static Node Unary(char op, Node operand) => new UnaryNode(op, operand); - public static Node Binary(string op, Node left, Node right) => new BinaryNode(op, left, right); - public static Node Assign(Node variable, Node value) => Binary("=", variable, value); - public static Node Block(IReadOnlyList statements) => new BlockNode(statements); - public static Node Block(Node statement) => statement is BlockNode ? statement : new BlockNode([statement]); - - public sealed override string ToString() - { - var sb = new IndentedStringBuilder(); - Print(this, sb); - return sb.ToString(); - - static void Print(Node node, IndentedStringBuilder sb) - { - switch (node) - { - case VariableNode(Name: var name): - sb.Append(name); - break; - - case NumberNode(Value: var number): - sb.Append(number); - break; - - case IfNode c: - sb.Append("if ("); - Print(c.Test, sb); - sb.Append(')'); - sb.AppendLine(); - - Print(c.IfTrue, sb); - - if (c.IfFalse is BlockNode { Statements: [IfNode ifElse] }) - { - sb.Append("else "); - Print(ifElse, sb); - } - else if (c.IfFalse is not BlockNode([])) - { - sb.Append("else"); - sb.AppendLine(); - Print(c.IfFalse, sb); - } - break; - - case TernaryNode c: - Print(c.Test, sb); - sb.IncrementIndent(); - sb - .AppendLine() - .Append("? "); - Print(c.IfTrue, sb); - - sb - .AppendLine() - .Append(": "); - Print(c.IfFalse, sb); - sb.DecrementIndent(); - break; - - case WhileLoopNode w: - sb.Append("while ("); - Print(w.Test, sb); - sb.Append(')'); - sb.AppendLine(); - Print(w.Body, sb); - break; - - case DoWhileLoopNode d: - sb.AppendLine("do"); - Print(d.Body, sb); - sb.Append("while ("); - Print(d.Test, sb); - sb.AppendLine(");"); - break; - - case UnaryNode u: - sb.Append(u.Operator); - sb.Append('('); - Print(u.Operand, sb); - sb.Append(')'); - break; - - case BinaryNode(Operator: "=", var variable, var expr): - Print(variable, sb); - sb.Append(" = ("); - Print(expr, sb); - sb.Append(')'); - break; - - case BinaryNode b: - sb.Append('('); - Print(b.Left, sb); - sb.Append($" {b.Operator} "); - Print(b.Right, sb); - sb.Append(')'); - break; - - case BlockNode block: - sb.AppendLine("{"); - sb.IncrementIndent(); - - foreach (var stmt in block.Statements) - { - Print(stmt, sb); - if (stmt is NumberNode or VariableNode or UnaryNode or BinaryNode) - sb.AppendLine(";"); - } - - sb.DecrementIndent(); - sb.AppendLine("}"); - break; - } - } - } - - public sealed record VariableNode(string Name) : Node; - public sealed record NumberNode(int Value) : Node; - public sealed record IfNode(Node Test, Node IfTrue, Node IfFalse) : Node; - public sealed record TernaryNode(Node Test, Node IfTrue, Node IfFalse) : Node; - public sealed record WhileLoopNode(Node Test, Node Body) : Node; - public sealed record DoWhileLoopNode(Node Test, Node Body) : Node; - public sealed record UnaryNode(char Operator, Node Operand) : Node; - public sealed record BinaryNode(string Operator, Node Left, Node Right) : Node; - public sealed record BlockNode(IReadOnlyList Statements) : Node; -} +using Samples.Utilities; + +namespace Samples.TinyC; + +public abstract record Node +{ + public static Node Empty() => new BlockNode([]); + public static Node Number(int value) => new NumberNode(value); + public static Node Variable(string name) => new VariableNode(name); + public static Node If(Node test, Node ifTrue, Node ifFalse) => new IfNode(test, Block(ifTrue), Block(ifFalse)); + public static Node Ternary(Node test, Node ifTrue, Node ifFalse) => new TernaryNode(test, ifTrue, ifFalse); + public static Node WhileLoop(Node test, Node body) => new WhileLoopNode(test, Block(body)); + public static Node DoWhileLoop(Node test, Node body) => new DoWhileLoopNode(test, Block(body)); + public static Node Unary(char op, Node operand) => new UnaryNode(op, operand); + public static Node Binary(string op, Node left, Node right) => new BinaryNode(op, left, right); + public static Node Assign(Node variable, Node value) => Binary("=", variable, value); + public static Node Block(IReadOnlyList statements) => new BlockNode(statements); + public static Node Block(Node statement) => statement is BlockNode ? statement : new BlockNode([statement]); + + public sealed override string ToString() + { + var sb = new IndentedStringBuilder(); + Print(this, sb); + return sb.ToString(); + + static void Print(Node node, IndentedStringBuilder sb) + { + switch (node) + { + case VariableNode(Name: var name): + sb.Append(name); + break; + + case NumberNode(Value: var number): + sb.Append(number); + break; + + case IfNode c: + sb.Append("if ("); + Print(c.Test, sb); + sb.Append(')'); + sb.AppendLine(); + + Print(c.IfTrue, sb); + + if (c.IfFalse is BlockNode { Statements: [IfNode ifElse] }) + { + sb.Append("else "); + Print(ifElse, sb); + } + else if (c.IfFalse is not BlockNode([])) + { + sb.Append("else"); + sb.AppendLine(); + Print(c.IfFalse, sb); + } + break; + + case TernaryNode c: + Print(c.Test, sb); + sb.IncrementIndent(); + sb + .AppendLine() + .Append("? "); + Print(c.IfTrue, sb); + + sb + .AppendLine() + .Append(": "); + Print(c.IfFalse, sb); + sb.DecrementIndent(); + break; + + case WhileLoopNode w: + sb.Append("while ("); + Print(w.Test, sb); + sb.Append(')'); + sb.AppendLine(); + Print(w.Body, sb); + break; + + case DoWhileLoopNode d: + sb.AppendLine("do"); + Print(d.Body, sb); + sb.Append("while ("); + Print(d.Test, sb); + sb.AppendLine(");"); + break; + + case UnaryNode u: + sb.Append(u.Operator); + sb.Append('('); + Print(u.Operand, sb); + sb.Append(')'); + break; + + case BinaryNode(Operator: "=", var variable, var expr): + Print(variable, sb); + sb.Append(" = ("); + Print(expr, sb); + sb.Append(')'); + break; + + case BinaryNode b: + sb.Append('('); + Print(b.Left, sb); + sb.Append($" {b.Operator} "); + Print(b.Right, sb); + sb.Append(')'); + break; + + case BlockNode block: + sb.AppendLine("{"); + sb.IncrementIndent(); + + foreach (var stmt in block.Statements) + { + Print(stmt, sb); + if (stmt is NumberNode or VariableNode or UnaryNode or BinaryNode) + sb.AppendLine(";"); + } + + sb.DecrementIndent(); + sb.AppendLine("}"); + break; + } + } + } + + public sealed record VariableNode(string Name) : Node; + public sealed record NumberNode(int Value) : Node; + public sealed record IfNode(Node Test, Node IfTrue, Node IfFalse) : Node; + public sealed record TernaryNode(Node Test, Node IfTrue, Node IfFalse) : Node; + public sealed record WhileLoopNode(Node Test, Node Body) : Node; + public sealed record DoWhileLoopNode(Node Test, Node Body) : Node; + public sealed record UnaryNode(char Operator, Node Operand) : Node; + public sealed record BinaryNode(string Operator, Node Left, Node Right) : Node; + public sealed record BlockNode(IReadOnlyList Statements) : Node; +} diff --git a/samples/TinyC/README.md b/samples/TinyC/README.md index e5e0033..299f75c 100644 --- a/samples/TinyC/README.md +++ b/samples/TinyC/README.md @@ -1,145 +1,145 @@ -# Tiny-C - -This project implements a parser for the [Tiny-C](http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c) language, a highly simplified version of `C` designed as an educational tool for learning about compilers. - -All variables are predefined, of integer type, and initialized to zero. - -The main differences from the original `Tiny-C` are: -- Variable names are not limited to single letters. -- Additional operators are supported. - -## Tiny-C Grammar - -```sh -start - = S statement EOF - ; - -keyword - = ("while" / "do" / "if" / "else") ![\w] - ; - -number - = [0-9]+ - ; - -variable - = !keyword [a-zA-Z_][a-zA-Z0-9_]* - ; - -S - = [ \t\n\r]* - ; - -EOF - = $ - ; - -var_expr - = variable S - ; - -number_expr - = number S - ; - -expr - = assigment_expr - / ternary_expr - ; - -assigment_expr - = var_expr "=" S expr - ; - -ternary_expr - = logical_or_expr ("?" S expr ":" S ternary_expr)? - ; - -logical_or_expr - = logical_and_expr ("||" S logical_and_expr)* - ; - -logical_and_expr - = bitwise_or_expr ("&&" S bitwise_or_expr)* - ; - -bitwise_or_expr - = bitwise_xor_expr ("|" S bitwise_xor_expr)* - ; - -bitwise_xor_expr - = bitwise_and_expr ("^" S bitwise_and_expr)* - ; - -bitwise_and_expr - = eq_expr ("&" S eq_expr)* - ; - -eq_expr - = relational_expr (("==" / "!=") S relational_expr)* - ; - -relational_expr - = shift_expr (("<" / "<=" / ">" / ">=") S shift_expr)* - ; - -shift_expr - = sum_expr (("<<" / ">>") S sum_expr)* - ; - -sum_expr - = mul_expr ([+-] S mul_expr)* - ; - -mul_expr - = unary_expr ([*/%] S unary_expr)* - ; - -unary_expr - = [-+~!]? S primary_expr - ; - -primary_expr - = parenthesis - / var_expr - / number_expr - ; - -parenthesis - = "(" S expr ")" S - ; - -statement - = if_statement - / while_statement - / do_while_statement - / block_statement - / expr_statement - / empty_statement - ; - -if_statement - = "if" S "(" S expr ")" S statement ("else" S statement)? - ; - -while_statement - = "while" S "(" S expr ")" S statement - ; - -do_while_statement - = "do" S statement "while" S "(" S expr ")" S ";" S - ; - -block_statement - = "{" S statement* "}" S - ; - -expr_statement - = expr ";" S - ; - -empty_statement - = ";" S - ; -``` +# Tiny-C + +This project implements a parser for the [Tiny-C](http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c) language, a highly simplified version of `C` designed as an educational tool for learning about compilers. + +All variables are predefined, of integer type, and initialized to zero. + +The main differences from the original `Tiny-C` are: +- Variable names are not limited to single letters. +- Additional operators are supported. + +## Tiny-C Grammar + +```sh +start + = S statement EOF + ; + +keyword + = ("while" / "do" / "if" / "else") ![\w] + ; + +number + = [0-9]+ + ; + +variable + = !keyword [a-zA-Z_][a-zA-Z0-9_]* + ; + +S + = [ \t\n\r]* + ; + +EOF + = $ + ; + +var_expr + = variable S + ; + +number_expr + = number S + ; + +expr + = assigment_expr + / ternary_expr + ; + +assigment_expr + = var_expr "=" S expr + ; + +ternary_expr + = logical_or_expr ("?" S expr ":" S ternary_expr)? + ; + +logical_or_expr + = logical_and_expr ("||" S logical_and_expr)* + ; + +logical_and_expr + = bitwise_or_expr ("&&" S bitwise_or_expr)* + ; + +bitwise_or_expr + = bitwise_xor_expr ("|" S bitwise_xor_expr)* + ; + +bitwise_xor_expr + = bitwise_and_expr ("^" S bitwise_and_expr)* + ; + +bitwise_and_expr + = eq_expr ("&" S eq_expr)* + ; + +eq_expr + = relational_expr (("==" / "!=") S relational_expr)* + ; + +relational_expr + = shift_expr (("<" / "<=" / ">" / ">=") S shift_expr)* + ; + +shift_expr + = sum_expr (("<<" / ">>") S sum_expr)* + ; + +sum_expr + = mul_expr ([+-] S mul_expr)* + ; + +mul_expr + = unary_expr ([*/%] S unary_expr)* + ; + +unary_expr + = [-+~!]? S primary_expr + ; + +primary_expr + = parenthesis + / var_expr + / number_expr + ; + +parenthesis + = "(" S expr ")" S + ; + +statement + = if_statement + / while_statement + / do_while_statement + / block_statement + / expr_statement + / empty_statement + ; + +if_statement + = "if" S "(" S expr ")" S statement ("else" S statement)? + ; + +while_statement + = "while" S "(" S expr ")" S statement + ; + +do_while_statement + = "do" S statement "while" S "(" S expr ")" S ";" S + ; + +block_statement + = "{" S statement* "}" S + ; + +expr_statement + = expr ";" S + ; + +empty_statement + = ";" S + ; +``` diff --git a/samples/TinyC/TinyCParser.cs b/samples/TinyC/TinyCParser.cs index 5e63749..f1731c6 100644 --- a/samples/TinyC/TinyCParser.cs +++ b/samples/TinyC/TinyCParser.cs @@ -1,258 +1,258 @@ -using System.Diagnostics.CodeAnalysis; - -using Ramstack.Parsing; - -using static Ramstack.Parsing.Parser; - -namespace Samples.TinyC; - -public static class TinyCParser -{ - public static readonly Parser Parser = CreateParser(); - - [SuppressMessage("ReSharper", "InconsistentNaming")] - private static Parser CreateParser() - { - var keyword = - Seq( - OneOf("while", "do", "if", "else"), - Not(Set("\\w"))); - - var number = - Set("0-9") - .OneOrMore() - .Map(Node (m) => Node.Number(int.Parse(m))) - .As("number"); - - var variable = - Seq( - Not(keyword), - Set("a-z"), - Set("a-zA-Z_0-9").ZeroOrMore() - ).Map(Identifier).As("variable"); - - var multiline_comment = - Seq( - L("/*"), - Choice( - Any, - Eof.Then(Error("'*/'")) - ).Until(L("*/")), - L("*/") - ).Void(); - - var single_comment = - Seq( - L("//"), - Any.Until(Eol), - Eol - ).Void(); - - var ws = - Choice( - single_comment, - multiline_comment, - S - ).ZeroOrMore().Void(); - - var semicolon = - Seq(L(';'), ws).Void(); - - var eq = - Seq(L('='), ws).Void(); - - var if_keyword = - Seq(L("if"), ws).Void(); - - var else_keyword = - Seq(L("else"), ws).Void(); - - var while_keyword = - Seq(L("while"), ws).Void(); - - var do_keyword = - Seq(L("do"), ws).Void(); - - var expr = - Deferred(); - - var number_expr = - number.ThenIgnore(ws); - - var var_expr = - variable.ThenIgnore(ws); - - var parenthesis = - expr.Between( - Seq(L('('), ws), - Seq(L(')'), ws)); - - var primary_expr = - Choice( - parenthesis, - number_expr, - var_expr); - - var unary_expr = - Seq( - OneOf("-+~!").Optional(), - ws, - primary_expr - ).Do(CreateUnary); - - var mul_expr = unary_expr.Fold( - OneOf("*/%").ThenIgnore(ws), - CreateBinary); - - var sum_expr = - mul_expr.Fold( - OneOf("+-").ThenIgnore(ws), - CreateBinary); - - var shift_expr = - sum_expr.Fold( - OneOf("<<", ">>").ThenIgnore(ws), - (l, r, o) => Node.Binary(o, l, r)); - - var relational_expr = - shift_expr.Fold( - OneOf("<", "<=", ">", ">=").ThenIgnore(ws), - (l, r, o) => Node.Binary(o, l, r)); - - var eq_expr = - relational_expr.Fold( - OneOf("==", "!=").ThenIgnore(ws), - (l, r, o) => Node.Binary(o, l, r)); - - var bitwise_and_expr = - eq_expr.Fold( - L('&').ThenIgnore(ws), - (l, r, _) => Node.Binary("&", l, r)); - - var bitwise_xor_expr = - bitwise_and_expr.Fold( - L('^').ThenIgnore(ws), - (l, r, _) => Node.Binary("^", l, r)); - - var bitwise_or_expr = - bitwise_xor_expr.Fold( - L('|').ThenIgnore(ws), - (l, r, _) => Node.Binary("|", l, r)); - - var logical_and_expr = - bitwise_or_expr.Fold( - L("&&").ThenIgnore(ws), - (l, r, _) => Node.Binary("&&", l, r)); - - var logical_or_expr = - logical_and_expr.Fold( - L("||").ThenIgnore(ws), - (l, r, _) => Node.Binary("||", l, r)); - - var ternary_expr = Deferred(); - ternary_expr.Parser = - Seq( - logical_or_expr, - Seq( - L('?'), ws, expr, - L(':'), ws, ternary_expr).Optional() - ).Do(CreateTernary); - - var assignment_expr = - Seq(var_expr, eq, expr).Do(CreateAssign); - - expr.Parser = - Choice( - assignment_expr, - ternary_expr); - - var statement = - Deferred(); - - var else_clause = - Seq( - else_keyword, - statement - ).Do((_, s) => s).DefaultOnFail(Node.Empty()); - - var if_statement = - Seq( - if_keyword, - parenthesis, - statement, - else_clause - ).Do(CreateIf); - - var block_statement = - statement - .ZeroOrMore() - .Between( - Seq(L('{'), ws), - Seq(L('}'), ws)) - .Do(CreateBlock); - - var empty_statement = - Seq(L(';'), ws - ).Map(_ => Node.Empty()); - - var while_statement = - Seq( - while_keyword, - parenthesis, - statement - ).Do(CreateWhile); - - var do_while_statement = - Seq( - do_keyword, - statement, - while_keyword, - parenthesis, - semicolon - ).Do(CreateDoWhile); - - var expr_statement = - expr.ThenIgnore(semicolon); - - statement.Parser = - Choice( - if_statement, - while_statement, - do_while_statement, - block_statement, - expr_statement, - empty_statement); - - return statement - .Between(ws, Eof); - - static Node Identifier(Match m) => - Node.Variable(m.ToString()); - - static Node CreateUnary(OptionalValue u, Unit _, Node operand) => - u.HasValue ? Node.Unary(u.Value, operand) : operand; - - static Node CreateBinary(Node l, Node r, char o) => - Node.Binary(new string(o, 1), l, r); - - static Node CreateAssign(Node variable, Unit _, Node value) => - Node.Assign(variable, value); - - static Node CreateTernary(Node expression, OptionalValue<(char _1, Unit _2, Node @true, char _4, Unit _5, Node @false)> optional) => - optional.HasValue - ? Node.Ternary(expression, optional.Value.@true, optional.Value.@false) - : expression; - - static Node CreateIf(Unit _, Node expression, Node @true, Node @false) => - Node.If(expression, @true, @false); - - static Node CreateWhile(Unit _, Node expression, Node body) => - Node.WhileLoop(expression, body); - - static Node CreateDoWhile(Unit _0, Node body, Unit _2, Node expression, Unit _4) => - Node.DoWhileLoop(expression, body); - - static Node CreateBlock(IReadOnlyList statements) => - Node.Block(statements); - } -} +using System.Diagnostics.CodeAnalysis; + +using Ramstack.Parsing; + +using static Ramstack.Parsing.Parser; + +namespace Samples.TinyC; + +public static class TinyCParser +{ + public static readonly Parser Parser = CreateParser(); + + [SuppressMessage("ReSharper", "InconsistentNaming")] + private static Parser CreateParser() + { + var keyword = + Seq( + OneOf("while", "do", "if", "else"), + Not(Set("\\w"))); + + var number = + Set("0-9") + .OneOrMore() + .Map(Node (m) => Node.Number(int.Parse(m))) + .As("number"); + + var variable = + Seq( + Not(keyword), + Set("a-z"), + Set("a-zA-Z_0-9").ZeroOrMore() + ).Map(Identifier).As("variable"); + + var multiline_comment = + Seq( + L("/*"), + Choice( + Any, + Eof.Then(Error("'*/'")) + ).Until(L("*/")), + L("*/") + ).Void(); + + var single_comment = + Seq( + L("//"), + Any.Until(Eol), + Eol + ).Void(); + + var ws = + Choice( + single_comment, + multiline_comment, + S + ).ZeroOrMore().Void(); + + var semicolon = + Seq(L(';'), ws).Void(); + + var eq = + Seq(L('='), ws).Void(); + + var if_keyword = + Seq(L("if"), ws).Void(); + + var else_keyword = + Seq(L("else"), ws).Void(); + + var while_keyword = + Seq(L("while"), ws).Void(); + + var do_keyword = + Seq(L("do"), ws).Void(); + + var expr = + Deferred(); + + var number_expr = + number.ThenIgnore(ws); + + var var_expr = + variable.ThenIgnore(ws); + + var parenthesis = + expr.Between( + Seq(L('('), ws), + Seq(L(')'), ws)); + + var primary_expr = + Choice( + parenthesis, + number_expr, + var_expr); + + var unary_expr = + Seq( + OneOf("-+~!").Optional(), + ws, + primary_expr + ).Do(CreateUnary); + + var mul_expr = unary_expr.Fold( + OneOf("*/%").ThenIgnore(ws), + CreateBinary); + + var sum_expr = + mul_expr.Fold( + OneOf("+-").ThenIgnore(ws), + CreateBinary); + + var shift_expr = + sum_expr.Fold( + OneOf("<<", ">>").ThenIgnore(ws), + (l, r, o) => Node.Binary(o, l, r)); + + var relational_expr = + shift_expr.Fold( + OneOf("<", "<=", ">", ">=").ThenIgnore(ws), + (l, r, o) => Node.Binary(o, l, r)); + + var eq_expr = + relational_expr.Fold( + OneOf("==", "!=").ThenIgnore(ws), + (l, r, o) => Node.Binary(o, l, r)); + + var bitwise_and_expr = + eq_expr.Fold( + L('&').ThenIgnore(ws), + (l, r, _) => Node.Binary("&", l, r)); + + var bitwise_xor_expr = + bitwise_and_expr.Fold( + L('^').ThenIgnore(ws), + (l, r, _) => Node.Binary("^", l, r)); + + var bitwise_or_expr = + bitwise_xor_expr.Fold( + L('|').ThenIgnore(ws), + (l, r, _) => Node.Binary("|", l, r)); + + var logical_and_expr = + bitwise_or_expr.Fold( + L("&&").ThenIgnore(ws), + (l, r, _) => Node.Binary("&&", l, r)); + + var logical_or_expr = + logical_and_expr.Fold( + L("||").ThenIgnore(ws), + (l, r, _) => Node.Binary("||", l, r)); + + var ternary_expr = Deferred(); + ternary_expr.Parser = + Seq( + logical_or_expr, + Seq( + L('?'), ws, expr, + L(':'), ws, ternary_expr).Optional() + ).Do(CreateTernary); + + var assignment_expr = + Seq(var_expr, eq, expr).Do(CreateAssign); + + expr.Parser = + Choice( + assignment_expr, + ternary_expr); + + var statement = + Deferred(); + + var else_clause = + Seq( + else_keyword, + statement + ).Do((_, s) => s).DefaultOnFail(Node.Empty()); + + var if_statement = + Seq( + if_keyword, + parenthesis, + statement, + else_clause + ).Do(CreateIf); + + var block_statement = + statement + .ZeroOrMore() + .Between( + Seq(L('{'), ws), + Seq(L('}'), ws)) + .Do(CreateBlock); + + var empty_statement = + Seq(L(';'), ws + ).Map(_ => Node.Empty()); + + var while_statement = + Seq( + while_keyword, + parenthesis, + statement + ).Do(CreateWhile); + + var do_while_statement = + Seq( + do_keyword, + statement, + while_keyword, + parenthesis, + semicolon + ).Do(CreateDoWhile); + + var expr_statement = + expr.ThenIgnore(semicolon); + + statement.Parser = + Choice( + if_statement, + while_statement, + do_while_statement, + block_statement, + expr_statement, + empty_statement); + + return statement + .Between(ws, Eof); + + static Node Identifier(Match m) => + Node.Variable(m.ToString()); + + static Node CreateUnary(OptionalValue u, Unit _, Node operand) => + u.HasValue ? Node.Unary(u.Value, operand) : operand; + + static Node CreateBinary(Node l, Node r, char o) => + Node.Binary(new string(o, 1), l, r); + + static Node CreateAssign(Node variable, Unit _, Node value) => + Node.Assign(variable, value); + + static Node CreateTernary(Node expression, OptionalValue<(char _1, Unit _2, Node @true, char _4, Unit _5, Node @false)> optional) => + optional.HasValue + ? Node.Ternary(expression, optional.Value.@true, optional.Value.@false) + : expression; + + static Node CreateIf(Unit _, Node expression, Node @true, Node @false) => + Node.If(expression, @true, @false); + + static Node CreateWhile(Unit _, Node expression, Node body) => + Node.WhileLoop(expression, body); + + static Node CreateDoWhile(Unit _0, Node body, Unit _2, Node expression, Unit _4) => + Node.DoWhileLoop(expression, body); + + static Node CreateBlock(IReadOnlyList statements) => + Node.Block(statements); + } +} diff --git a/samples/TinyC/examples/example1.c b/samples/TinyC/examples/example1.c index b7c7209..7f4a779 100644 --- a/samples/TinyC/examples/example1.c +++ b/samples/TinyC/examples/example1.c @@ -1,21 +1,21 @@ -/* - * Tiny-C: Example 1. - */ - -{ - ///////////////////// - ///// Factorial ///// - ///////////////////// - x = 5; - fact = 1; - - if (x > 0) - { - do - { - fact = fact * x; - x = x - 1; - } - while (x != 0); - } -} +/* + * Tiny-C: Example 1. + */ + +{ + ///////////////////// + ///// Factorial ///// + ///////////////////// + x = 5; + fact = 1; + + if (x > 0) + { + do + { + fact = fact * x; + x = x - 1; + } + while (x != 0); + } +} diff --git a/samples/TinyC/examples/example2.c b/samples/TinyC/examples/example2.c index 2a097b0..592c76a 100644 --- a/samples/TinyC/examples/example2.c +++ b/samples/TinyC/examples/example2.c @@ -1,11 +1,11 @@ -{ - i = 100; - while (i) - { - i = i - 1; - } - - i = 0; - while (i * 2 < 100) - i = i + 1; -} +{ + i = 100; + while (i) + { + i = i - 1; + } + + i = 0; + while (i * 2 < 100) + i = i + 1; +} diff --git a/samples/TinyC/examples/example3.c b/samples/TinyC/examples/example3.c index 3b76edf..75ac0c3 100644 --- a/samples/TinyC/examples/example3.c +++ b/samples/TinyC/examples/example3.c @@ -1,26 +1,26 @@ -if (i > 100) -{ - if (j == 0) - a = 1; - else - a = 2; -} -else if (i < 100) -{ - if (j == 0) a = 10; - else if (j == 1) a = 20; - else if (j == 2) a = 30; - else if (j == 3) a = 40; - else if (j == 4) a = 50; - else if (j == 5) a = 60; - else a = 70; -} -else -{ - b = j == 0 ? 10 : - j == 1 ? 20 : - j == 2 ? 30 : - j == 3 ? 40 : - j == 4 ? 50 : - j == 5 ? 60 : 70; -} +if (i > 100) +{ + if (j == 0) + a = 1; + else + a = 2; +} +else if (i < 100) +{ + if (j == 0) a = 10; + else if (j == 1) a = 20; + else if (j == 2) a = 30; + else if (j == 3) a = 40; + else if (j == 4) a = 50; + else if (j == 5) a = 60; + else a = 70; +} +else +{ + b = j == 0 ? 10 : + j == 1 ? 20 : + j == 2 ? 30 : + j == 3 ? 40 : + j == 4 ? 50 : + j == 5 ? 60 : 70; +} diff --git a/samples/TinyC/examples/example4.c b/samples/TinyC/examples/example4.c index d614b20..a12a81b 100644 --- a/samples/TinyC/examples/example4.c +++ b/samples/TinyC/examples/example4.c @@ -1,31 +1,31 @@ -{ - index = a = b = c = d = 0; - - { - d = 199; - - ; - ; - ; - } - - j = 128; - i = 0; - - if (j * 2 == 128 << 1) - { - do - { - i = i + 1; - j = j - 1; - } - while (i <= j); - } - - x = !0; - y = !1; - z = ~x + ~y ? a : b; - n = z - ? a < 0 ? b + 100 * c : c % 100 + 1 - : b > 0 ? x : (y = 23 & -7); -} +{ + index = a = b = c = d = 0; + + { + d = 199; + + ; + ; + ; + } + + j = 128; + i = 0; + + if (j * 2 == 128 << 1) + { + do + { + i = i + 1; + j = j - 1; + } + while (i <= j); + } + + x = !0; + y = !1; + z = ~x + ~y ? a : b; + n = z + ? a < 0 ? b + 100 * c : c % 100 + 1 + : b > 0 ? x : (y = 23 & -7); +} diff --git a/samples/Utilities/IndentedStringBuilder.cs b/samples/Utilities/IndentedStringBuilder.cs index f523f0c..791bb3e 100644 --- a/samples/Utilities/IndentedStringBuilder.cs +++ b/samples/Utilities/IndentedStringBuilder.cs @@ -1,76 +1,76 @@ -using System.Text; - -namespace Samples.Utilities; - -internal sealed class IndentedStringBuilder -{ - private int _indent; - private bool _indentPending = true; - - private readonly StringBuilder _sb = new StringBuilder(); - - public IndentedStringBuilder Append(char value) - { - DoIndent(); - _sb.Append(value); - return this; - } - - public IndentedStringBuilder Append(int value) - { - DoIndent(); - _sb.Append(value); - return this; - } - - public IndentedStringBuilder Append(string value) - { - DoIndent(); - _sb.Append(value); - return this; - } - - public IndentedStringBuilder Append(FormattableString value) - { - DoIndent(); - _sb.Append(value); - return this; - } - - public IndentedStringBuilder AppendLine() - { - AppendLine(string.Empty); - return this; - } - - public IndentedStringBuilder AppendLine(string value) - { - DoIndent(); - _sb.AppendLine(value); - _indentPending = true; - return this; - } - - public IndentedStringBuilder IncrementIndent() - { - _indent++; - return this; - } - - public IndentedStringBuilder DecrementIndent() - { - _indent = Math.Max(0, _indent - 1); - return this; - } - - public override string ToString() => - _sb.ToString(); - - private void DoIndent() - { - if (_indentPending && _indent != 0) - _sb.Append(' ', _indent * 4); - - _indentPending = false; - } -} +using System.Text; + +namespace Samples.Utilities; + +internal sealed class IndentedStringBuilder +{ + private int _indent; + private bool _indentPending = true; + + private readonly StringBuilder _sb = new StringBuilder(); + + public IndentedStringBuilder Append(char value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder Append(int value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder Append(string value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder Append(FormattableString value) + { + DoIndent(); + _sb.Append(value); + return this; + } + + public IndentedStringBuilder AppendLine() + { + AppendLine(string.Empty); + return this; + } + + public IndentedStringBuilder AppendLine(string value) + { + DoIndent(); + _sb.AppendLine(value); + _indentPending = true; + return this; + } + + public IndentedStringBuilder IncrementIndent() + { + _indent++; + return this; + } + + public IndentedStringBuilder DecrementIndent() + { + _indent = Math.Max(0, _indent - 1); + return this; + } + + public override string ToString() => + _sb.ToString(); + + private void DoIndent() + { + if (_indentPending && _indent != 0) + _sb.Append(' ', _indent * 4); + + _indentPending = false; + } +} diff --git a/src/Ramstack.Parsing/Argument.cs b/src/Ramstack.Parsing/Argument.cs index d0e7ad0..11427b6 100644 --- a/src/Ramstack.Parsing/Argument.cs +++ b/src/Ramstack.Parsing/Argument.cs @@ -1,302 +1,302 @@ -namespace Ramstack.Parsing; - -/// -/// Provides helper methods to validate arguments and throw exceptions if the arguments do not meet specified conditions. -/// -internal static class Argument -{ - #if NET8_0_OR_GREATER - /// - /// Throws an if is less than . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators - { - if (value < other) - Error_ArgumentLess(value, other, paramName); - } - - /// - /// Throws an if is less than or equal to . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators - { - if (value <= other) - Error_ArgumentLessOrEqual(value, other, paramName); - } - - /// - /// Throws an if is greater than . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators - { - if (value > other) - Error_ArgumentGreater(value, other, paramName); - } - - /// - /// Throws an if is greater than or equal to . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators - { - if (value >= other) - Error_ArgumentGreaterOrEqual(value, other, paramName); - } - - /// - /// Throws an if is negative. - /// - /// The type of the value being checked, which implements . - /// The value to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNegative(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.INumberBase - { - if (T.IsNegative(value)) - Error_ArgumentNegative(value, paramName); - } - - /// - /// Throws an if is negative or zero. - /// - /// The type of the value being checked, which implements . - /// The value to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNegativeOrZero(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.INumberBase - { - if (T.IsNegative(value) || T.IsZero(value)) - Error_ArgumentNegativeOrZero(value, paramName); - } - - /// - /// Throws an if is equal to . - /// - /// The type of the values being compared, which implements . - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IEqualityOperators - { - if (value == other) - Error_ArgumentEqual(value, other, paramName); - } - - /// - /// Throws an if is not equal to . - /// - /// The type of the values being compared, which implements . - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IEqualityOperators - { - if (value != other) - Error_ArgumentNotEqual(value, other, paramName); - } - #else - /// - /// Throws an if is less than . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(other) < 0) - Error_ArgumentLess(value, other, paramName); - } - - /// - /// Throws an if is less than or equal to . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(other) <= 0) - Error_ArgumentLessOrEqual(value, other, paramName); - } - - /// - /// Throws an if is greater than . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(other) > 0) - Error_ArgumentGreater(value, other, paramName); - } - - /// - /// Throws an if is greater than or equal to . - /// - /// The type of the values being compared. - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(other) >= 0) - Error_ArgumentGreaterOrEqual(value, other, paramName); - } - - /// - /// Throws an if is negative. - /// - /// The type of the value being checked, which implements . - /// The value to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNegative(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(default) < 0) - Error_ArgumentNegative(value, paramName); - } - - /// - /// Throws an if is negative or zero. - /// - /// The type of the value being checked, which implements . - /// The value to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNegativeOrZero(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable - { - if (value.CompareTo(default) <= 0) - Error_ArgumentNegativeOrZero(value, paramName); - } - - /// - /// Throws an if is equal to . - /// - /// The type of the values being compared, which implements . - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : IEquatable? - { - if (EqualityComparer.Default.Equals(value, other)) - Error_ArgumentEqual(value, other, paramName); - } - - /// - /// Throws an if is not equal to . - /// - /// The type of the values being compared, which implements . - /// The value to check. - /// The reference value to compare against. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : IEquatable? - { - if (EqualityComparer.Default.Equals(value, other)) - Error_ArgumentNotEqual(value, other, paramName); - } - #endif - - /// - /// Throws an if the specified object is . - /// - /// The object to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (value is null) - Error_ArgumentNull(paramName); - } - - /// - /// Throws an if the specified span is empty. - /// - /// The span to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfEmpty(ReadOnlySpan value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (value.IsEmpty) - Error_ArgumentEmpty(paramName); - } - - /// - /// Throws an if the specified string is or empty. - /// - /// The string to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (string.IsNullOrEmpty(value)) - Error_ArgumentNullOrEmpty(paramName); - } - - /// - /// Throws an if the specified array is or empty. - /// - /// The type of the array elements. - /// The array to check. - /// The name of the parameter being checked. Automatically supplied by the compiler. - public static void ThrowIfNullOrEmpty([NotNull] T[]? value, [CallerArgumentExpression("value")] string? paramName = null) - { - if (value is null || value.Length == 0) - Error_ArgumentNullOrEmpty(paramName); - } - - [DoesNotReturn] - private static void Error_ArgumentNull(string? paramName) => - throw new ArgumentNullException(paramName); - - [DoesNotReturn] - private static void Error_ArgumentEmpty(string? paramName) => - throw new ArgumentException("Argument should not be empty.", paramName); - - [DoesNotReturn] - private static void Error_ArgumentNullOrEmpty(string? paramName) => - throw new ArgumentException("Argument should not be null or empty.", paramName); - - [DoesNotReturn] - private static void Error_ArgumentEqual(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must not be equal to '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentNotEqual(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be equal to '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentLess(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than or equal to '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentLessOrEqual(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentGreater(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than or equal to '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentGreaterOrEqual(T value, T other, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than '{other}'."); - - [DoesNotReturn] - private static void Error_ArgumentNegative(T value, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than or equal to zero."); - - [DoesNotReturn] - private static void Error_ArgumentNegativeOrZero(T value, string? paramName) => - throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than zero."); -} +namespace Ramstack.Parsing; + +/// +/// Provides helper methods to validate arguments and throw exceptions if the arguments do not meet specified conditions. +/// +internal static class Argument +{ + #if NET8_0_OR_GREATER + /// + /// Throws an if is less than . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators + { + if (value < other) + Error_ArgumentLess(value, other, paramName); + } + + /// + /// Throws an if is less than or equal to . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators + { + if (value <= other) + Error_ArgumentLessOrEqual(value, other, paramName); + } + + /// + /// Throws an if is greater than . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators + { + if (value > other) + Error_ArgumentGreater(value, other, paramName); + } + + /// + /// Throws an if is greater than or equal to . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IComparisonOperators + { + if (value >= other) + Error_ArgumentGreaterOrEqual(value, other, paramName); + } + + /// + /// Throws an if is negative. + /// + /// The type of the value being checked, which implements . + /// The value to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNegative(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.INumberBase + { + if (T.IsNegative(value)) + Error_ArgumentNegative(value, paramName); + } + + /// + /// Throws an if is negative or zero. + /// + /// The type of the value being checked, which implements . + /// The value to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNegativeOrZero(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.INumberBase + { + if (T.IsNegative(value) || T.IsZero(value)) + Error_ArgumentNegativeOrZero(value, paramName); + } + + /// + /// Throws an if is equal to . + /// + /// The type of the values being compared, which implements . + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IEqualityOperators + { + if (value == other) + Error_ArgumentEqual(value, other, paramName); + } + + /// + /// Throws an if is not equal to . + /// + /// The type of the values being compared, which implements . + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, System.Numerics.IEqualityOperators + { + if (value != other) + Error_ArgumentNotEqual(value, other, paramName); + } + #else + /// + /// Throws an if is less than . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfLessThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(other) < 0) + Error_ArgumentLess(value, other, paramName); + } + + /// + /// Throws an if is less than or equal to . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(other) <= 0) + Error_ArgumentLessOrEqual(value, other, paramName); + } + + /// + /// Throws an if is greater than . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(other) > 0) + Error_ArgumentGreater(value, other, paramName); + } + + /// + /// Throws an if is greater than or equal to . + /// + /// The type of the values being compared. + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(other) >= 0) + Error_ArgumentGreaterOrEqual(value, other, paramName); + } + + /// + /// Throws an if is negative. + /// + /// The type of the value being checked, which implements . + /// The value to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNegative(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(default) < 0) + Error_ArgumentNegative(value, paramName); + } + + /// + /// Throws an if is negative or zero. + /// + /// The type of the value being checked, which implements . + /// The value to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNegativeOrZero(T value, [CallerArgumentExpression("value")] string? paramName = null) where T : struct, IComparable + { + if (value.CompareTo(default) <= 0) + Error_ArgumentNegativeOrZero(value, paramName); + } + + /// + /// Throws an if is equal to . + /// + /// The type of the values being compared, which implements . + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : IEquatable? + { + if (EqualityComparer.Default.Equals(value, other)) + Error_ArgumentEqual(value, other, paramName); + } + + /// + /// Throws an if is not equal to . + /// + /// The type of the values being compared, which implements . + /// The value to check. + /// The reference value to compare against. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression("value")] string? paramName = null) where T : IEquatable? + { + if (EqualityComparer.Default.Equals(value, other)) + Error_ArgumentNotEqual(value, other, paramName); + } + #endif + + /// + /// Throws an if the specified object is . + /// + /// The object to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNull([NotNull] object? value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (value is null) + Error_ArgumentNull(paramName); + } + + /// + /// Throws an if the specified span is empty. + /// + /// The span to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfEmpty(ReadOnlySpan value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (value.IsEmpty) + Error_ArgumentEmpty(paramName); + } + + /// + /// Throws an if the specified string is or empty. + /// + /// The string to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNullOrEmpty([NotNull] string? value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (string.IsNullOrEmpty(value)) + Error_ArgumentNullOrEmpty(paramName); + } + + /// + /// Throws an if the specified array is or empty. + /// + /// The type of the array elements. + /// The array to check. + /// The name of the parameter being checked. Automatically supplied by the compiler. + public static void ThrowIfNullOrEmpty([NotNull] T[]? value, [CallerArgumentExpression("value")] string? paramName = null) + { + if (value is null || value.Length == 0) + Error_ArgumentNullOrEmpty(paramName); + } + + [DoesNotReturn] + private static void Error_ArgumentNull(string? paramName) => + throw new ArgumentNullException(paramName); + + [DoesNotReturn] + private static void Error_ArgumentEmpty(string? paramName) => + throw new ArgumentException("Argument should not be empty.", paramName); + + [DoesNotReturn] + private static void Error_ArgumentNullOrEmpty(string? paramName) => + throw new ArgumentException("Argument should not be null or empty.", paramName); + + [DoesNotReturn] + private static void Error_ArgumentEqual(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must not be equal to '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentNotEqual(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be equal to '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentLess(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than or equal to '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentLessOrEqual(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentGreater(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than or equal to '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentGreaterOrEqual(T value, T other, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be less than '{other}'."); + + [DoesNotReturn] + private static void Error_ArgumentNegative(T value, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than or equal to zero."); + + [DoesNotReturn] + private static void Error_ArgumentNegativeOrZero(T value, string? paramName) => + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be greater than zero."); +} diff --git a/src/Ramstack.Parsing/Character.cs b/src/Ramstack.Parsing/Character.cs index 77b5d41..e1552de 100644 --- a/src/Ramstack.Parsing/Character.cs +++ b/src/Ramstack.Parsing/Character.cs @@ -1,87 +1,87 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -/// -/// Provides parsers for character-based parsing operations. -/// -public static class Character -{ - /// - /// Gets a parser that matches a single letter character. - /// - public static Parser Letter => L(GeneralUnicodeCategory.Letter).As("letter"); - - /// - /// Gets a parser that matches a single uppercase letter. - /// - public static Parser Uppercase => L(GeneralUnicodeCategory.UppercaseLetter).As("uppercase letter"); - - /// - /// Gets a parser that matches a single lowercase letter. - /// - public static Parser Lowercase => L(GeneralUnicodeCategory.LowercaseLetter).As("lowercase letter"); - - /// - /// Gets a parser that matches a single digit character. - /// - public static Parser Digit => L(GeneralUnicodeCategory.DecimalDigitNumber).As("digit"); - - /// - /// Gets a parser that matches a single letter or digit character. - /// - public static Parser LetterOrDigit => L(GeneralUnicodeCategory.Letter | GeneralUnicodeCategory.DecimalDigitNumber); - - /// - /// Gets a parser that matches a single number character. - /// - public static Parser Number => L(GeneralUnicodeCategory.Number); - - /// - /// Gets a parser that matches a single symbol character. - /// - public static Parser Symbol => L(GeneralUnicodeCategory.Symbol); - - /// - /// Gets a parser that matches a single separator character. - /// - public static Parser Separator => L(GeneralUnicodeCategory.Separator); - - /// - /// Gets a parser that matches a single punctuation character. - /// - public static Parser Punctuation => L(GeneralUnicodeCategory.Punctuation); - - /// - /// Gets a parser that matches a single control character. - /// - public static Parser Control => L(GeneralUnicodeCategory.Control); - - /// - /// Gets a parser that matches any surrogate character (high or low). - /// - public static Parser Surrogate => Set('\uD800', '\uDFFF').As("surrogate"); - - /// - /// Gets a parser that matches a high surrogate character (U+D800 to U+DBFF). - /// - public static Parser HighSurrogate => Set('\uD800', '\uDBFF').As("high-surrogate"); - - /// - /// Gets a parser that matches a low surrogate character (U+DC00 to U+DFFF). - /// - public static Parser LowSurrogate => Set('\uDC00', '\uDFFF').As("low-surrogate"); - - /// - /// Gets a parser that matches a single whitespace character. - /// - public static Parser WhiteSpace => Choice( - OneOf("\t\n\v\f\r\u0085 "), - L(GeneralUnicodeCategory.Separator) - ).As("whitespace"); - - /// - /// Gets a parser that matches a single hexadecimal digit (0..9, A..F, a..f). - /// - public static Parser HexDigit => Set("0-9A-Fa-f").As("hexadecimal"); -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +/// +/// Provides parsers for character-based parsing operations. +/// +public static class Character +{ + /// + /// Gets a parser that matches a single letter character. + /// + public static Parser Letter => L(GeneralUnicodeCategory.Letter).As("letter"); + + /// + /// Gets a parser that matches a single uppercase letter. + /// + public static Parser Uppercase => L(GeneralUnicodeCategory.UppercaseLetter).As("uppercase letter"); + + /// + /// Gets a parser that matches a single lowercase letter. + /// + public static Parser Lowercase => L(GeneralUnicodeCategory.LowercaseLetter).As("lowercase letter"); + + /// + /// Gets a parser that matches a single digit character. + /// + public static Parser Digit => L(GeneralUnicodeCategory.DecimalDigitNumber).As("digit"); + + /// + /// Gets a parser that matches a single letter or digit character. + /// + public static Parser LetterOrDigit => L(GeneralUnicodeCategory.Letter | GeneralUnicodeCategory.DecimalDigitNumber); + + /// + /// Gets a parser that matches a single number character. + /// + public static Parser Number => L(GeneralUnicodeCategory.Number); + + /// + /// Gets a parser that matches a single symbol character. + /// + public static Parser Symbol => L(GeneralUnicodeCategory.Symbol); + + /// + /// Gets a parser that matches a single separator character. + /// + public static Parser Separator => L(GeneralUnicodeCategory.Separator); + + /// + /// Gets a parser that matches a single punctuation character. + /// + public static Parser Punctuation => L(GeneralUnicodeCategory.Punctuation); + + /// + /// Gets a parser that matches a single control character. + /// + public static Parser Control => L(GeneralUnicodeCategory.Control); + + /// + /// Gets a parser that matches any surrogate character (high or low). + /// + public static Parser Surrogate => Set('\uD800', '\uDFFF').As("surrogate"); + + /// + /// Gets a parser that matches a high surrogate character (U+D800 to U+DBFF). + /// + public static Parser HighSurrogate => Set('\uD800', '\uDBFF').As("high-surrogate"); + + /// + /// Gets a parser that matches a low surrogate character (U+DC00 to U+DFFF). + /// + public static Parser LowSurrogate => Set('\uDC00', '\uDFFF').As("low-surrogate"); + + /// + /// Gets a parser that matches a single whitespace character. + /// + public static Parser WhiteSpace => Choice( + OneOf("\t\n\v\f\r\u0085 "), + L(GeneralUnicodeCategory.Separator) + ).As("whitespace"); + + /// + /// Gets a parser that matches a single hexadecimal digit (0..9, A..F, a..f). + /// + public static Parser HexDigit => Set("0-9A-Fa-f").As("hexadecimal"); +} diff --git a/src/Ramstack.Parsing/Collections/ArrayBuilder.cs b/src/Ramstack.Parsing/Collections/ArrayBuilder.cs index 12eadc4..2b11939 100644 --- a/src/Ramstack.Parsing/Collections/ArrayBuilder.cs +++ b/src/Ramstack.Parsing/Collections/ArrayBuilder.cs @@ -1,213 +1,213 @@ -namespace Ramstack.Parsing.Collections; - -/// -/// Represents an array builder for elements of type . -/// -/// -/// This structure is optimized for performance and relies on proper initialization. -/// -/// Always initialize instances using one of the available constructors with the new keyword, -/// including the parameterless constructor: new ArrayBuilder<T>(). -/// -/// Do not use default(ArrayBuilder<T>) or similar patterns, as this results -/// in an uninitialized instance with an invalid internal state, -/// causing a when methods are called. -/// -/// This design choice was made to avoid unnecessary overhead and maintain control over -/// internal state initialization in modern C# versions where parameterless constructors -/// in structs are supported. -/// -/// The type of elements stored in the array builder. -[DebuggerDisplay("Count = {Count}")] -[DebuggerTypeProxy(typeof(ArrayBuilderDebugView<>))] -internal struct ArrayBuilder -{ - private T[] _array; - private int _count; - - /// - /// Gets the number of items contained in the . - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public readonly int Count => _count; - - /// - /// Gets the underlying buffer. - /// - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public readonly T[] InnerBuffer => _array; - - /// - /// Gets an item at the specified index. - /// - /// The zero-based index of the item to get. - public readonly ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - var array = _array; - if ((uint)index >= (uint)_count || (uint)index >= (uint)array.Length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return ref array[index]; - } - } - - /// - /// Initializes a new instance of the . - /// - public ArrayBuilder() - { - _count = 0; - _array = []; - } - - /// - /// Initializes a new instance of the with the specified buffer capacity. - /// - /// The initial capacity of the buffer. - public ArrayBuilder(int capacity) - { - _count = 0; - _array = new T[capacity]; - } - - /// - /// Adds an item to the underlying array, resizing it if necessary. - /// - /// The item to add. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - var array = _array; - var count = _count; - - if ((uint)count < (uint)array.Length) - { - array[count] = item; - _count = count + 1; - } - else - { - AddSlow(item); - } - } - - /// - /// Attempts to add an item to the underlying array, without throwing an exception if there is no room. - /// - /// The item to add. - /// - /// Use this method if you know there is enough space in the - /// for another item, and you are writing performance-sensitive code. - /// - /// - /// if the item was added to the underlying array; otherwise, . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryAdd(T item) - { - var array = _array; - var count = _count; - - if ((uint)count < (uint)array.Length) - { - array[count] = item; - _count = count + 1; - return true; - } - - return false; - } - - /// - /// Adds the elements of the specified array to the end of the underlying array. - /// - /// The array of elements to add. - public void AddRange(T[] items) - { - var array = _array; - var count = _count; - - if (array.Length - count < items.Length) - array = Grow(checked(count + items.Length)); - - _count = count + items.Length; - _ = array.Length; - items.AsSpan().TryCopyTo(array.AsSpan(count)); - } - - /// - /// Removes all elements from the . - /// - public void Clear() - { - // The array is not cleared (e.g., references or reference-containing data are not set to null) - // intentionally for performance reasons. This type is designed to be used as a temporary object - // and solely for building arrays. Therefore, there is no risk of memory leaks, - // and clearing unused elements would be unnecessary. - _count = 0; - } - - /// - /// Returns a representing the written data of the current instance. - /// - /// - /// A that represents the data within the current instance. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Span AsSpan() - { - var array = _array; - var count = _count; - _ = array.Length; - - return MemoryMarshal.CreateSpan( - ref MemoryMarshal.GetArrayDataReference(array), - count); - } - - /// - /// Copies the elements of the to a new array. - /// - /// - /// An array containing copies of the elements of the . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly T[] ToArray() => - AsSpan().ToArray(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private void AddSlow(T item) - { - var array = Grow(1); - var count = _count; - - if ((uint)count < (uint)array.Length) - array[count] = item; - - _count = count + 1; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private T[] Grow(int extra) - { - var array = _array; - - var required = checked(array.Length + extra); - var capacity = array.Length != 0 ? array.Length * 2 : 4; - - if ((uint)capacity > (uint)Array.MaxLength) - capacity = Array.MaxLength; - - if (capacity < required) - capacity = required; - - var destination = new T[capacity]; - array.AsSpan().TryCopyTo(destination); - - _array = destination; - return destination; - } -} +namespace Ramstack.Parsing.Collections; + +/// +/// Represents an array builder for elements of type . +/// +/// +/// This structure is optimized for performance and relies on proper initialization. +/// +/// Always initialize instances using one of the available constructors with the new keyword, +/// including the parameterless constructor: new ArrayBuilder<T>(). +/// +/// Do not use default(ArrayBuilder<T>) or similar patterns, as this results +/// in an uninitialized instance with an invalid internal state, +/// causing a when methods are called. +/// +/// This design choice was made to avoid unnecessary overhead and maintain control over +/// internal state initialization in modern C# versions where parameterless constructors +/// in structs are supported. +/// +/// The type of elements stored in the array builder. +[DebuggerDisplay("Count = {Count}")] +[DebuggerTypeProxy(typeof(ArrayBuilderDebugView<>))] +internal struct ArrayBuilder +{ + private T[] _array; + private int _count; + + /// + /// Gets the number of items contained in the . + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public readonly int Count => _count; + + /// + /// Gets the underlying buffer. + /// + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public readonly T[] InnerBuffer => _array; + + /// + /// Gets an item at the specified index. + /// + /// The zero-based index of the item to get. + public readonly ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var array = _array; + if ((uint)index >= (uint)_count || (uint)index >= (uint)array.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return ref array[index]; + } + } + + /// + /// Initializes a new instance of the . + /// + public ArrayBuilder() + { + _count = 0; + _array = []; + } + + /// + /// Initializes a new instance of the with the specified buffer capacity. + /// + /// The initial capacity of the buffer. + public ArrayBuilder(int capacity) + { + _count = 0; + _array = new T[capacity]; + } + + /// + /// Adds an item to the underlying array, resizing it if necessary. + /// + /// The item to add. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + var array = _array; + var count = _count; + + if ((uint)count < (uint)array.Length) + { + array[count] = item; + _count = count + 1; + } + else + { + AddSlow(item); + } + } + + /// + /// Attempts to add an item to the underlying array, without throwing an exception if there is no room. + /// + /// The item to add. + /// + /// Use this method if you know there is enough space in the + /// for another item, and you are writing performance-sensitive code. + /// + /// + /// if the item was added to the underlying array; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAdd(T item) + { + var array = _array; + var count = _count; + + if ((uint)count < (uint)array.Length) + { + array[count] = item; + _count = count + 1; + return true; + } + + return false; + } + + /// + /// Adds the elements of the specified array to the end of the underlying array. + /// + /// The array of elements to add. + public void AddRange(T[] items) + { + var array = _array; + var count = _count; + + if (array.Length - count < items.Length) + array = Grow(checked(count + items.Length)); + + _count = count + items.Length; + _ = array.Length; + items.AsSpan().TryCopyTo(array.AsSpan(count)); + } + + /// + /// Removes all elements from the . + /// + public void Clear() + { + // The array is not cleared (e.g., references or reference-containing data are not set to null) + // intentionally for performance reasons. This type is designed to be used as a temporary object + // and solely for building arrays. Therefore, there is no risk of memory leaks, + // and clearing unused elements would be unnecessary. + _count = 0; + } + + /// + /// Returns a representing the written data of the current instance. + /// + /// + /// A that represents the data within the current instance. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Span AsSpan() + { + var array = _array; + var count = _count; + _ = array.Length; + + return MemoryMarshal.CreateSpan( + ref MemoryMarshal.GetArrayDataReference(array), + count); + } + + /// + /// Copies the elements of the to a new array. + /// + /// + /// An array containing copies of the elements of the . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly T[] ToArray() => + AsSpan().ToArray(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private void AddSlow(T item) + { + var array = Grow(1); + var count = _count; + + if ((uint)count < (uint)array.Length) + array[count] = item; + + _count = count + 1; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private T[] Grow(int extra) + { + var array = _array; + + var required = checked(array.Length + extra); + var capacity = array.Length != 0 ? array.Length * 2 : 4; + + if ((uint)capacity > (uint)Array.MaxLength) + capacity = Array.MaxLength; + + if (capacity < required) + capacity = required; + + var destination = new T[capacity]; + array.AsSpan().TryCopyTo(destination); + + _array = destination; + return destination; + } +} diff --git a/src/Ramstack.Parsing/Collections/ArrayBuilderDebugView.cs b/src/Ramstack.Parsing/Collections/ArrayBuilderDebugView.cs index 5d45bbb..4e4a1d8 100644 --- a/src/Ramstack.Parsing/Collections/ArrayBuilderDebugView.cs +++ b/src/Ramstack.Parsing/Collections/ArrayBuilderDebugView.cs @@ -1,14 +1,14 @@ -namespace Ramstack.Parsing.Collections; - -/// -/// Represents a debugger view for the structure. -/// -/// The type of items in the . -internal sealed class ArrayBuilderDebugView(ArrayBuilder builder) -{ - /// - /// Gets the array of items contained in the . - /// - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] Items => builder.ToArray(); -} +namespace Ramstack.Parsing.Collections; + +/// +/// Represents a debugger view for the structure. +/// +/// The type of items in the . +internal sealed class ArrayBuilderDebugView(ArrayBuilder builder) +{ + /// + /// Gets the array of items contained in the . + /// + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items => builder.ToArray(); +} diff --git a/src/Ramstack.Parsing/DeferredParser.cs b/src/Ramstack.Parsing/DeferredParser.cs index 8cf26a5..4bd9407 100644 --- a/src/Ramstack.Parsing/DeferredParser.cs +++ b/src/Ramstack.Parsing/DeferredParser.cs @@ -1,50 +1,50 @@ -namespace Ramstack.Parsing; - -/// -/// Represents a parser that can be defined later. -/// -public sealed class DeferredParser : Parser -{ - /// - /// Gets or sets the underlying parser. - /// - public Parser Parser { get; set; } - - /// - /// Initializes a new instance of the class with an empty parser. - /// - internal DeferredParser() => - Parser = Parsing.Parser.Fail("The deferred parser has not been initialized."); - - /// - /// Initializes a new instance of the class - /// using the specified function to define the parser. - /// - /// A function that accepts a reference to this deferred parser and returns the resulting parser. - internal DeferredParser(Func, Parser> parser) => - Parser = parser(this) ?? throw new InvalidOperationException("The deferred parser has not been initialized."); - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) => - Parser.TryParse(ref context, out value); - - /// - protected internal override Parser ToNamedParser(string? name) - { - return new DeferredParser - { - Name = name, - Parser = Parser.ToNamedParser(name) - }; - } - - /// - protected internal override Parser ToVoidParser() - { - return new DeferredParser - { - Name = Name, - Parser = Parser.Void() - }; - } -} +namespace Ramstack.Parsing; + +/// +/// Represents a parser that can be defined later. +/// +public sealed class DeferredParser : Parser +{ + /// + /// Gets or sets the underlying parser. + /// + public Parser Parser { get; set; } + + /// + /// Initializes a new instance of the class with an empty parser. + /// + internal DeferredParser() => + Parser = Parsing.Parser.Fail("The deferred parser has not been initialized."); + + /// + /// Initializes a new instance of the class + /// using the specified function to define the parser. + /// + /// A function that accepts a reference to this deferred parser and returns the resulting parser. + internal DeferredParser(Func, Parser> parser) => + Parser = parser(this) ?? throw new InvalidOperationException("The deferred parser has not been initialized."); + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) => + Parser.TryParse(ref context, out value); + + /// + protected internal override Parser ToNamedParser(string? name) + { + return new DeferredParser + { + Name = name, + Parser = Parser.ToNamedParser(name) + }; + } + + /// + protected internal override Parser ToVoidParser() + { + return new DeferredParser + { + Name = Name, + Parser = Parser.Void() + }; + } +} diff --git a/src/Ramstack.Parsing/DiagnosticState.cs b/src/Ramstack.Parsing/DiagnosticState.cs index 16d9dae..ace7b77 100644 --- a/src/Ramstack.Parsing/DiagnosticState.cs +++ b/src/Ramstack.Parsing/DiagnosticState.cs @@ -1,17 +1,17 @@ -namespace Ramstack.Parsing; - -/// -/// Represents the diagnostic state for controlling the recording of parsing errors. -/// -public enum DiagnosticState -{ - /// - /// Diagnostic messages are recorded during parsing. - /// - Normal, - - /// - /// Diagnostic messages are suppressed and not recorded during parsing. - /// - Suppressed -} +namespace Ramstack.Parsing; + +/// +/// Represents the diagnostic state for controlling the recording of parsing errors. +/// +public enum DiagnosticState +{ + /// + /// Diagnostic messages are recorded during parsing. + /// + Normal, + + /// + /// Diagnostic messages are suppressed and not recorded during parsing. + /// + Suppressed +} diff --git a/src/Ramstack.Parsing/ErrorSet.cs b/src/Ramstack.Parsing/ErrorSet.cs index 73376e8..41076e4 100644 --- a/src/Ramstack.Parsing/ErrorSet.cs +++ b/src/Ramstack.Parsing/ErrorSet.cs @@ -1,89 +1,89 @@ -namespace Ramstack.Parsing; - -/// -/// Represents a collection of parsing errors in the source text. -/// -internal struct ErrorSet -{ - private ArrayBuilder _expectations; - private int _index; - - /// - /// Gets the position in the source text where a parsing error occurred. - /// - [SuppressMessage("ReSharper", "ConvertToAutoPropertyWithPrivateSetter")] - public readonly int Index => _index; - - /// - /// Initializes a new instance of the structure. - /// - public ErrorSet() => - _expectations = new ArrayBuilder(); - - /// - /// Reports a missing expected sequence or rule at the specified position. - /// - /// The position where the error occurred. - /// The expected sequence or rule. - [MethodImpl(MethodImplOptions.NoInlining)] - public void ReportExpected(int index, string expected) - { - if (index >= _index) - { - if (index > _index) - { - _index = index; - _expectations.Clear(); - } - - _expectations.Add(expected); - } - } - - /// - /// Reports multiple missing expected sequences or rules at the specified position. - /// - /// The position in the source where the errors occurred. - /// An array of expected sequences or rules. - public void ReportExpected(int index, string[] expected) - { - if (index >= _index) - { - if (index > _index) - { - _index = index; - _expectations.Clear(); - } - - _expectations.AddRange(expected); - } - } - - /// - /// Returns a formatted string describing the expected sequences that were not found. - /// - /// - /// A formatted string listing the expected sequences. - /// - public readonly override string ToString() - { - if (_expectations.Count == 0) - return ""; - - var list = _expectations - .InnerBuffer - .Take(_expectations.Count) - .Distinct() - .ToArray(); - - if (list.Length == 0) - return ""; - - return "Expected " + list.Length switch - { - 1 => list[0], - 2 => list[0] + " or " + list[1], - _ => string.Join(", ", list, 0, list.Length - 1) + ", or " + list[^1] - }; - } -} +namespace Ramstack.Parsing; + +/// +/// Represents a collection of parsing errors in the source text. +/// +internal struct ErrorSet +{ + private ArrayBuilder _expectations; + private int _index; + + /// + /// Gets the position in the source text where a parsing error occurred. + /// + [SuppressMessage("ReSharper", "ConvertToAutoPropertyWithPrivateSetter")] + public readonly int Index => _index; + + /// + /// Initializes a new instance of the structure. + /// + public ErrorSet() => + _expectations = new ArrayBuilder(); + + /// + /// Reports a missing expected sequence or rule at the specified position. + /// + /// The position where the error occurred. + /// The expected sequence or rule. + [MethodImpl(MethodImplOptions.NoInlining)] + public void ReportExpected(int index, string expected) + { + if (index >= _index) + { + if (index > _index) + { + _index = index; + _expectations.Clear(); + } + + _expectations.Add(expected); + } + } + + /// + /// Reports multiple missing expected sequences or rules at the specified position. + /// + /// The position in the source where the errors occurred. + /// An array of expected sequences or rules. + public void ReportExpected(int index, string[] expected) + { + if (index >= _index) + { + if (index > _index) + { + _index = index; + _expectations.Clear(); + } + + _expectations.AddRange(expected); + } + } + + /// + /// Returns a formatted string describing the expected sequences that were not found. + /// + /// + /// A formatted string listing the expected sequences. + /// + public readonly override string ToString() + { + if (_expectations.Count == 0) + return ""; + + var list = _expectations + .InnerBuffer + .Take(_expectations.Count) + .Distinct() + .ToArray(); + + if (list.Length == 0) + return ""; + + return "Expected " + list.Length switch + { + 1 => list[0], + 2 => list[0] + " or " + list[1], + _ => string.Join(", ", list, 0, list.Length - 1) + ", or " + list[^1] + }; + } +} diff --git a/src/Ramstack.Parsing/FatalErrorException.cs b/src/Ramstack.Parsing/FatalErrorException.cs index 71cfbda..9f87e86 100644 --- a/src/Ramstack.Parsing/FatalErrorException.cs +++ b/src/Ramstack.Parsing/FatalErrorException.cs @@ -1,7 +1,7 @@ -namespace Ramstack.Parsing; - -/// -/// Represents a user-defined error that occurred during parsing. -/// -/// The message describing the error. -internal sealed class FatalErrorException(string message) : Exception(message); +namespace Ramstack.Parsing; + +/// +/// Represents a user-defined error that occurred during parsing. +/// +/// The message describing the error. +internal sealed class FatalErrorException(string message) : Exception(message); diff --git a/src/Ramstack.Parsing/GeneralUnicodeCategory.cs b/src/Ramstack.Parsing/GeneralUnicodeCategory.cs index 99dcc43..b79afe5 100644 --- a/src/Ramstack.Parsing/GeneralUnicodeCategory.cs +++ b/src/Ramstack.Parsing/GeneralUnicodeCategory.cs @@ -1,282 +1,282 @@ -namespace Ramstack.Parsing; - -/// -/// Defines the Unicode category of a character. -/// -/// -/// Unlike the standard , this enumeration is designed -/// to work conveniently with flag-based combinations.
-/// https://www.unicode.org/reports/tr44/#General_Category_Values -///
-[Flags] -public enum GeneralUnicodeCategory -{ - /// - /// Represents the absence of any Unicode category. - /// Indicates that no specific category has been assigned. - /// - None = 0, - - /// - /// Uppercase letter. - /// Signified by the Unicode designation "Lu" (letter, uppercase). - /// - UppercaseLetter = 1 << UnicodeCategory.UppercaseLetter, - - /// - /// Lowercase letter. - /// Signified by the Unicode designation "Ll" (letter, lowercase). - /// - LowercaseLetter = 1 << UnicodeCategory.LowercaseLetter, - - /// - /// Titlecase letter. - /// Signified by the Unicode designation "Lt" (letter, titlecase). - /// - TitlecaseLetter = 1 << UnicodeCategory.TitlecaseLetter, - - /// - /// Modifier letter character, which is a free-standing spacing character - /// that indicates modifications of a preceding letter. - /// Signified by the Unicode designation "Lm" (letter, modifier). - /// - ModifierLetter = 1 << UnicodeCategory.ModifierLetter, - - /// - /// Letter that is not an uppercase letter, a lowercase letter, - /// a titlecase letter, or a modifier letter. - /// Signified by the Unicode designation "Lo" (letter, other). - /// - OtherLetter = 1 << UnicodeCategory.OtherLetter, - - /// - /// Nonspacing character that indicates modifications of a base character. - /// Signified by the Unicode designation "Mn" (mark, nonspacing). - /// - NonSpacingMark = 1 << UnicodeCategory.NonSpacingMark, - - /// - /// Spacing character that indicates modifications of a base character - /// and affects the width of the glyph for that base character. - /// Signified by the Unicode designation "Mc" (mark, spacing combining). - /// - SpacingCombiningMark = 1 << UnicodeCategory.SpacingCombiningMark, - - /// - /// Enclosing mark character, which is a nonspacing combining character - /// that surrounds all previous characters up to and including a base character. - /// Signified by the Unicode designation "Me" (mark, enclosing). - /// - EnclosingMark = 1 << UnicodeCategory.EnclosingMark, - - /// - /// A decimal digit with a value from 0 to 9, including digits from non-Latin scripts. - /// Signified by the Unicode designation "Nd" (number, decimal digit). - /// - DecimalDigitNumber = 1 << UnicodeCategory.DecimalDigitNumber, - - /// - /// Number represented by a letter, instead of a decimal digit, - /// for example, the Roman numeral five, "Ⅴ" (U+2164). - /// The indicator is signified by the Unicode designation "Nl" (number, letter). - /// - LetterNumber = 1 << UnicodeCategory.LetterNumber, - - /// - /// Number that is neither a decimal digit nor a letter number, for example, the fraction "½" (U+00BD). - /// The indicator is signified by the Unicode designation "No" (number, other). - /// - OtherNumber = 1 << UnicodeCategory.OtherNumber, - - /// - /// Space character, which has no glyph but is not a control or format character. - /// Signified by the Unicode designation "Zs" (separator, space). - /// - SpaceSeparator = 1 << UnicodeCategory.SpaceSeparator, - - /// - /// Character that is used to separate lines of text. - /// Signified by the Unicode designation "Zl" (separator, line). - LineSeparator = 1 << UnicodeCategory.LineSeparator, - - /// - /// Character used to separate paragraphs. - /// Signified by the Unicode designation "Zp" (separator, paragraph). - /// - ParagraphSeparator = 1 << UnicodeCategory.ParagraphSeparator, - - /// - /// Control code character, with a Unicode value of U+007F - /// or in the range U+0000 through U+001F or U+0080 through U+009F. - /// Signified by the Unicode designation "Cc" (other, control). - /// - Control = 1 << UnicodeCategory.Control, - - /// - /// Format character that affects the layout of text - /// or the operation of text processes, but is not normally rendered. - /// Signified by the Unicode designation "Cf" (other, format). - /// - Format = 1 << UnicodeCategory.Format, - - /// - /// High surrogate or a low surrogate character. - /// Surrogate code values are in the range U+D800 through U+DFFF. - /// Signified by the Unicode designation "Cs" (other, surrogate). - /// - Surrogate = 1 << UnicodeCategory.Surrogate, - - /// - /// Private-use character, with a Unicode value in the range U+E000 through U+F8FF. - /// Signified by the Unicode designation "Co" (other, private use). - /// - PrivateUse = 1 << UnicodeCategory.PrivateUse, - - /// - /// Connector punctuation character that connects two characters. - /// Signified by the Unicode designation "Pc" (punctuation, connector). - /// - ConnectorPunctuation = 1 << UnicodeCategory.ConnectorPunctuation, - - /// - /// Dash or hyphen character. - /// Signified by the Unicode designation "Pd" (punctuation, dash). - /// - DashPunctuation = 1 << UnicodeCategory.DashPunctuation, - - /// - /// Opening character of one of the paired punctuation marks, - /// such as parentheses, square brackets, and braces. - /// Signified by the Unicode designation "Ps" (punctuation, open). - /// - OpenPunctuation = 1 << UnicodeCategory.OpenPunctuation, - - /// - /// Closing character of one of the paired punctuation marks, - /// such as parentheses, square brackets, and braces. - /// Signified by the Unicode designation "Pe" (punctuation, close). - /// - ClosePunctuation = 1 << UnicodeCategory.ClosePunctuation, - - /// - /// Opening or initial quotation mark character. - /// Signified by the Unicode designation "Pi" (punctuation, initial quote). - /// - InitialQuotePunctuation = 1 << UnicodeCategory.InitialQuotePunctuation, - - /// - /// Closing or final quotation mark character. - /// Signified by the Unicode designation "Pf" (punctuation, final quote). - /// - FinalQuotePunctuation = 1 << UnicodeCategory.FinalQuotePunctuation, - - /// - /// Punctuation character that is not a connector, a dash, open punctuation, - /// close punctuation, an initial quote, or a final quote. - /// Signified by the Unicode designation "Po" (punctuation, other). - /// - OtherPunctuation = 1 << UnicodeCategory.OtherPunctuation, - - /// - /// Mathematical symbol character, such as "+" or "=". - /// Signified by the Unicode designation "Sm" (symbol, math). - /// - MathSymbol = 1 << UnicodeCategory.MathSymbol, - - /// - /// Currency symbol character. - /// Signified by the Unicode designation "Sc" (symbol, currency). - /// - CurrencySymbol = 1 << UnicodeCategory.CurrencySymbol, - - /// - /// Modifier symbol character, such as the circumflex accent "^" (U+005E). - /// The indicator is signified by the Unicode designation "Sk" (symbol, modifier). - /// - ModifierSymbol = 1 << UnicodeCategory.ModifierSymbol, - - /// - /// Symbol character that is not a mathematical symbol, a currency symbol or a modifier symbol. - /// Signified by the Unicode designation "So" (symbol, other). - /// - OtherSymbol = 1 << UnicodeCategory.OtherSymbol, - - /// - /// A code point that has not been assigned to a character. - /// Signified by the Unicode designation "Cn" (other, not assigned). - /// - OtherNotAssigned = 1 << UnicodeCategory.OtherNotAssigned, - - /// - /// Represents any letter category, including uppercase, lowercase, - /// titlecase, modifier, and other letters. - /// Signified by the Unicode designation "L" ("Lu", "Ll", "Lt", "Lm", "Lo"). - /// - Letter = - UppercaseLetter - | LowercaseLetter - | TitlecaseLetter - | ModifierLetter - | OtherLetter, - - /// - /// Represents any mark category, including non-spacing, spacing combining, and enclosing marks. - /// Signified by the Unicode designation "M" ("Mn", "Mc", "Me"). - /// - Mark = - NonSpacingMark - | SpacingCombiningMark - | EnclosingMark, - - /// - /// Represents any number category, including decimal digit, letter, and other numbers. - /// Signified by the Unicode designation "N" ("Nd", "Nl", "No"). - /// - Number = - DecimalDigitNumber - | LetterNumber - | OtherNumber, - - /// - /// Represents any separator category, including space, line, and paragraph separators. - /// Signified by the Unicode designation "Z" ("Zs", "Zl", "Zp"). - /// - Separator = - SpaceSeparator - | LineSeparator - | ParagraphSeparator, - - /// - /// Represents any punctuation category, including connector, dash, open, close, initial quote, final quote, and other punctuation. - /// Signified by the Unicode designation "P" ("Pc", "Pd", "Ps", "Pe", "Pi", "Pf", "Po"). - /// - Punctuation = - ConnectorPunctuation - | DashPunctuation - | OpenPunctuation - | ClosePunctuation - | InitialQuotePunctuation - | FinalQuotePunctuation - | OtherPunctuation, - - /// - /// Represents any symbol category, including math, currency, modifier, and other symbols. - /// Signified by the Unicode designation "S" ("Sm", "Sc", "Sk", "So"). - /// - Symbol = - MathSymbol - | CurrencySymbol - | ModifierSymbol - | OtherSymbol, - - /// - /// Includes control, format, surrogate, private-use, and unassigned code points. - /// Signified by the Unicode designation "C" ("Cc", "Cf", "Cs", "Co", "Cn"). - /// - Other = - Control - | Format - | Surrogate - | PrivateUse - | OtherNotAssigned, -} +namespace Ramstack.Parsing; + +/// +/// Defines the Unicode category of a character. +/// +/// +/// Unlike the standard , this enumeration is designed +/// to work conveniently with flag-based combinations.
+/// https://www.unicode.org/reports/tr44/#General_Category_Values +///
+[Flags] +public enum GeneralUnicodeCategory +{ + /// + /// Represents the absence of any Unicode category. + /// Indicates that no specific category has been assigned. + /// + None = 0, + + /// + /// Uppercase letter. + /// Signified by the Unicode designation "Lu" (letter, uppercase). + /// + UppercaseLetter = 1 << UnicodeCategory.UppercaseLetter, + + /// + /// Lowercase letter. + /// Signified by the Unicode designation "Ll" (letter, lowercase). + /// + LowercaseLetter = 1 << UnicodeCategory.LowercaseLetter, + + /// + /// Titlecase letter. + /// Signified by the Unicode designation "Lt" (letter, titlecase). + /// + TitlecaseLetter = 1 << UnicodeCategory.TitlecaseLetter, + + /// + /// Modifier letter character, which is a free-standing spacing character + /// that indicates modifications of a preceding letter. + /// Signified by the Unicode designation "Lm" (letter, modifier). + /// + ModifierLetter = 1 << UnicodeCategory.ModifierLetter, + + /// + /// Letter that is not an uppercase letter, a lowercase letter, + /// a titlecase letter, or a modifier letter. + /// Signified by the Unicode designation "Lo" (letter, other). + /// + OtherLetter = 1 << UnicodeCategory.OtherLetter, + + /// + /// Nonspacing character that indicates modifications of a base character. + /// Signified by the Unicode designation "Mn" (mark, nonspacing). + /// + NonSpacingMark = 1 << UnicodeCategory.NonSpacingMark, + + /// + /// Spacing character that indicates modifications of a base character + /// and affects the width of the glyph for that base character. + /// Signified by the Unicode designation "Mc" (mark, spacing combining). + /// + SpacingCombiningMark = 1 << UnicodeCategory.SpacingCombiningMark, + + /// + /// Enclosing mark character, which is a nonspacing combining character + /// that surrounds all previous characters up to and including a base character. + /// Signified by the Unicode designation "Me" (mark, enclosing). + /// + EnclosingMark = 1 << UnicodeCategory.EnclosingMark, + + /// + /// A decimal digit with a value from 0 to 9, including digits from non-Latin scripts. + /// Signified by the Unicode designation "Nd" (number, decimal digit). + /// + DecimalDigitNumber = 1 << UnicodeCategory.DecimalDigitNumber, + + /// + /// Number represented by a letter, instead of a decimal digit, + /// for example, the Roman numeral five, "Ⅴ" (U+2164). + /// The indicator is signified by the Unicode designation "Nl" (number, letter). + /// + LetterNumber = 1 << UnicodeCategory.LetterNumber, + + /// + /// Number that is neither a decimal digit nor a letter number, for example, the fraction "½" (U+00BD). + /// The indicator is signified by the Unicode designation "No" (number, other). + /// + OtherNumber = 1 << UnicodeCategory.OtherNumber, + + /// + /// Space character, which has no glyph but is not a control or format character. + /// Signified by the Unicode designation "Zs" (separator, space). + /// + SpaceSeparator = 1 << UnicodeCategory.SpaceSeparator, + + /// + /// Character that is used to separate lines of text. + /// Signified by the Unicode designation "Zl" (separator, line). + LineSeparator = 1 << UnicodeCategory.LineSeparator, + + /// + /// Character used to separate paragraphs. + /// Signified by the Unicode designation "Zp" (separator, paragraph). + /// + ParagraphSeparator = 1 << UnicodeCategory.ParagraphSeparator, + + /// + /// Control code character, with a Unicode value of U+007F + /// or in the range U+0000 through U+001F or U+0080 through U+009F. + /// Signified by the Unicode designation "Cc" (other, control). + /// + Control = 1 << UnicodeCategory.Control, + + /// + /// Format character that affects the layout of text + /// or the operation of text processes, but is not normally rendered. + /// Signified by the Unicode designation "Cf" (other, format). + /// + Format = 1 << UnicodeCategory.Format, + + /// + /// High surrogate or a low surrogate character. + /// Surrogate code values are in the range U+D800 through U+DFFF. + /// Signified by the Unicode designation "Cs" (other, surrogate). + /// + Surrogate = 1 << UnicodeCategory.Surrogate, + + /// + /// Private-use character, with a Unicode value in the range U+E000 through U+F8FF. + /// Signified by the Unicode designation "Co" (other, private use). + /// + PrivateUse = 1 << UnicodeCategory.PrivateUse, + + /// + /// Connector punctuation character that connects two characters. + /// Signified by the Unicode designation "Pc" (punctuation, connector). + /// + ConnectorPunctuation = 1 << UnicodeCategory.ConnectorPunctuation, + + /// + /// Dash or hyphen character. + /// Signified by the Unicode designation "Pd" (punctuation, dash). + /// + DashPunctuation = 1 << UnicodeCategory.DashPunctuation, + + /// + /// Opening character of one of the paired punctuation marks, + /// such as parentheses, square brackets, and braces. + /// Signified by the Unicode designation "Ps" (punctuation, open). + /// + OpenPunctuation = 1 << UnicodeCategory.OpenPunctuation, + + /// + /// Closing character of one of the paired punctuation marks, + /// such as parentheses, square brackets, and braces. + /// Signified by the Unicode designation "Pe" (punctuation, close). + /// + ClosePunctuation = 1 << UnicodeCategory.ClosePunctuation, + + /// + /// Opening or initial quotation mark character. + /// Signified by the Unicode designation "Pi" (punctuation, initial quote). + /// + InitialQuotePunctuation = 1 << UnicodeCategory.InitialQuotePunctuation, + + /// + /// Closing or final quotation mark character. + /// Signified by the Unicode designation "Pf" (punctuation, final quote). + /// + FinalQuotePunctuation = 1 << UnicodeCategory.FinalQuotePunctuation, + + /// + /// Punctuation character that is not a connector, a dash, open punctuation, + /// close punctuation, an initial quote, or a final quote. + /// Signified by the Unicode designation "Po" (punctuation, other). + /// + OtherPunctuation = 1 << UnicodeCategory.OtherPunctuation, + + /// + /// Mathematical symbol character, such as "+" or "=". + /// Signified by the Unicode designation "Sm" (symbol, math). + /// + MathSymbol = 1 << UnicodeCategory.MathSymbol, + + /// + /// Currency symbol character. + /// Signified by the Unicode designation "Sc" (symbol, currency). + /// + CurrencySymbol = 1 << UnicodeCategory.CurrencySymbol, + + /// + /// Modifier symbol character, such as the circumflex accent "^" (U+005E). + /// The indicator is signified by the Unicode designation "Sk" (symbol, modifier). + /// + ModifierSymbol = 1 << UnicodeCategory.ModifierSymbol, + + /// + /// Symbol character that is not a mathematical symbol, a currency symbol or a modifier symbol. + /// Signified by the Unicode designation "So" (symbol, other). + /// + OtherSymbol = 1 << UnicodeCategory.OtherSymbol, + + /// + /// A code point that has not been assigned to a character. + /// Signified by the Unicode designation "Cn" (other, not assigned). + /// + OtherNotAssigned = 1 << UnicodeCategory.OtherNotAssigned, + + /// + /// Represents any letter category, including uppercase, lowercase, + /// titlecase, modifier, and other letters. + /// Signified by the Unicode designation "L" ("Lu", "Ll", "Lt", "Lm", "Lo"). + /// + Letter = + UppercaseLetter + | LowercaseLetter + | TitlecaseLetter + | ModifierLetter + | OtherLetter, + + /// + /// Represents any mark category, including non-spacing, spacing combining, and enclosing marks. + /// Signified by the Unicode designation "M" ("Mn", "Mc", "Me"). + /// + Mark = + NonSpacingMark + | SpacingCombiningMark + | EnclosingMark, + + /// + /// Represents any number category, including decimal digit, letter, and other numbers. + /// Signified by the Unicode designation "N" ("Nd", "Nl", "No"). + /// + Number = + DecimalDigitNumber + | LetterNumber + | OtherNumber, + + /// + /// Represents any separator category, including space, line, and paragraph separators. + /// Signified by the Unicode designation "Z" ("Zs", "Zl", "Zp"). + /// + Separator = + SpaceSeparator + | LineSeparator + | ParagraphSeparator, + + /// + /// Represents any punctuation category, including connector, dash, open, close, initial quote, final quote, and other punctuation. + /// Signified by the Unicode designation "P" ("Pc", "Pd", "Ps", "Pe", "Pi", "Pf", "Po"). + /// + Punctuation = + ConnectorPunctuation + | DashPunctuation + | OpenPunctuation + | ClosePunctuation + | InitialQuotePunctuation + | FinalQuotePunctuation + | OtherPunctuation, + + /// + /// Represents any symbol category, including math, currency, modifier, and other symbols. + /// Signified by the Unicode designation "S" ("Sm", "Sc", "Sk", "So"). + /// + Symbol = + MathSymbol + | CurrencySymbol + | ModifierSymbol + | OtherSymbol, + + /// + /// Includes control, format, surrogate, private-use, and unassigned code points. + /// Signified by the Unicode designation "C" ("Cc", "Cf", "Cs", "Co", "Cn"). + /// + Other = + Control + | Format + | Surrogate + | PrivateUse + | OtherNotAssigned, +} diff --git a/src/Ramstack.Parsing/ICharClassSupport.cs b/src/Ramstack.Parsing/ICharClassSupport.cs index 69de331..9d928e9 100644 --- a/src/Ramstack.Parsing/ICharClassSupport.cs +++ b/src/Ramstack.Parsing/ICharClassSupport.cs @@ -1,15 +1,15 @@ -namespace Ramstack.Parsing; - -/// -/// Provides support for working with character classes. -/// -internal interface ICharClassSupport -{ - /// - /// Generates a object representing the current instance. - /// - /// - /// A object representing the current instance. - /// - CharClass GetCharClass(); -} +namespace Ramstack.Parsing; + +/// +/// Provides support for working with character classes. +/// +internal interface ICharClassSupport +{ + /// + /// Generates a object representing the current instance. + /// + /// + /// A object representing the current instance. + /// + CharClass GetCharClass(); +} diff --git a/src/Ramstack.Parsing/Literal.Char.cs b/src/Ramstack.Parsing/Literal.Char.cs index 29e8f6f..623edbf 100644 --- a/src/Ramstack.Parsing/Literal.Char.cs +++ b/src/Ramstack.Parsing/Literal.Char.cs @@ -1,24 +1,24 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class Literal -{ - private static Parser? _quotedCharacter; - - /// - /// Gets a parser that matches a character enclosed in single quotes. - /// - public static Parser QuotedCharacter => _quotedCharacter ??= - Choice( - EscapeSequence, - UnicodeEscapeSequence, - Not( - Choice( - L('\''), - L('\n'), - L('\r')) - ).Then(Any)) - .Between(L('\''), L('\'')) - .As("quoted-character"); -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class Literal +{ + private static Parser? _quotedCharacter; + + /// + /// Gets a parser that matches a character enclosed in single quotes. + /// + public static Parser QuotedCharacter => _quotedCharacter ??= + Choice( + EscapeSequence, + UnicodeEscapeSequence, + Not( + Choice( + L('\''), + L('\n'), + L('\r')) + ).Then(Any)) + .Between(L('\''), L('\'')) + .As("quoted-character"); +} diff --git a/src/Ramstack.Parsing/Literal.EscapeSequence.cs b/src/Ramstack.Parsing/Literal.EscapeSequence.cs index 1038cc7..e4f19b2 100644 --- a/src/Ramstack.Parsing/Literal.EscapeSequence.cs +++ b/src/Ramstack.Parsing/Literal.EscapeSequence.cs @@ -1,199 +1,199 @@ -namespace Ramstack.Parsing; - -partial class Literal -{ - /// - /// A lookup table for unescaping characters. Each entry in the table corresponds - /// to a character code, and the value indicates the unescaped representation of that character. - /// A value of -1 indicates that the character is not escapable - /// or does not require unescaping. - /// - /// - /// The table is indexed by character codes (e.g., ASCII values), and the values - /// represent the corresponding unescaped characters. - /// For example: Character code 110 (n) maps to 10, - /// indicating it is unescaped as a newline (\n). - /// - internal static ReadOnlySpan UnescapeTable => - [ - /* 000 */ -1, - /* 001 */ -1, - /* 002 */ -1, - /* 003 */ -1, - /* 004 */ -1, - /* 005 */ -1, - /* 006 */ -1, - /* 007 */ -1, - /* 008 */ -1, - /* 009 */ -1, - /* 010 */ -1, - /* 011 */ -1, - /* 012 */ -1, - /* 013 */ -1, - /* 014 */ -1, - /* 015 */ -1, - /* 016 */ -1, - /* 017 */ -1, - /* 018 */ -1, - /* 019 */ -1, - /* 020 */ -1, - /* 021 */ -1, - /* 022 */ -1, - /* 023 */ -1, - /* 024 */ -1, - /* 025 */ -1, - /* 026 */ -1, - /* 027 */ -1, - /* 028 */ -1, - /* 029 */ -1, - /* 030 */ -1, - /* 031 */ -1, - /* 032 */ -1, - /* 033 ! */ -1, - /* 034 " */ 34, - /* 035 # */ -1, - /* 036 $ */ -1, - /* 037 % */ -1, - /* 038 & */ -1, - /* 039 ' */ 39, - /* 040 ( */ -1, - /* 041 ) */ -1, - /* 042 * */ -1, - /* 043 + */ -1, - /* 044 , */ -1, - /* 045 - */ -1, - /* 046 . */ -1, - /* 047 / */ 47, - /* 048 0 */ 00, - /* 049 1 */ -1, - /* 050 2 */ -1, - /* 051 3 */ -1, - /* 052 4 */ -1, - /* 053 5 */ -1, - /* 054 6 */ -1, - /* 055 7 */ -1, - /* 056 8 */ -1, - /* 057 9 */ -1, - /* 058 : */ -1, - /* 059 ; */ -1, - /* 060 < */ -1, - /* 061 = */ -1, - /* 062 > */ -1, - /* 063 ? */ -1, - /* 064 @ */ -1, - /* 065 A */ -1, - /* 066 B */ -1, - /* 067 C */ -1, - /* 068 D */ -1, - /* 069 E */ -1, - /* 070 F */ -1, - /* 071 G */ -1, - /* 072 H */ -1, - /* 073 I */ -1, - /* 074 J */ -1, - /* 075 K */ -1, - /* 076 L */ -1, - /* 077 M */ -1, - /* 078 N */ -1, - /* 079 O */ -1, - /* 080 P */ -1, - /* 081 Q */ -1, - /* 082 R */ -1, - /* 083 S */ -1, - /* 084 T */ -1, - /* 085 U */ -1, - /* 086 V */ -1, - /* 087 W */ -1, - /* 088 X */ -1, - /* 089 Y */ -1, - /* 090 Z */ -1, - /* 091 [ */ -1, - /* 092 \ */ 92, - /* 093 ] */ -1, - /* 094 ^ */ -1, - /* 095 _ */ -1, - /* 096 ` */ -1, - /* 097 a */ 07, - /* 098 b */ 08, - /* 099 c */ -1, - /* 100 d */ -1, - /* 101 e */ 27, - /* 102 f */ 12, - /* 103 g */ -1, - /* 104 h */ -1, - /* 105 i */ -1, - /* 106 j */ -1, - /* 107 k */ -1, - /* 108 l */ -1, - /* 109 m */ -1, - /* 110 n */ 10, - /* 111 o */ -1, - /* 112 p */ -1, - /* 113 q */ -1, - /* 114 r */ 13, - /* 115 s */ -1, - /* 116 t */ 09, - /* 117 u */ -1, - /* 118 v */ 11, - /* 119 w */ -1, - /* 120 x */ -1, - /* 121 y */ -1, - /* 122 z */ -1, - /* 123 { */ -1, - /* 124 | */ -1, - /* 125 } */ -1, - /* 126 ~ */ -1 - ]; - - /// - /// Gets a parser that matches the escape sequence. - /// - public static Parser EscapeSequence { get; } = new EscapeSequenceParser(); - - #region Inner type: EscapeSequenceParser - - /// - /// Represents a parser that matches escape sequences, where each sequence is denoted by a backslash ('\'). - /// - private sealed class EscapeSequenceParser() : Parser("escape sequence") where T : struct - { - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Source; - var p = context.Position; - - if ((uint)p < (uint)s.Length && s[p] == '\\') - { - if ((uint)(p + 1) < (uint)s.Length && s[p + 1] < UnescapeTable.Length) - { - var c = UnescapeTable[s[p + 1]]; - if (c >= 0) - { - if (typeof(T) == typeof(char)) - value = (T)(object)(char)c; - else - value = default; - - context.Advance(2); - return true; - } - } - } - - value = default; - context.ReportExpected(Name); - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new EscapeSequenceParser { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new EscapeSequenceParser { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Literal +{ + /// + /// A lookup table for unescaping characters. Each entry in the table corresponds + /// to a character code, and the value indicates the unescaped representation of that character. + /// A value of -1 indicates that the character is not escapable + /// or does not require unescaping. + /// + /// + /// The table is indexed by character codes (e.g., ASCII values), and the values + /// represent the corresponding unescaped characters. + /// For example: Character code 110 (n) maps to 10, + /// indicating it is unescaped as a newline (\n). + /// + internal static ReadOnlySpan UnescapeTable => + [ + /* 000 */ -1, + /* 001 */ -1, + /* 002 */ -1, + /* 003 */ -1, + /* 004 */ -1, + /* 005 */ -1, + /* 006 */ -1, + /* 007 */ -1, + /* 008 */ -1, + /* 009 */ -1, + /* 010 */ -1, + /* 011 */ -1, + /* 012 */ -1, + /* 013 */ -1, + /* 014 */ -1, + /* 015 */ -1, + /* 016 */ -1, + /* 017 */ -1, + /* 018 */ -1, + /* 019 */ -1, + /* 020 */ -1, + /* 021 */ -1, + /* 022 */ -1, + /* 023 */ -1, + /* 024 */ -1, + /* 025 */ -1, + /* 026 */ -1, + /* 027 */ -1, + /* 028 */ -1, + /* 029 */ -1, + /* 030 */ -1, + /* 031 */ -1, + /* 032 */ -1, + /* 033 ! */ -1, + /* 034 " */ 34, + /* 035 # */ -1, + /* 036 $ */ -1, + /* 037 % */ -1, + /* 038 & */ -1, + /* 039 ' */ 39, + /* 040 ( */ -1, + /* 041 ) */ -1, + /* 042 * */ -1, + /* 043 + */ -1, + /* 044 , */ -1, + /* 045 - */ -1, + /* 046 . */ -1, + /* 047 / */ 47, + /* 048 0 */ 00, + /* 049 1 */ -1, + /* 050 2 */ -1, + /* 051 3 */ -1, + /* 052 4 */ -1, + /* 053 5 */ -1, + /* 054 6 */ -1, + /* 055 7 */ -1, + /* 056 8 */ -1, + /* 057 9 */ -1, + /* 058 : */ -1, + /* 059 ; */ -1, + /* 060 < */ -1, + /* 061 = */ -1, + /* 062 > */ -1, + /* 063 ? */ -1, + /* 064 @ */ -1, + /* 065 A */ -1, + /* 066 B */ -1, + /* 067 C */ -1, + /* 068 D */ -1, + /* 069 E */ -1, + /* 070 F */ -1, + /* 071 G */ -1, + /* 072 H */ -1, + /* 073 I */ -1, + /* 074 J */ -1, + /* 075 K */ -1, + /* 076 L */ -1, + /* 077 M */ -1, + /* 078 N */ -1, + /* 079 O */ -1, + /* 080 P */ -1, + /* 081 Q */ -1, + /* 082 R */ -1, + /* 083 S */ -1, + /* 084 T */ -1, + /* 085 U */ -1, + /* 086 V */ -1, + /* 087 W */ -1, + /* 088 X */ -1, + /* 089 Y */ -1, + /* 090 Z */ -1, + /* 091 [ */ -1, + /* 092 \ */ 92, + /* 093 ] */ -1, + /* 094 ^ */ -1, + /* 095 _ */ -1, + /* 096 ` */ -1, + /* 097 a */ 07, + /* 098 b */ 08, + /* 099 c */ -1, + /* 100 d */ -1, + /* 101 e */ 27, + /* 102 f */ 12, + /* 103 g */ -1, + /* 104 h */ -1, + /* 105 i */ -1, + /* 106 j */ -1, + /* 107 k */ -1, + /* 108 l */ -1, + /* 109 m */ -1, + /* 110 n */ 10, + /* 111 o */ -1, + /* 112 p */ -1, + /* 113 q */ -1, + /* 114 r */ 13, + /* 115 s */ -1, + /* 116 t */ 09, + /* 117 u */ -1, + /* 118 v */ 11, + /* 119 w */ -1, + /* 120 x */ -1, + /* 121 y */ -1, + /* 122 z */ -1, + /* 123 { */ -1, + /* 124 | */ -1, + /* 125 } */ -1, + /* 126 ~ */ -1 + ]; + + /// + /// Gets a parser that matches the escape sequence. + /// + public static Parser EscapeSequence { get; } = new EscapeSequenceParser(); + + #region Inner type: EscapeSequenceParser + + /// + /// Represents a parser that matches escape sequences, where each sequence is denoted by a backslash ('\'). + /// + private sealed class EscapeSequenceParser() : Parser("escape sequence") where T : struct + { + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Source; + var p = context.Position; + + if ((uint)p < (uint)s.Length && s[p] == '\\') + { + if ((uint)(p + 1) < (uint)s.Length && s[p + 1] < UnescapeTable.Length) + { + var c = UnescapeTable[s[p + 1]]; + if (c >= 0) + { + if (typeof(T) == typeof(char)) + value = (T)(object)(char)c; + else + value = default; + + context.Advance(2); + return true; + } + } + } + + value = default; + context.ReportExpected(Name); + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new EscapeSequenceParser { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new EscapeSequenceParser { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Literal.Number.cs b/src/Ramstack.Parsing/Literal.Number.cs index 2907ea8..d3f8283 100644 --- a/src/Ramstack.Parsing/Literal.Number.cs +++ b/src/Ramstack.Parsing/Literal.Number.cs @@ -1,326 +1,326 @@ -using System.Numerics; - -namespace Ramstack.Parsing; - -partial class Literal -{ - /// - /// Creates a parser for numeric types. - /// - /// The numeric type to parse. - /// The numeric format to parse. Defaults to . - /// - /// A parser for the specified numeric type. - /// - public static Parser Number(NumberKind kind = NumberKind.Auto) where T : struct - #if NET7_0_OR_GREATER - , INumber - #endif - { - return Number(null, kind); - } - - /// - /// Creates a parser for numeric types. - /// - /// The numeric type to parse. - /// An optional name for the parser. - /// The numeric format to parse. Defaults to . - /// - /// A parser for the specified numeric type. - /// - public static Parser Number(string? name, NumberKind kind = NumberKind.Auto) where T : struct - #if NET7_0_OR_GREATER - , INumber - #endif - { - if (typeof(T) == typeof(float) - || typeof(T) == typeof(double) - || typeof(T) == typeof(decimal) - || typeof(T) == typeof(Half)) - { - return kind is NumberKind.Auto or NumberKind.Float - ? new NumberLiteral(name ?? "floating-point number", NumberStyles.Float) - : throw new ArgumentException($"Number kind {kind} is not supported for floating-point types.", nameof(kind)); - } - - if (typeof(T) == typeof(sbyte) - || typeof(T) == typeof(byte) - || typeof(T) == typeof(short) - || typeof(T) == typeof(ushort) - || typeof(T) == typeof(int) - || typeof(T) == typeof(uint) - || typeof(T) == typeof(long) - || typeof(T) == typeof(ulong) - || typeof(T) == typeof(nint) - || typeof(T) == typeof(nuint) - #if NET7_0_OR_GREATER - || typeof(T) == typeof(Int128) - || typeof(T) == typeof(UInt128) - #endif - || typeof(T) == typeof(BigInteger)) - { - if (kind is NumberKind.Auto or NumberKind.Integer) - return new NumberLiteral(name ?? "number", NumberStyles.Integer); - - if (kind == NumberKind.HexNumber) - return new NumberLiteral(name ?? "hexadecimal number", NumberStyles.HexNumber); - - #if NET8_0_OR_GREATER - if (kind == NumberKind.BinaryNumber) - return new NumberLiteral(name ?? "binary number", NumberStyles.BinaryNumber); - #endif - - throw new ArgumentException( - $"Number kind {kind} is not supported for integer types.", - nameof(kind)); - } - - throw new InvalidOperationException($"Type {typeof(T)} is not supported."); - } - - private static int TryParseNumber(ref char span, int length, int p) - { - var s = MemoryMarshal.CreateReadOnlySpan(ref span, length); - if ((uint)p >= (uint)s.Length) - return 0; - - if (typeof(TKind) == typeof(IntegerLiteralKind) || typeof(TKind) == typeof(FloatLiteralKind)) - { - // - // This line checks if s[p] is '+' or '-'. - // - // 1. Subtracting '+' maps: - // '+' (ASCII 43) -> 0 - // '-' (ASCII 45) -> 2 - // - // 2. Applying & -3 means: -3 is 11111...11111101: - // - bit #1 is zero, ensuring both 0 and 2 become 0 when we do & -3: - // 0b00000...00000000 & 0b11111...11111101 = 0 - // 0b00000...00000010 & 0b11111...11111101 = 0 - // - // 3. If (((s[p] - '+') & -3) == 0) is true, - // it means the character was either '+' or '-'. - // - - if ((s[p] - '+' & -3) == 0) - { - p++; - if ((uint)p >= (uint)s.Length) - return 0; - } - - if (!IsAsciiDigit(s[p])) - return 0; - - p++; - while ((uint)p < (uint)s.Length && IsAsciiDigit(s[p])) - p++; - - // - // Float number pattern: \d+(\.\d+)?([Ee][-+]?\d+)? - // - - if (typeof(TKind) == typeof(FloatLiteralKind) && (uint)p < s.Length) - { - if (s[p] == '.') - { - var index = p + 1; - - if ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) - { - index++; - while ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) - index++; - - p = index; - } - } - - if ((uint)p < (uint)s.Length && (s[p] | 0x20) == 'e') - { - var index = p + 1; - - if ((uint)index < (uint)s.Length) - { - // c == '+' || c == '-' - if ((s[index] - '+' & -3) == 0) - index++; - - if ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) - { - index++; - while ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) - index++; - - p = index; - } - } - } - } - } - - if (typeof(TKind) == typeof(HexNumberLiteralKind)) - { - if (!IsAsciiHexDigit(s[p])) - return 0; - - while ((uint)p < (uint)s.Length && IsAsciiHexDigit(s[p])) - p++; - } - - if (typeof(TKind) == typeof(BinaryNumberLiteralKind)) - { - if ((uint)s[p] - '0' > 1) - return 0; - - while ((uint)p < (uint)s.Length && (uint)s[p] - '0' <= 1) - p++; - } - - return p; - } - - #region Helper methods - - private static bool IsAsciiDigit(char c) => - IsBetween(c, '0', '9'); - - private static bool IsAsciiHexDigit(char c) - { - #if NET7_0_OR_GREATER - return char.IsAsciiHexDigit(c); - #else - return IsBetween(c, '0', '9') | IsBetween((uint)c | 0x20, 'a', 'f'); - #endif - } - - private static bool IsBetween(uint c, char min, char max) => - c - min <= (uint)(max - min); - - #endregion - - #region Inner type: NumberLiteral - - /// - /// Represents a parser for numeric literals based on the specified numeric type and format. - /// - /// The numeric type to parse. - /// The kind of numeric literal being parsed. - /// A name for the parser. - /// A bitwise combination of enumeration values that indicates the permitted format of the numeric literal. - private sealed class NumberLiteral(string name, NumberStyles style) : Parser(name) where TKind : struct where T : struct - #if NET7_0_OR_GREATER - , INumber - #endif - { - /// - public override bool TryParse(ref ParseContext context, out T value) - { - Unsafe.SkipInit(out value); - - var bookmark = context.BookmarkPosition(); - var position = TryParseNumber( - ref MemoryMarshal.GetReference(context.Source), - context.Source.Length, - context.Position); - - if (position != 0) - { - context.Advance(position - context.Position); - - #if NET7_0_OR_GREATER - if (T.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out value)) - return true; - #else - if (typeof(T) == typeof(sbyte)) - if (sbyte.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(byte)) - if (byte.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(short)) - if (short.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(ushort)) - if (ushort.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(int)) - if (int.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(uint)) - if (uint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(nint)) - if (nint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(nuint)) - if (nuint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(long)) - if (long.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(ulong)) - if (ulong.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(BigInteger)) - if (BigInteger.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(double)) - if (double.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(float)) - if (float.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(decimal)) - if (decimal.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - - if (typeof(T) == typeof(Half)) - if (Half.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) - return true; - #endif - } - - context.RestorePosition(bookmark); - context.ReportExpected(Name); - value = default; - return false; - } - } - - #endregion - - /// - /// A marker struct used to indicate the kind of number literal being parsed as an integer. - /// - private struct IntegerLiteralKind; - - /// - /// A marker struct used to indicate the kind of number literal being parsed as a hexadecimal integer number. - /// - private struct HexNumberLiteralKind; - - /// - /// A marker struct used to indicate the kind of number literal being parsed as a binary integer number. - /// - private struct BinaryNumberLiteralKind; - - /// - /// A marker struct used to indicate the kind of number literal being parsed as a floating-point number. - /// - private struct FloatLiteralKind; -} +using System.Numerics; + +namespace Ramstack.Parsing; + +partial class Literal +{ + /// + /// Creates a parser for numeric types. + /// + /// The numeric type to parse. + /// The numeric format to parse. Defaults to . + /// + /// A parser for the specified numeric type. + /// + public static Parser Number(NumberKind kind = NumberKind.Auto) where T : struct + #if NET7_0_OR_GREATER + , INumber + #endif + { + return Number(null, kind); + } + + /// + /// Creates a parser for numeric types. + /// + /// The numeric type to parse. + /// An optional name for the parser. + /// The numeric format to parse. Defaults to . + /// + /// A parser for the specified numeric type. + /// + public static Parser Number(string? name, NumberKind kind = NumberKind.Auto) where T : struct + #if NET7_0_OR_GREATER + , INumber + #endif + { + if (typeof(T) == typeof(float) + || typeof(T) == typeof(double) + || typeof(T) == typeof(decimal) + || typeof(T) == typeof(Half)) + { + return kind is NumberKind.Auto or NumberKind.Float + ? new NumberLiteral(name ?? "floating-point number", NumberStyles.Float) + : throw new ArgumentException($"Number kind {kind} is not supported for floating-point types.", nameof(kind)); + } + + if (typeof(T) == typeof(sbyte) + || typeof(T) == typeof(byte) + || typeof(T) == typeof(short) + || typeof(T) == typeof(ushort) + || typeof(T) == typeof(int) + || typeof(T) == typeof(uint) + || typeof(T) == typeof(long) + || typeof(T) == typeof(ulong) + || typeof(T) == typeof(nint) + || typeof(T) == typeof(nuint) + #if NET7_0_OR_GREATER + || typeof(T) == typeof(Int128) + || typeof(T) == typeof(UInt128) + #endif + || typeof(T) == typeof(BigInteger)) + { + if (kind is NumberKind.Auto or NumberKind.Integer) + return new NumberLiteral(name ?? "number", NumberStyles.Integer); + + if (kind == NumberKind.HexNumber) + return new NumberLiteral(name ?? "hexadecimal number", NumberStyles.HexNumber); + + #if NET8_0_OR_GREATER + if (kind == NumberKind.BinaryNumber) + return new NumberLiteral(name ?? "binary number", NumberStyles.BinaryNumber); + #endif + + throw new ArgumentException( + $"Number kind {kind} is not supported for integer types.", + nameof(kind)); + } + + throw new InvalidOperationException($"Type {typeof(T)} is not supported."); + } + + private static int TryParseNumber(ref char span, int length, int p) + { + var s = MemoryMarshal.CreateReadOnlySpan(ref span, length); + if ((uint)p >= (uint)s.Length) + return 0; + + if (typeof(TKind) == typeof(IntegerLiteralKind) || typeof(TKind) == typeof(FloatLiteralKind)) + { + // + // This line checks if s[p] is '+' or '-'. + // + // 1. Subtracting '+' maps: + // '+' (ASCII 43) -> 0 + // '-' (ASCII 45) -> 2 + // + // 2. Applying & -3 means: -3 is 11111...11111101: + // - bit #1 is zero, ensuring both 0 and 2 become 0 when we do & -3: + // 0b00000...00000000 & 0b11111...11111101 = 0 + // 0b00000...00000010 & 0b11111...11111101 = 0 + // + // 3. If (((s[p] - '+') & -3) == 0) is true, + // it means the character was either '+' or '-'. + // + + if ((s[p] - '+' & -3) == 0) + { + p++; + if ((uint)p >= (uint)s.Length) + return 0; + } + + if (!IsAsciiDigit(s[p])) + return 0; + + p++; + while ((uint)p < (uint)s.Length && IsAsciiDigit(s[p])) + p++; + + // + // Float number pattern: \d+(\.\d+)?([Ee][-+]?\d+)? + // + + if (typeof(TKind) == typeof(FloatLiteralKind) && (uint)p < s.Length) + { + if (s[p] == '.') + { + var index = p + 1; + + if ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) + { + index++; + while ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) + index++; + + p = index; + } + } + + if ((uint)p < (uint)s.Length && (s[p] | 0x20) == 'e') + { + var index = p + 1; + + if ((uint)index < (uint)s.Length) + { + // c == '+' || c == '-' + if ((s[index] - '+' & -3) == 0) + index++; + + if ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) + { + index++; + while ((uint)index < (uint)s.Length && IsAsciiDigit(s[index])) + index++; + + p = index; + } + } + } + } + } + + if (typeof(TKind) == typeof(HexNumberLiteralKind)) + { + if (!IsAsciiHexDigit(s[p])) + return 0; + + while ((uint)p < (uint)s.Length && IsAsciiHexDigit(s[p])) + p++; + } + + if (typeof(TKind) == typeof(BinaryNumberLiteralKind)) + { + if ((uint)s[p] - '0' > 1) + return 0; + + while ((uint)p < (uint)s.Length && (uint)s[p] - '0' <= 1) + p++; + } + + return p; + } + + #region Helper methods + + private static bool IsAsciiDigit(char c) => + IsBetween(c, '0', '9'); + + private static bool IsAsciiHexDigit(char c) + { + #if NET7_0_OR_GREATER + return char.IsAsciiHexDigit(c); + #else + return IsBetween(c, '0', '9') | IsBetween((uint)c | 0x20, 'a', 'f'); + #endif + } + + private static bool IsBetween(uint c, char min, char max) => + c - min <= (uint)(max - min); + + #endregion + + #region Inner type: NumberLiteral + + /// + /// Represents a parser for numeric literals based on the specified numeric type and format. + /// + /// The numeric type to parse. + /// The kind of numeric literal being parsed. + /// A name for the parser. + /// A bitwise combination of enumeration values that indicates the permitted format of the numeric literal. + private sealed class NumberLiteral(string name, NumberStyles style) : Parser(name) where TKind : struct where T : struct + #if NET7_0_OR_GREATER + , INumber + #endif + { + /// + public override bool TryParse(ref ParseContext context, out T value) + { + Unsafe.SkipInit(out value); + + var bookmark = context.BookmarkPosition(); + var position = TryParseNumber( + ref MemoryMarshal.GetReference(context.Source), + context.Source.Length, + context.Position); + + if (position != 0) + { + context.Advance(position - context.Position); + + #if NET7_0_OR_GREATER + if (T.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out value)) + return true; + #else + if (typeof(T) == typeof(sbyte)) + if (sbyte.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(byte)) + if (byte.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(short)) + if (short.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(ushort)) + if (ushort.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(int)) + if (int.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(uint)) + if (uint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(nint)) + if (nint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(nuint)) + if (nuint.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(long)) + if (long.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(ulong)) + if (ulong.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(BigInteger)) + if (BigInteger.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(double)) + if (double.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(float)) + if (float.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(decimal)) + if (decimal.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + + if (typeof(T) == typeof(Half)) + if (Half.TryParse(context.MatchedSegment, style, CultureInfo.InvariantCulture, out Unsafe.As(ref value))) + return true; + #endif + } + + context.RestorePosition(bookmark); + context.ReportExpected(Name); + value = default; + return false; + } + } + + #endregion + + /// + /// A marker struct used to indicate the kind of number literal being parsed as an integer. + /// + private struct IntegerLiteralKind; + + /// + /// A marker struct used to indicate the kind of number literal being parsed as a hexadecimal integer number. + /// + private struct HexNumberLiteralKind; + + /// + /// A marker struct used to indicate the kind of number literal being parsed as a binary integer number. + /// + private struct BinaryNumberLiteralKind; + + /// + /// A marker struct used to indicate the kind of number literal being parsed as a floating-point number. + /// + private struct FloatLiteralKind; +} diff --git a/src/Ramstack.Parsing/Literal.String.cs b/src/Ramstack.Parsing/Literal.String.cs index add7274..8b8415d 100644 --- a/src/Ramstack.Parsing/Literal.String.cs +++ b/src/Ramstack.Parsing/Literal.String.cs @@ -1,206 +1,206 @@ -namespace Ramstack.Parsing; - -partial class Literal -{ - #if NET8_0_OR_GREATER - private static System.Buffers.SearchValues SingleQuoteStopChars { get; } = System.Buffers.SearchValues.Create(['\'', '\\', '\r', '\n']); - private static System.Buffers.SearchValues DoubleQuoteStopChars { get; } = System.Buffers.SearchValues.Create(['\"', '\\', '\r', '\n']); - #elif NET7_0 - private static ReadOnlySpan SingleQuoteStopChars => ['\'', '\\', '\r', '\n']; - private static ReadOnlySpan DoubleQuoteStopChars => ['\"', '\\', '\r', '\n']; - #else - private static char[] SingleQuoteStopChars { get; } = ['\'', '\\', '\r', '\n']; - private static char[] DoubleQuoteStopChars { get; } = ['\"', '\\', '\r', '\n']; - #endif - - private static Parser? _doubleQuotedString; - private static Parser? _singleQuotedString; - - /// - /// Gets a parser that matches a string enclosed in double quotes. - /// - public static Parser DoubleQuotedString => _doubleQuotedString ??= new QuotedStringParser("double-quoted string"); - - /// - /// Gets a parser that matches a string enclosed in single quotes. - /// - public static Parser SingleQuotedString => _singleQuotedString ??= new QuotedStringParser("single-quoted string"); - - #region Inner type: QuotedStringParser - - /// - /// Represents a parser that matches a string enclosed in double or single quotes. - /// - /// The type of the string kind to parse. - /// The type of the value produced by the parser. - /// The name to assign to the parser. - private sealed class QuotedStringParser(string? name) : Parser(name) where TStringKind : struct - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - var count = TryParseImpl(context.Remaining, out value); - if (count == 0) - { - context.ReportExpected(Name); - } - else - { - context.Advance(count); - } - - return count != 0; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new QuotedStringParser(name); - - /// - protected internal override Parser ToVoidParser() => - new QuotedStringParser(Name); - - private static int TryParseImpl(ReadOnlySpan s, out TResult? value) - { - var quote = typeof(TStringKind) == typeof(DoubleQuoteString) - ? '"' : '\''; - - value = default; - - var result = new StringBuffer(); - var offset = 0; - - if (s.Length == 0 || s[0] != quote) - goto FAIL; - - s = s.Slice(1); - - while (true) - { - var index = typeof(TStringKind) == typeof(SingleQuoteString) - ? s.IndexOfAny(SingleQuoteStopChars) - : s.IndexOfAny(DoubleQuoteStopChars); - - if ((uint)index >= (uint)s.Length) - goto FAIL; - - if (s[index] == quote) - { - if (typeof(TResult) != typeof(Unit)) - { - if (result.Length == 0) - { - Unsafe.As(ref value!) = new string(s[..index]); - } - else - { - result.Append(s[..index]); - Unsafe.As(ref value!) = result.ToString(); - } - } - - return offset + index + 2; - } - - if (s[index] is '\n' or '\r') - goto FAIL; - - if ((uint)index + 1 >= (uint)s.Length) - goto FAIL; - - if (typeof(TResult) != typeof(Unit)) - if (index != 0) - result.Append(s[..index]); - - char ch; - - var escaped = s[index + 1]; - if (escaped != 'u') - { - if (escaped >= (uint)UnescapeTable.Length) - goto FAIL; - - var c = UnescapeTable[escaped]; - if (c < 0) - goto FAIL; - - ch = (char)c; - } - else - { - var p = s.Slice(index); - if (p.Length < 6) - goto FAIL; - - var v1 = (nint)p[2]; - var v2 = (nint)p[3]; - var v3 = (nint)p[4]; - var v4 = (nint)p[5]; - - // We use Unsafe-methods here to index into the HexTable without bounds checks. - // - // The check 'if ((uint)(v1 | v2 | v3 | v4) <= 127)' guarantees that all four values - // are within the range 0..127. - // - // This makes it safe to access HexTable[v1], HexTable[v2], HexTable[v3], and HexTable[v4] - // without additional bounds checks, because HexTable has exactly 128 elements. - // By using Unsafe.Add, we avoid the overhead of safety checks and improve performance. - // - // Once the JIT compiler can properly recognize and optimize this pattern, we may be able - // to replace these unsafe calls with safe indexed access without performance loss. - if ((uint)(v1 | v2 | v3 | v4) > 127) - goto FAIL; - - ref var table = ref MemoryMarshal.GetReference(HexTable); - - var r1 = (int)Unsafe.Add(ref table, v1); - var r2 = (int)Unsafe.Add(ref table, v2); - var r3 = (int)Unsafe.Add(ref table, v3); - var r4 = (int)Unsafe.Add(ref table, v4); - - r1 <<= 12; - r2 <<= 08; - r3 <<= 04; - var c = r1 | r2 | r3 | r4; - - if ((uint)c > 0xFFFF) - goto FAIL; - - ch = (char)c; - index += 4; - } - - if (typeof(TResult) != typeof(Unit)) - result.Append(ch); - - // Micro-optimization: - // While bounds check here is technically redundant (no out-of-bounds access here), - // this explicit check helps reduce code size by preventing the JIT from generating - // CORINFO_HELP_RNGCHKFAIL for the Slice() call below - if ((uint)index + 2 > (uint)s.Length) - goto FAIL; - - s = s.Slice(index + 2); - offset += index + 2; - } - - FAIL: - if (typeof(TResult) != typeof(Unit)) - result.Dispose(); - - return 0; - } - } - - #endregion - - /// - /// A marker struct used to indicate a string literal enclosed in single quotes (' '). - /// - private struct SingleQuoteString; - - /// - /// A marker struct used to indicate a string literal enclosed in double quotes (" "). - /// - private struct DoubleQuoteString; -} +namespace Ramstack.Parsing; + +partial class Literal +{ + #if NET8_0_OR_GREATER + private static System.Buffers.SearchValues SingleQuoteStopChars { get; } = System.Buffers.SearchValues.Create(['\'', '\\', '\r', '\n']); + private static System.Buffers.SearchValues DoubleQuoteStopChars { get; } = System.Buffers.SearchValues.Create(['\"', '\\', '\r', '\n']); + #elif NET7_0 + private static ReadOnlySpan SingleQuoteStopChars => ['\'', '\\', '\r', '\n']; + private static ReadOnlySpan DoubleQuoteStopChars => ['\"', '\\', '\r', '\n']; + #else + private static char[] SingleQuoteStopChars { get; } = ['\'', '\\', '\r', '\n']; + private static char[] DoubleQuoteStopChars { get; } = ['\"', '\\', '\r', '\n']; + #endif + + private static Parser? _doubleQuotedString; + private static Parser? _singleQuotedString; + + /// + /// Gets a parser that matches a string enclosed in double quotes. + /// + public static Parser DoubleQuotedString => _doubleQuotedString ??= new QuotedStringParser("double-quoted string"); + + /// + /// Gets a parser that matches a string enclosed in single quotes. + /// + public static Parser SingleQuotedString => _singleQuotedString ??= new QuotedStringParser("single-quoted string"); + + #region Inner type: QuotedStringParser + + /// + /// Represents a parser that matches a string enclosed in double or single quotes. + /// + /// The type of the string kind to parse. + /// The type of the value produced by the parser. + /// The name to assign to the parser. + private sealed class QuotedStringParser(string? name) : Parser(name) where TStringKind : struct + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + var count = TryParseImpl(context.Remaining, out value); + if (count == 0) + { + context.ReportExpected(Name); + } + else + { + context.Advance(count); + } + + return count != 0; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new QuotedStringParser(name); + + /// + protected internal override Parser ToVoidParser() => + new QuotedStringParser(Name); + + private static int TryParseImpl(ReadOnlySpan s, out TResult? value) + { + var quote = typeof(TStringKind) == typeof(DoubleQuoteString) + ? '"' : '\''; + + value = default; + + var result = new StringBuffer(); + var offset = 0; + + if (s.Length == 0 || s[0] != quote) + goto FAIL; + + s = s.Slice(1); + + while (true) + { + var index = typeof(TStringKind) == typeof(SingleQuoteString) + ? s.IndexOfAny(SingleQuoteStopChars) + : s.IndexOfAny(DoubleQuoteStopChars); + + if ((uint)index >= (uint)s.Length) + goto FAIL; + + if (s[index] == quote) + { + if (typeof(TResult) != typeof(Unit)) + { + if (result.Length == 0) + { + Unsafe.As(ref value!) = new string(s[..index]); + } + else + { + result.Append(s[..index]); + Unsafe.As(ref value!) = result.ToString(); + } + } + + return offset + index + 2; + } + + if (s[index] is '\n' or '\r') + goto FAIL; + + if ((uint)index + 1 >= (uint)s.Length) + goto FAIL; + + if (typeof(TResult) != typeof(Unit)) + if (index != 0) + result.Append(s[..index]); + + char ch; + + var escaped = s[index + 1]; + if (escaped != 'u') + { + if (escaped >= (uint)UnescapeTable.Length) + goto FAIL; + + var c = UnescapeTable[escaped]; + if (c < 0) + goto FAIL; + + ch = (char)c; + } + else + { + var p = s.Slice(index); + if (p.Length < 6) + goto FAIL; + + var v1 = (nint)p[2]; + var v2 = (nint)p[3]; + var v3 = (nint)p[4]; + var v4 = (nint)p[5]; + + // We use Unsafe-methods here to index into the HexTable without bounds checks. + // + // The check 'if ((uint)(v1 | v2 | v3 | v4) <= 127)' guarantees that all four values + // are within the range 0..127. + // + // This makes it safe to access HexTable[v1], HexTable[v2], HexTable[v3], and HexTable[v4] + // without additional bounds checks, because HexTable has exactly 128 elements. + // By using Unsafe.Add, we avoid the overhead of safety checks and improve performance. + // + // Once the JIT compiler can properly recognize and optimize this pattern, we may be able + // to replace these unsafe calls with safe indexed access without performance loss. + if ((uint)(v1 | v2 | v3 | v4) > 127) + goto FAIL; + + ref var table = ref MemoryMarshal.GetReference(HexTable); + + var r1 = (int)Unsafe.Add(ref table, v1); + var r2 = (int)Unsafe.Add(ref table, v2); + var r3 = (int)Unsafe.Add(ref table, v3); + var r4 = (int)Unsafe.Add(ref table, v4); + + r1 <<= 12; + r2 <<= 08; + r3 <<= 04; + var c = r1 | r2 | r3 | r4; + + if ((uint)c > 0xFFFF) + goto FAIL; + + ch = (char)c; + index += 4; + } + + if (typeof(TResult) != typeof(Unit)) + result.Append(ch); + + // Micro-optimization: + // While bounds check here is technically redundant (no out-of-bounds access here), + // this explicit check helps reduce code size by preventing the JIT from generating + // CORINFO_HELP_RNGCHKFAIL for the Slice() call below + if ((uint)index + 2 > (uint)s.Length) + goto FAIL; + + s = s.Slice(index + 2); + offset += index + 2; + } + + FAIL: + if (typeof(TResult) != typeof(Unit)) + result.Dispose(); + + return 0; + } + } + + #endregion + + /// + /// A marker struct used to indicate a string literal enclosed in single quotes (' '). + /// + private struct SingleQuoteString; + + /// + /// A marker struct used to indicate a string literal enclosed in double quotes (" "). + /// + private struct DoubleQuoteString; +} diff --git a/src/Ramstack.Parsing/Literal.UnicodeEscapeSequence.cs b/src/Ramstack.Parsing/Literal.UnicodeEscapeSequence.cs index 7464aa5..73d0ed1 100644 --- a/src/Ramstack.Parsing/Literal.UnicodeEscapeSequence.cs +++ b/src/Ramstack.Parsing/Literal.UnicodeEscapeSequence.cs @@ -1,107 +1,107 @@ -namespace Ramstack.Parsing; - -partial class Literal -{ - /// - /// A lookup table for converting hexadecimal characters to their corresponding - /// integer values. Each entry in the table corresponds to a character code, - /// and the value indicates the integer value of the hexadecimal digit (0-15). - /// A value of -1 indicates that the character is not a valid hexadecimal digit. - /// - internal static ReadOnlySpan HexTable => - [ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - ]; - - private static Parser? _unicodeEscapeSequence; - - /// - /// Gets a parser that matches a Unicode escape sequence in the format \uXXXX. - /// - /// - /// The parser expects a Unicode escape sequence in the format \uXXXX where XXXX - /// is a 4-digit hexadecimal number. - /// For example: \u0041 represents the character 'A'. - /// - public static Parser UnicodeEscapeSequence => _unicodeEscapeSequence ??= new UnicodeEscapeSequenceParser(); - - #region Inner type: UnicodeEscapeSequenceParser - - /// - /// Represents a parser that converts unicode escape sequences to their corresponding characters. - /// - private sealed class UnicodeEscapeSequenceParser() : Parser("unicode escape") where T : struct - { - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Remaining; - - if ((uint)s.Length > 5 && s.StartsWith("\\u")) - { - var v1 = (nint)s[2]; - var v2 = (nint)s[3]; - var v3 = (nint)s[4]; - var v4 = (nint)s[5]; - - // We use Unsafe-methods here to index into the HexTable without bounds checks. - // - // The check 'if ((uint)(v1 | v2 | v3 | v4) <= 127)' guarantees that all four values - // are within the range 0..127. - // - // This makes it safe to access HexTable[v1], HexTable[v2], HexTable[v3], and HexTable[v4] - // without additional bounds checks, because HexTable has exactly 128 elements. - // By using Unsafe.Add, we avoid the overhead of safety checks and improve performance. - // - // Once the JIT compiler can properly recognize and optimize this pattern, we may be able - // to replace these unsafe calls with safe indexed access without performance loss. - if ((uint)(v1 | v2 | v3 | v4) <= 127) - { - ref var table = ref MemoryMarshal.GetReference(HexTable); - - var r1 = (int)Unsafe.Add(ref table, v1); - var r2 = (int)Unsafe.Add(ref table, v2); - var r3 = (int)Unsafe.Add(ref table, v3); - var r4 = (int)Unsafe.Add(ref table, v4); - - r1 <<= 12; - r2 <<= 08; - r3 <<= 04; - var ch = r1 | r2 | r3 | r4; - - if ((uint)ch <= 0xFFFF) - { - if (typeof(T) == typeof(char)) - value = (T)(object)(char)ch; - else - value = default; - - context.Advance(6); - return true; - } - } - } - - context.ReportExpected(Name); - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new UnicodeEscapeSequenceParser { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new UnicodeEscapeSequenceParser { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Literal +{ + /// + /// A lookup table for converting hexadecimal characters to their corresponding + /// integer values. Each entry in the table corresponds to a character code, + /// and the value indicates the integer value of the hexadecimal digit (0-15). + /// A value of -1 indicates that the character is not a valid hexadecimal digit. + /// + internal static ReadOnlySpan HexTable => + [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + ]; + + private static Parser? _unicodeEscapeSequence; + + /// + /// Gets a parser that matches a Unicode escape sequence in the format \uXXXX. + /// + /// + /// The parser expects a Unicode escape sequence in the format \uXXXX where XXXX + /// is a 4-digit hexadecimal number. + /// For example: \u0041 represents the character 'A'. + /// + public static Parser UnicodeEscapeSequence => _unicodeEscapeSequence ??= new UnicodeEscapeSequenceParser(); + + #region Inner type: UnicodeEscapeSequenceParser + + /// + /// Represents a parser that converts unicode escape sequences to their corresponding characters. + /// + private sealed class UnicodeEscapeSequenceParser() : Parser("unicode escape") where T : struct + { + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Remaining; + + if ((uint)s.Length > 5 && s.StartsWith("\\u")) + { + var v1 = (nint)s[2]; + var v2 = (nint)s[3]; + var v3 = (nint)s[4]; + var v4 = (nint)s[5]; + + // We use Unsafe-methods here to index into the HexTable without bounds checks. + // + // The check 'if ((uint)(v1 | v2 | v3 | v4) <= 127)' guarantees that all four values + // are within the range 0..127. + // + // This makes it safe to access HexTable[v1], HexTable[v2], HexTable[v3], and HexTable[v4] + // without additional bounds checks, because HexTable has exactly 128 elements. + // By using Unsafe.Add, we avoid the overhead of safety checks and improve performance. + // + // Once the JIT compiler can properly recognize and optimize this pattern, we may be able + // to replace these unsafe calls with safe indexed access without performance loss. + if ((uint)(v1 | v2 | v3 | v4) <= 127) + { + ref var table = ref MemoryMarshal.GetReference(HexTable); + + var r1 = (int)Unsafe.Add(ref table, v1); + var r2 = (int)Unsafe.Add(ref table, v2); + var r3 = (int)Unsafe.Add(ref table, v3); + var r4 = (int)Unsafe.Add(ref table, v4); + + r1 <<= 12; + r2 <<= 08; + r3 <<= 04; + var ch = r1 | r2 | r3 | r4; + + if ((uint)ch <= 0xFFFF) + { + if (typeof(T) == typeof(char)) + value = (T)(object)(char)ch; + else + value = default; + + context.Advance(6); + return true; + } + } + } + + context.ReportExpected(Name); + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new UnicodeEscapeSequenceParser { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new UnicodeEscapeSequenceParser { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Literal.cs b/src/Ramstack.Parsing/Literal.cs index 5bfecde..4dea349 100644 --- a/src/Ramstack.Parsing/Literal.cs +++ b/src/Ramstack.Parsing/Literal.cs @@ -1,6 +1,6 @@ -namespace Ramstack.Parsing; - -/// -/// Provides parsers for numbers, strings, characters, and escape sequences. -/// -public static partial class Literal; +namespace Ramstack.Parsing; + +/// +/// Provides parsers for numbers, strings, characters, and escape sequences. +/// +public static partial class Literal; diff --git a/src/Ramstack.Parsing/MapFunc.cs b/src/Ramstack.Parsing/MapFunc.cs index 6c4e911..c3376aa 100644 --- a/src/Ramstack.Parsing/MapFunc.cs +++ b/src/Ramstack.Parsing/MapFunc.cs @@ -1,28 +1,28 @@ -namespace Ramstack.Parsing; - -/// -/// Encapsulates a method that receives a representing a matched -/// segment from the parsed source and returns a . -/// -/// The matched segment from the parsed source. -/// The type of the returned value. -/// -/// A that is the result of processing or transforming -/// the matched segment. -/// -public delegate TResult MapFunc(Match match); - -/// -/// Encapsulates a method that receives a representing a matched -/// segment from the parsed source, along with a parsed value of type , -/// and returns a . -/// -/// The type of the parsed value. -/// The type of the returned value. -/// The matched segment from the parsed source. -/// The parsed value of type . -/// -/// A that is the result of processing or transforming -/// both the matched segment and the parsed value. -/// -public delegate TResult MapFunc(Match match, T value); +namespace Ramstack.Parsing; + +/// +/// Encapsulates a method that receives a representing a matched +/// segment from the parsed source and returns a . +/// +/// The matched segment from the parsed source. +/// The type of the returned value. +/// +/// A that is the result of processing or transforming +/// the matched segment. +/// +public delegate TResult MapFunc(Match match); + +/// +/// Encapsulates a method that receives a representing a matched +/// segment from the parsed source, along with a parsed value of type , +/// and returns a . +/// +/// The type of the parsed value. +/// The type of the returned value. +/// The matched segment from the parsed source. +/// The parsed value of type . +/// +/// A that is the result of processing or transforming +/// both the matched segment and the parsed value. +/// +public delegate TResult MapFunc(Match match, T value); diff --git a/src/Ramstack.Parsing/Match.cs b/src/Ramstack.Parsing/Match.cs index f15d0f4..57a0912 100644 --- a/src/Ramstack.Parsing/Match.cs +++ b/src/Ramstack.Parsing/Match.cs @@ -1,101 +1,101 @@ -namespace Ramstack.Parsing; - -/// -/// Represents the matched segment of the source text. -/// -[SuppressMessage("ReSharper", "ConvertToAutoPropertyWhenPossible")] -public readonly ref struct Match -{ - #if NET7_0_OR_GREATER - private readonly ref char _source; - #else - private readonly ReadOnlySpan _source; - #endif - - private readonly int _index; - private readonly int _length; - - /// - /// Gets the zero-based position of the matched segment in the source. - /// - public int Index => _index; - - /// - /// Gets the number of characters that were matched. - /// - public int Length => _length; - - /// - /// Gets a read-only span of the matched characters. - /// - public ReadOnlySpan Value => this; - - /// - /// Initializes a new instance of the structure. - /// - /// The source containing the matched text. - /// The zero-based position of the matched segment. - /// The number of matched characters. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Match(ReadOnlySpan source, int index, int length) - { - #if NET7_0_OR_GREATER - _source = ref MemoryMarshal.GetReference(source); - #else - _source = source; - #endif - - _index = index; - _length = length; - } - - /// - /// Deconstructs the current into its individual components. - /// - /// When this method returns, contains the zero-based position of the matched segment. - /// When this method returns, contains the number of matched characters. - [EditorBrowsable(EditorBrowsableState.Never)] - public void Deconstruct(out int index, out int length) - { - index = Index; - length = Length; - } - - /// - public override string ToString() => - new string(Value); - - /// - /// Implicit conversion from a to a . - /// - /// The value. - /// - /// A representing the matched segment. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator ReadOnlySpan(Match match) - { - #if NET7_0_OR_GREATER - return MemoryMarshal.CreateReadOnlySpan( - ref Unsafe.Add(ref match._source, (nint)(uint)match._index), - match._length); - #else - return MemoryMarshal.CreateReadOnlySpan( - ref Unsafe.Add( - ref MemoryMarshal.GetReference(match._source), - (nint)(uint)match._index), - match._length); - #endif - } - - /// - /// Implicit conversion from a to a . - /// This tuple contains the zero-based index and the length of the matched segment. - /// - /// The value. - /// - /// A containing the index and length of the matched segment. - /// - public static implicit operator (int Index, int Length)(Match match) => - (match.Index, match.Length); -} +namespace Ramstack.Parsing; + +/// +/// Represents the matched segment of the source text. +/// +[SuppressMessage("ReSharper", "ConvertToAutoPropertyWhenPossible")] +public readonly ref struct Match +{ + #if NET7_0_OR_GREATER + private readonly ref char _source; + #else + private readonly ReadOnlySpan _source; + #endif + + private readonly int _index; + private readonly int _length; + + /// + /// Gets the zero-based position of the matched segment in the source. + /// + public int Index => _index; + + /// + /// Gets the number of characters that were matched. + /// + public int Length => _length; + + /// + /// Gets a read-only span of the matched characters. + /// + public ReadOnlySpan Value => this; + + /// + /// Initializes a new instance of the structure. + /// + /// The source containing the matched text. + /// The zero-based position of the matched segment. + /// The number of matched characters. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Match(ReadOnlySpan source, int index, int length) + { + #if NET7_0_OR_GREATER + _source = ref MemoryMarshal.GetReference(source); + #else + _source = source; + #endif + + _index = index; + _length = length; + } + + /// + /// Deconstructs the current into its individual components. + /// + /// When this method returns, contains the zero-based position of the matched segment. + /// When this method returns, contains the number of matched characters. + [EditorBrowsable(EditorBrowsableState.Never)] + public void Deconstruct(out int index, out int length) + { + index = Index; + length = Length; + } + + /// + public override string ToString() => + new string(Value); + + /// + /// Implicit conversion from a to a . + /// + /// The value. + /// + /// A representing the matched segment. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(Match match) + { + #if NET7_0_OR_GREATER + return MemoryMarshal.CreateReadOnlySpan( + ref Unsafe.Add(ref match._source, (nint)(uint)match._index), + match._length); + #else + return MemoryMarshal.CreateReadOnlySpan( + ref Unsafe.Add( + ref MemoryMarshal.GetReference(match._source), + (nint)(uint)match._index), + match._length); + #endif + } + + /// + /// Implicit conversion from a to a . + /// This tuple contains the zero-based index and the length of the matched segment. + /// + /// The value. + /// + /// A containing the index and length of the matched segment. + /// + public static implicit operator (int Index, int Length)(Match match) => + (match.Index, match.Length); +} diff --git a/src/Ramstack.Parsing/NumberKind.cs b/src/Ramstack.Parsing/NumberKind.cs index 977f64e..8f83838 100644 --- a/src/Ramstack.Parsing/NumberKind.cs +++ b/src/Ramstack.Parsing/NumberKind.cs @@ -1,38 +1,38 @@ -namespace Ramstack.Parsing; - -/// -/// Specifies the types of numeric string representations. -/// -public enum NumberKind -{ - /// - /// Selects the numeric format based on the target type. - /// - Auto, - - /// - /// Indicates that the input represents a floating-point numeric string, - /// optionally prefixed with a "+" or "-" sign. - /// - Float, - - /// - /// Indicates that the input represents an integer numeric string, - /// optionally prefixed with a "+" or "-" sign. - /// - Integer, - - /// - /// Indicates that the input represents a hexadecimal numeric string. - /// Leading "+" or "-" signs are not allowed. - /// - HexNumber, - - #if NET8_0_OR_GREATER - /// - /// Indicates that the input represents a binary numeric string. - /// Leading "+" or "-" signs are not allowed. - /// - BinaryNumber - #endif -} +namespace Ramstack.Parsing; + +/// +/// Specifies the types of numeric string representations. +/// +public enum NumberKind +{ + /// + /// Selects the numeric format based on the target type. + /// + Auto, + + /// + /// Indicates that the input represents a floating-point numeric string, + /// optionally prefixed with a "+" or "-" sign. + /// + Float, + + /// + /// Indicates that the input represents an integer numeric string, + /// optionally prefixed with a "+" or "-" sign. + /// + Integer, + + /// + /// Indicates that the input represents a hexadecimal numeric string. + /// Leading "+" or "-" signs are not allowed. + /// + HexNumber, + + #if NET8_0_OR_GREATER + /// + /// Indicates that the input represents a binary numeric string. + /// Leading "+" or "-" signs are not allowed. + /// + BinaryNumber + #endif +} diff --git a/src/Ramstack.Parsing/OptionalValue.cs b/src/Ramstack.Parsing/OptionalValue.cs index e43266b..a7b69c0 100644 --- a/src/Ramstack.Parsing/OptionalValue.cs +++ b/src/Ramstack.Parsing/OptionalValue.cs @@ -1,30 +1,30 @@ -namespace Ramstack.Parsing; - -/// -/// Represents an optional value of type . -/// -/// The underlying type of the optional value. -public readonly struct OptionalValue -{ - /// - /// The underlying value. - /// If is , this contains an uninitialized value. - /// - public readonly T? Value; - - /// - /// A value indicating whether the current object - /// has a valid value of its underlying type. - /// - public readonly bool HasValue; - - /// - /// Initializes a new instance of the structure with the specified value. - /// - /// The value to store. - public OptionalValue(T value) - { - Value = value; - HasValue = true; - } -} +namespace Ramstack.Parsing; + +/// +/// Represents an optional value of type . +/// +/// The underlying type of the optional value. +public readonly struct OptionalValue +{ + /// + /// The underlying value. + /// If is , this contains an uninitialized value. + /// + public readonly T? Value; + + /// + /// A value indicating whether the current object + /// has a valid value of its underlying type. + /// + public readonly bool HasValue; + + /// + /// Initializes a new instance of the structure with the specified value. + /// + /// The value to store. + public OptionalValue(T value) + { + Value = value; + HasValue = true; + } +} diff --git a/src/Ramstack.Parsing/ParseContext.cs b/src/Ramstack.Parsing/ParseContext.cs index fc0d77d..23043cf 100644 --- a/src/Ramstack.Parsing/ParseContext.cs +++ b/src/Ramstack.Parsing/ParseContext.cs @@ -1,284 +1,284 @@ -namespace Ramstack.Parsing; - -/// -/// Represents the context in which parsing operations are performed. -/// -public ref struct ParseContext -{ - private readonly ReadOnlySpan _source; - private (int Index, int Length) _match; - private ErrorSet _diagnostics; - private int _position; - - /// - /// Gets or sets the diagnostic state of this parsing context. - /// - public DiagnosticState DiagnosticState { get; set; } - - /// - /// Gets the source text being parsed. - /// - [SuppressMessage("ReSharper", "ConvertToAutoPropertyWhenPossible")] - public readonly ReadOnlySpan Source => _source; - - /// - /// Gets the portion of the source text that has not yet been parsed. - /// - public readonly ReadOnlySpan Remaining - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - var source = _source; - var length = source.Length - _position; - - Debug.Assert(_source[_position..].Length == length); - - ref var reference = ref Unsafe.Add( - ref MemoryMarshal.GetReference(source), - (nint)(uint)_position); - - return MemoryMarshal.CreateReadOnlySpan(ref reference, length); - } - } - - /// - /// Gets the current position in the source text. - /// - [SuppressMessage("ReSharper", "ConvertToAutoPropertyWithPrivateSetter")] - public readonly int Position => _position; - - /// - /// Gets the most recently matched segment of the source text. - /// - public readonly Match MatchedSegment => new Match(_source, _match.Index, _match.Length); - - /// - /// Initializes a new instance of the structure. - /// - /// The source text to parse. - public ParseContext(ReadOnlySpan source) - { - _source = source; - _diagnostics = new ErrorSet(); - } - - /// - /// Advances the current position by the specified number of characters. - /// - /// The number of characters to advance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) - { - var pos = _position; - var len = _source.Length; - - if ((uint)count > (uint)(len - pos)) - count = len - pos; - - _match.Index = pos; - _match.Length = count; - _position = pos + count; - } - - /// - /// Sets the most recently matched segment in the source text, spanning from the specified bookmark position to the current position. - /// - /// The bookmark that marks the start position of the matched segment. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetMatched(PositionBookmark bookmark) - { - var length = Position - bookmark.Position; - _match.Index = bookmark.Position; - _match.Length = length; - } - - /// - /// Sets the most recently matched segment in the source text. - /// - /// The zero-based start position of the matched segment. - /// The length of the matched segment. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetMatched(int index, int length) - { - Debug.Assert(_source.Slice(index, length).Length == length); - - var len = _source.Length; - - if ((uint)index > (uint)len) - { - (index, length) = (len, 0); - } - else if ((uint)length > (uint)(len - index)) - { - length = len - index; - } - - _match.Index = index; - _match.Length = length; - } - - /// - /// Reports a missing expected sequence or rule at the current position. - /// The expected sequence or rule is added only if diagnostics are enabled. - /// - /// The expected sequence or rule. - public void ReportExpected(string? expected) - { - if (DiagnosticState == DiagnosticState.Normal && expected is not null) - _diagnostics.ReportExpected(_position, expected); - } - - /// - /// Reports a missing expected sequence or rule at the specified position. - /// The expected sequence or rule is added only if diagnostics are enabled. - /// - /// The position in the source text where the error occurred. - /// The expected sequence or rule. - internal void ReportExpected(int index, string? expected) - { - if (DiagnosticState == DiagnosticState.Normal && expected is not null) - _diagnostics.ReportExpected(index, expected); - } - - /// - /// Reports multiple missing expected sequences or rules at the current position. - /// The expected sequences or rules are added only if diagnostics are enabled. - /// - /// An array of expected sequences or rules. - public void ReportExpected(string[] expected) - { - if (DiagnosticState == DiagnosticState.Normal) - _diagnostics.ReportExpected(_position, expected); - } - - /// - /// Suppresses diagnostics and returns the previous diagnostic state. - /// - /// - /// The previous , which can be used to restore - /// the diagnostic state later via . - /// - public DiagnosticState SuppressDiagnostics() - { - var state = DiagnosticState; - DiagnosticState = DiagnosticState.Suppressed; - return state; - } - - /// - /// Conditionally suppresses diagnostic messages if the parser is named. - /// If is not , diagnostic messages - /// will be suppressed until is called. - /// - /// The name of the parser. If this value is not , - /// diagnostics will be suppressed for its sub-parsers. - /// - /// The previous , which can be used to restore - /// the diagnostic state later via . - /// - /// - /// - /// This method is useful in scenarios where multiple sub-parsers - /// could generate redundant diagnostic messages, yet the top-level (named) - /// parser only needs to present a single, consolidated error message. - /// - /// - /// - /// Consider, for example, a grammar for parsing a float value: - /// - /// // ('+' / '-')? [0-9]+ (.[0-9]+)? - /// // [ sign ] [int ] [fraction] - /// - /// var parser = Seq( - /// Set("+-").Optional(), // sign - /// Set("0-9").OneOrMore(), // integer - /// Seq( - /// L('.'), - /// Set("0-9").OneOrMore() - /// ).Optional() // fraction - /// ).As("float"); - /// - /// - /// We can visualize this parser, where float is the top-level - /// named parser and sign, integer, and fraction are its sub-parsers: - /// - /// float - /// ├─ sign (optional) - /// │ └─ [ '+' | '-' ] - /// ├─ integer - /// │ └─ [0-9]+ - /// └─ fraction (optional) - /// ├─ '.' - /// └─ [0-9]+ - /// - /// When a user input is missing digits or has a misplaced decimal point, - /// each of these sub-parsers could produce an individual diagnostic: - /// "Expected digit" or "Expected '.'". However, all these messages are eventually - /// discarded in favor of a single top-level error from the float parser.

- /// - /// By suppressing diagnostics from sub-parsers and only producing a concise top-level message - /// like "(1:5) Expected float" we avoid unnecessary memory allocations and generally - /// do less work, which ultimately improves performance. - ///
- ///
- /// - /// - /// var previousState = context.SuppressDiagnosticsIfNamed(Name); - /// // ... parsing logic ... - /// context.RestoreDiagnosticState(previousState); - /// - /// - public DiagnosticState SuppressDiagnosticsIfNamed(string? name) - { - var state = DiagnosticState; - if (name is not null) - DiagnosticState = DiagnosticState.Suppressed; - return state; - } - - /// - /// Restores the diagnostic state to the specified value. - /// - /// The to restore. - public void RestoreDiagnosticState(DiagnosticState state) => - DiagnosticState = state; - - /// - /// Returns a bookmark for the current parser position. - /// - /// - /// A representing the current position. - /// - public readonly PositionBookmark BookmarkPosition() => - new PositionBookmark(_position); - - /// - /// Restores the parser position using the specified bookmark. - /// - /// The bookmark to restore from. - public void RestorePosition(PositionBookmark bookmark) - { - _position = bookmark.Position; - _match.Length = 0; - } - - /// - public readonly override string ToString() => - GenerateErrorMessage(_source, _diagnostics.Index, _diagnostics.ToString()); - - /// - /// Generates a formatted error message that includes line and column information. - /// - /// The source text. - /// The position in the source text where the error occurred. - /// The error message. - /// - /// A formatted error message that includes line and column details. - /// - internal static string GenerateErrorMessage(ReadOnlySpan source, int position, string message) - { - var (line, column) = TextHelper.GetLineColumn(source, position); - return $"({line}:{column}) {message}"; - } -} +namespace Ramstack.Parsing; + +/// +/// Represents the context in which parsing operations are performed. +/// +public ref struct ParseContext +{ + private readonly ReadOnlySpan _source; + private (int Index, int Length) _match; + private ErrorSet _diagnostics; + private int _position; + + /// + /// Gets or sets the diagnostic state of this parsing context. + /// + public DiagnosticState DiagnosticState { get; set; } + + /// + /// Gets the source text being parsed. + /// + [SuppressMessage("ReSharper", "ConvertToAutoPropertyWhenPossible")] + public readonly ReadOnlySpan Source => _source; + + /// + /// Gets the portion of the source text that has not yet been parsed. + /// + public readonly ReadOnlySpan Remaining + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var source = _source; + var length = source.Length - _position; + + Debug.Assert(_source[_position..].Length == length); + + ref var reference = ref Unsafe.Add( + ref MemoryMarshal.GetReference(source), + (nint)(uint)_position); + + return MemoryMarshal.CreateReadOnlySpan(ref reference, length); + } + } + + /// + /// Gets the current position in the source text. + /// + [SuppressMessage("ReSharper", "ConvertToAutoPropertyWithPrivateSetter")] + public readonly int Position => _position; + + /// + /// Gets the most recently matched segment of the source text. + /// + public readonly Match MatchedSegment => new Match(_source, _match.Index, _match.Length); + + /// + /// Initializes a new instance of the structure. + /// + /// The source text to parse. + public ParseContext(ReadOnlySpan source) + { + _source = source; + _diagnostics = new ErrorSet(); + } + + /// + /// Advances the current position by the specified number of characters. + /// + /// The number of characters to advance. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) + { + var pos = _position; + var len = _source.Length; + + if ((uint)count > (uint)(len - pos)) + count = len - pos; + + _match.Index = pos; + _match.Length = count; + _position = pos + count; + } + + /// + /// Sets the most recently matched segment in the source text, spanning from the specified bookmark position to the current position. + /// + /// The bookmark that marks the start position of the matched segment. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetMatched(PositionBookmark bookmark) + { + var length = Position - bookmark.Position; + _match.Index = bookmark.Position; + _match.Length = length; + } + + /// + /// Sets the most recently matched segment in the source text. + /// + /// The zero-based start position of the matched segment. + /// The length of the matched segment. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetMatched(int index, int length) + { + Debug.Assert(_source.Slice(index, length).Length == length); + + var len = _source.Length; + + if ((uint)index > (uint)len) + { + (index, length) = (len, 0); + } + else if ((uint)length > (uint)(len - index)) + { + length = len - index; + } + + _match.Index = index; + _match.Length = length; + } + + /// + /// Reports a missing expected sequence or rule at the current position. + /// The expected sequence or rule is added only if diagnostics are enabled. + /// + /// The expected sequence or rule. + public void ReportExpected(string? expected) + { + if (DiagnosticState == DiagnosticState.Normal && expected is not null) + _diagnostics.ReportExpected(_position, expected); + } + + /// + /// Reports a missing expected sequence or rule at the specified position. + /// The expected sequence or rule is added only if diagnostics are enabled. + /// + /// The position in the source text where the error occurred. + /// The expected sequence or rule. + internal void ReportExpected(int index, string? expected) + { + if (DiagnosticState == DiagnosticState.Normal && expected is not null) + _diagnostics.ReportExpected(index, expected); + } + + /// + /// Reports multiple missing expected sequences or rules at the current position. + /// The expected sequences or rules are added only if diagnostics are enabled. + /// + /// An array of expected sequences or rules. + public void ReportExpected(string[] expected) + { + if (DiagnosticState == DiagnosticState.Normal) + _diagnostics.ReportExpected(_position, expected); + } + + /// + /// Suppresses diagnostics and returns the previous diagnostic state. + /// + /// + /// The previous , which can be used to restore + /// the diagnostic state later via . + /// + public DiagnosticState SuppressDiagnostics() + { + var state = DiagnosticState; + DiagnosticState = DiagnosticState.Suppressed; + return state; + } + + /// + /// Conditionally suppresses diagnostic messages if the parser is named. + /// If is not , diagnostic messages + /// will be suppressed until is called. + /// + /// The name of the parser. If this value is not , + /// diagnostics will be suppressed for its sub-parsers. + /// + /// The previous , which can be used to restore + /// the diagnostic state later via . + /// + /// + /// + /// This method is useful in scenarios where multiple sub-parsers + /// could generate redundant diagnostic messages, yet the top-level (named) + /// parser only needs to present a single, consolidated error message. + /// + /// + /// + /// Consider, for example, a grammar for parsing a float value: + /// + /// // ('+' / '-')? [0-9]+ (.[0-9]+)? + /// // [ sign ] [int ] [fraction] + /// + /// var parser = Seq( + /// Set("+-").Optional(), // sign + /// Set("0-9").OneOrMore(), // integer + /// Seq( + /// L('.'), + /// Set("0-9").OneOrMore() + /// ).Optional() // fraction + /// ).As("float"); + /// + /// + /// We can visualize this parser, where float is the top-level + /// named parser and sign, integer, and fraction are its sub-parsers: + /// + /// float + /// ├─ sign (optional) + /// │ └─ [ '+' | '-' ] + /// ├─ integer + /// │ └─ [0-9]+ + /// └─ fraction (optional) + /// ├─ '.' + /// └─ [0-9]+ + /// + /// When a user input is missing digits or has a misplaced decimal point, + /// each of these sub-parsers could produce an individual diagnostic: + /// "Expected digit" or "Expected '.'". However, all these messages are eventually + /// discarded in favor of a single top-level error from the float parser.

+ /// + /// By suppressing diagnostics from sub-parsers and only producing a concise top-level message + /// like "(1:5) Expected float" we avoid unnecessary memory allocations and generally + /// do less work, which ultimately improves performance. + ///
+ ///
+ /// + /// + /// var previousState = context.SuppressDiagnosticsIfNamed(Name); + /// // ... parsing logic ... + /// context.RestoreDiagnosticState(previousState); + /// + /// + public DiagnosticState SuppressDiagnosticsIfNamed(string? name) + { + var state = DiagnosticState; + if (name is not null) + DiagnosticState = DiagnosticState.Suppressed; + return state; + } + + /// + /// Restores the diagnostic state to the specified value. + /// + /// The to restore. + public void RestoreDiagnosticState(DiagnosticState state) => + DiagnosticState = state; + + /// + /// Returns a bookmark for the current parser position. + /// + /// + /// A representing the current position. + /// + public readonly PositionBookmark BookmarkPosition() => + new PositionBookmark(_position); + + /// + /// Restores the parser position using the specified bookmark. + /// + /// The bookmark to restore from. + public void RestorePosition(PositionBookmark bookmark) + { + _position = bookmark.Position; + _match.Length = 0; + } + + /// + public readonly override string ToString() => + GenerateErrorMessage(_source, _diagnostics.Index, _diagnostics.ToString()); + + /// + /// Generates a formatted error message that includes line and column information. + /// + /// The source text. + /// The position in the source text where the error occurred. + /// The error message. + /// + /// A formatted error message that includes line and column details. + /// + internal static string GenerateErrorMessage(ReadOnlySpan source, int position, string message) + { + var (line, column) = TextHelper.GetLineColumn(source, position); + return $"({line}:{column}) {message}"; + } +} diff --git a/src/Ramstack.Parsing/ParseResult.cs b/src/Ramstack.Parsing/ParseResult.cs index d1fa1fd..7c56744 100644 --- a/src/Ramstack.Parsing/ParseResult.cs +++ b/src/Ramstack.Parsing/ParseResult.cs @@ -1,39 +1,39 @@ -namespace Ramstack.Parsing; - -/// -/// Represents the result of a parsing operation. -/// -/// The type of the parsed value. -public sealed class ParseResult -{ - /// - /// Gets a value indicating whether the parsing was successful. - /// - public bool Success => string.IsNullOrEmpty(ErrorMessage); - - /// - /// Gets the parsed value if parsing succeeded; - /// otherwise, the value of . - /// - public T? Value { get; init; } - - /// - /// Gets the number of characters that were successfully matched during parsing. - /// - public required int Length { get; init; } - - /// - /// Gets the error message that occurred during parsing, if any. - /// - [MemberNotNullWhen(false, nameof(Success))] - public string? ErrorMessage { get; init; } - - /// - /// Gets the exception that was thrown during parsing, if any. - /// - public Exception? Exception { get; init; } - - /// - public override string ToString() => - (Success ? Value?.ToString() : ErrorMessage) ?? ""; -} +namespace Ramstack.Parsing; + +/// +/// Represents the result of a parsing operation. +/// +/// The type of the parsed value. +public sealed class ParseResult +{ + /// + /// Gets a value indicating whether the parsing was successful. + /// + public bool Success => string.IsNullOrEmpty(ErrorMessage); + + /// + /// Gets the parsed value if parsing succeeded; + /// otherwise, the value of . + /// + public T? Value { get; init; } + + /// + /// Gets the number of characters that were successfully matched during parsing. + /// + public required int Length { get; init; } + + /// + /// Gets the error message that occurred during parsing, if any. + /// + [MemberNotNullWhen(false, nameof(Success))] + public string? ErrorMessage { get; init; } + + /// + /// Gets the exception that was thrown during parsing, if any. + /// + public Exception? Exception { get; init; } + + /// + public override string ToString() => + (Success ? Value?.ToString() : ErrorMessage) ?? ""; +} diff --git a/src/Ramstack.Parsing/Parser.And.cs b/src/Ramstack.Parsing/Parser.And.cs index 19e7a39..09d8f6b 100644 --- a/src/Ramstack.Parsing/Parser.And.cs +++ b/src/Ramstack.Parsing/Parser.And.cs @@ -1,59 +1,59 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a predicate parser that succeeds if the specified parser succeeds. - /// - /// - /// The predicate parser does not advance the parsing position. - /// - /// The parser that is expected to succeed. - /// - /// A predicate parser that succeeds if the specified parser succeeds. - /// - public static Parser And(Parser parser) => - new AssertParser(parser.Void()); - - #region Inner type: AssertParser - - /// - /// Represents a predicate parser that evaluates based on the specified assertion type. - /// - /// - /// The assertion type used to determine the result of the parse operation. - /// Expected to be either or . - /// - /// The underlying parser to evaluate. - private sealed class AssertParser(Parser parser) : Parser - { - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var state = context.SuppressDiagnostics(); - var bookmark = context.BookmarkPosition(); - - var result = parser.TryParse(ref context, out value); - - context.RestoreDiagnosticState(state); - context.RestorePosition(bookmark); - - if (typeof(TAssert) == typeof(AndAssert)) - return result; - - return !result; - } - } - - /// - /// A marker struct used to indicate that the parser should succeed only if the underlying parser succeeds. - /// - private struct AndAssert; - - /// - /// A marker struct used to indicate that the parser should succeed only if the underlying parser fails. - /// - private struct NotAssert; - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a predicate parser that succeeds if the specified parser succeeds. + /// + /// + /// The predicate parser does not advance the parsing position. + /// + /// The parser that is expected to succeed. + /// + /// A predicate parser that succeeds if the specified parser succeeds. + /// + public static Parser And(Parser parser) => + new AssertParser(parser.Void()); + + #region Inner type: AssertParser + + /// + /// Represents a predicate parser that evaluates based on the specified assertion type. + /// + /// + /// The assertion type used to determine the result of the parse operation. + /// Expected to be either or . + /// + /// The underlying parser to evaluate. + private sealed class AssertParser(Parser parser) : Parser + { + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var state = context.SuppressDiagnostics(); + var bookmark = context.BookmarkPosition(); + + var result = parser.TryParse(ref context, out value); + + context.RestoreDiagnosticState(state); + context.RestorePosition(bookmark); + + if (typeof(TAssert) == typeof(AndAssert)) + return result; + + return !result; + } + } + + /// + /// A marker struct used to indicate that the parser should succeed only if the underlying parser succeeds. + /// + private struct AndAssert; + + /// + /// A marker struct used to indicate that the parser should succeed only if the underlying parser fails. + /// + private struct NotAssert; + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Any.cs b/src/Ramstack.Parsing/Parser.Any.cs index 51263a6..7c0e1fc 100644 --- a/src/Ramstack.Parsing/Parser.Any.cs +++ b/src/Ramstack.Parsing/Parser.Any.cs @@ -1,57 +1,57 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Gets a parser that matches any single character. - /// - /// - /// This parser succeeds if any single character is available in the source text. - /// - public static Parser Any { get; } = new AnyParser(); - - #region Inner type: AnyParser - - /// - /// Represents a parser that matches any single character. - /// - private sealed class AnyParser() : Parser("any character"), ICharClassSupport where T : struct - { - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Source; - var p = context.Position; - - if ((uint)p < (uint)s.Length) - { - if (typeof(T) == typeof(char)) - value = (T)(object)s[p]; - else - value = default; - - context.Advance(1); - return true; - } - - context.ReportExpected(Name); - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new AnyParser { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new AnyParser { Name = Name }; - - /// - public CharClass GetCharClass() => - new CharClass([CharClassRange.Create('\0', '\xFFFF')]); - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Gets a parser that matches any single character. + /// + /// + /// This parser succeeds if any single character is available in the source text. + /// + public static Parser Any { get; } = new AnyParser(); + + #region Inner type: AnyParser + + /// + /// Represents a parser that matches any single character. + /// + private sealed class AnyParser() : Parser("any character"), ICharClassSupport where T : struct + { + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Source; + var p = context.Position; + + if ((uint)p < (uint)s.Length) + { + if (typeof(T) == typeof(char)) + value = (T)(object)s[p]; + else + value = default; + + context.Advance(1); + return true; + } + + context.ReportExpected(Name); + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new AnyParser { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new AnyParser { Name = Name }; + + /// + public CharClass GetCharClass() => + new CharClass([CharClassRange.Create('\0', '\xFFFF')]); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.As.cs b/src/Ramstack.Parsing/Parser.As.cs index 1e95753..8c477d1 100644 --- a/src/Ramstack.Parsing/Parser.As.cs +++ b/src/Ramstack.Parsing/Parser.As.cs @@ -1,55 +1,55 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a new parser with the specified name. - /// - /// The type of the value produced by the parser. - /// The parser instance to name. - /// The name to assign to the parser. - /// - /// A new parser instance with the specified name. - /// - public static Parser As(this Parser parser, string name) - { - Argument.ThrowIfNullOrEmpty(name); - return parser.ToNamedParser(name); - } - - #region Inner type: NamedParser - - /// - /// Represents a parser that has an assigned name. - /// - /// The type of the value produced by the parser. - /// The underlying parser to wrap. - internal sealed class NamedParser(Parser parser) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var state = context.SuppressDiagnostics(); - - if (parser.TryParse(ref context, out value)) - { - context.RestoreDiagnosticState(state); - return true; - } - - context.RestoreDiagnosticState(state); - context.ReportExpected(Name); - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new NamedParser(parser) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new NamedParser(parser.Void()) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a new parser with the specified name. + /// + /// The type of the value produced by the parser. + /// The parser instance to name. + /// The name to assign to the parser. + /// + /// A new parser instance with the specified name. + /// + public static Parser As(this Parser parser, string name) + { + Argument.ThrowIfNullOrEmpty(name); + return parser.ToNamedParser(name); + } + + #region Inner type: NamedParser + + /// + /// Represents a parser that has an assigned name. + /// + /// The type of the value produced by the parser. + /// The underlying parser to wrap. + internal sealed class NamedParser(Parser parser) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var state = context.SuppressDiagnostics(); + + if (parser.TryParse(ref context, out value)) + { + context.RestoreDiagnosticState(state); + return true; + } + + context.RestoreDiagnosticState(state); + context.ReportExpected(Name); + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new NamedParser(parser) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new NamedParser(parser.Void()) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Between.cs b/src/Ramstack.Parsing/Parser.Between.cs index 29f5a20..6e7d01c 100644 --- a/src/Ramstack.Parsing/Parser.Between.cs +++ b/src/Ramstack.Parsing/Parser.Between.cs @@ -1,90 +1,90 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that applies the specified parser both before and after the main parser, - /// returning the result of the main parser. - /// - /// The type of the value produced by the main parser. - /// The type of the value produced by the parser to be applied around the main parser (ignored in the final result). - /// The main parser whose result will be returned. - /// The parser to apply before and after the main parser. - /// - /// A new that applies before and after , - /// returning the result of the main parser. - /// - public static Parser Between(this Parser parser, Parser around) => - new BetweenParser(parser, around.Void()); - - /// - /// Creates a parser that applies the specified parsers before and after the main parser, - /// returning the result of the main parser. - /// - /// The type of the value produced by the main parser. - /// The type of value produced by the parser applied before the main parser (ignored in the final result). - /// The type of value produced by the parser applied after the main parser (ignored in the final result). - /// The main parser whose result will be returned. - /// The parser to apply before the main parser. - /// The parser to apply after the main parser. - /// - /// A new that applies and then , - /// followed by , returning the result of the main parser. - /// - public static Parser Between(this Parser parser, Parser before, Parser after) => - new BetweenParser(parser, before.Void(), after.Void()); - - #region Inner type: BetweenParser - - /// - /// Represents a parser that applies the specified parsers before and after the main parser, - /// returning the result of the main parser. - /// - /// The type of the value produced by the main parser. - /// The main parser whose result will be returned. - /// The parser to apply before the main parser. - /// The parser to apply after the main parser. - private sealed class BetweenParser(Parser parser, Parser before, Parser after) : Parser - { - /// - /// Initializes a new instance of the class, - /// applying the same parser both before and after the main parser. - /// - /// The main parser whose result will be returned. - /// The parser to apply before and after the main parser. - public BetweenParser(Parser parser, Parser around) : this(parser, around, around) - { - } - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var bookmark = context.BookmarkPosition(); - - if (before.TryParse(ref context, out _) && parser.TryParse(ref context, out value)) - { - var (index, length) = context.MatchedSegment; - - if (after.TryParse(ref context, out _)) - { - context.SetMatched(index, length); - return true; - } - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() - { - return ReferenceEquals(before, after) - ? new BetweenParser(parser.Void(), before.Void()) - : new BetweenParser(parser.Void(), before.Void(), after.Void()); - } - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that applies the specified parser both before and after the main parser, + /// returning the result of the main parser. + /// + /// The type of the value produced by the main parser. + /// The type of the value produced by the parser to be applied around the main parser (ignored in the final result). + /// The main parser whose result will be returned. + /// The parser to apply before and after the main parser. + /// + /// A new that applies before and after , + /// returning the result of the main parser. + /// + public static Parser Between(this Parser parser, Parser around) => + new BetweenParser(parser, around.Void()); + + /// + /// Creates a parser that applies the specified parsers before and after the main parser, + /// returning the result of the main parser. + /// + /// The type of the value produced by the main parser. + /// The type of value produced by the parser applied before the main parser (ignored in the final result). + /// The type of value produced by the parser applied after the main parser (ignored in the final result). + /// The main parser whose result will be returned. + /// The parser to apply before the main parser. + /// The parser to apply after the main parser. + /// + /// A new that applies and then , + /// followed by , returning the result of the main parser. + /// + public static Parser Between(this Parser parser, Parser before, Parser after) => + new BetweenParser(parser, before.Void(), after.Void()); + + #region Inner type: BetweenParser + + /// + /// Represents a parser that applies the specified parsers before and after the main parser, + /// returning the result of the main parser. + /// + /// The type of the value produced by the main parser. + /// The main parser whose result will be returned. + /// The parser to apply before the main parser. + /// The parser to apply after the main parser. + private sealed class BetweenParser(Parser parser, Parser before, Parser after) : Parser + { + /// + /// Initializes a new instance of the class, + /// applying the same parser both before and after the main parser. + /// + /// The main parser whose result will be returned. + /// The parser to apply before and after the main parser. + public BetweenParser(Parser parser, Parser around) : this(parser, around, around) + { + } + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var bookmark = context.BookmarkPosition(); + + if (before.TryParse(ref context, out _) && parser.TryParse(ref context, out value)) + { + var (index, length) = context.MatchedSegment; + + if (after.TryParse(ref context, out _)) + { + context.SetMatched(index, length); + return true; + } + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() + { + return ReferenceEquals(before, after) + ? new BetweenParser(parser.Void(), before.Void()) + : new BetweenParser(parser.Void(), before.Void(), after.Void()); + } + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Char.cs b/src/Ramstack.Parsing/Parser.Char.cs index b8d2ccf..e705ced 100644 --- a/src/Ramstack.Parsing/Parser.Char.cs +++ b/src/Ramstack.Parsing/Parser.Char.cs @@ -1,88 +1,88 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that matches a single instance of the specified character. - /// - /// The character to match. - /// - /// A that matches the specified character. - /// - public static Parser L(char ch) => - new CharParser(ch) { Name = ch.ToPrintable() }; - - /// - /// Creates a parser that matches any character in the specified string. - /// - /// A string containing one or more characters to match. - /// - /// A that matches any character in . - /// - public static Parser OneOf(string chars) - { - Argument.ThrowIfNullOrEmpty(chars); - - if (chars.Length == 1) - return L(chars[0]); - - var @class = new CharClass( - CharClassRange.Create(chars) - ); - return Set(@class); - } - - #region Inner type: CharParser - - /// - /// Represents a parser that matches a single character. - /// - private sealed class CharParser : Parser, ICharClassSupport where T : struct - { - private readonly int _c; - - /// - /// Initializes a new instance of the class that matches the specified character. - /// - /// The character to match. - public CharParser(char c) => - _c = c; - - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Source; - var p = context.Position; - - if ((uint)p < (uint)s.Length && s[p] == _c) - { - if (typeof(T) == typeof(char)) - value = (T)(object)s[p]; - else - value = default; - - context.Advance(1); - return true; - } - - value = default; - context.ReportExpected(Name); - - return false; - } - - /// - public CharClass GetCharClass() => - new CharClass([CharClassRange.Create((char)_c)]); - - /// - protected internal override Parser ToNamedParser(string? name) => - new CharParser((char)_c) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new CharParser((char)_c) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that matches a single instance of the specified character. + /// + /// The character to match. + /// + /// A that matches the specified character. + /// + public static Parser L(char ch) => + new CharParser(ch) { Name = ch.ToPrintable() }; + + /// + /// Creates a parser that matches any character in the specified string. + /// + /// A string containing one or more characters to match. + /// + /// A that matches any character in . + /// + public static Parser OneOf(string chars) + { + Argument.ThrowIfNullOrEmpty(chars); + + if (chars.Length == 1) + return L(chars[0]); + + var @class = new CharClass( + CharClassRange.Create(chars) + ); + return Set(@class); + } + + #region Inner type: CharParser + + /// + /// Represents a parser that matches a single character. + /// + private sealed class CharParser : Parser, ICharClassSupport where T : struct + { + private readonly int _c; + + /// + /// Initializes a new instance of the class that matches the specified character. + /// + /// The character to match. + public CharParser(char c) => + _c = c; + + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Source; + var p = context.Position; + + if ((uint)p < (uint)s.Length && s[p] == _c) + { + if (typeof(T) == typeof(char)) + value = (T)(object)s[p]; + else + value = default; + + context.Advance(1); + return true; + } + + value = default; + context.ReportExpected(Name); + + return false; + } + + /// + public CharClass GetCharClass() => + new CharClass([CharClassRange.Create((char)_c)]); + + /// + protected internal override Parser ToNamedParser(string? name) => + new CharParser((char)_c) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new CharParser((char)_c) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Choice.cs b/src/Ramstack.Parsing/Parser.Choice.cs index bb4e4b2..e94b742 100644 --- a/src/Ramstack.Parsing/Parser.Choice.cs +++ b/src/Ramstack.Parsing/Parser.Choice.cs @@ -1,224 +1,224 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that attempts to parse using each of the specified parsers in sequence, - /// returning the result of the first parser that succeeds. - /// - /// The type of the value produced by each parser. - /// An array of parsers that are tried in sequence. - /// - /// A that tries each parser in sequentially - /// and returns the result of the first parser that successfully parses the input. - /// - public static Parser Choice(params Parser[] parsers) - { - Argument.ThrowIfNullOrEmpty(parsers); - - var list = new List>(); - - while (true) - { - foreach (var parser in parsers) - { - switch (parser) - { - case ChoiceParser p: - list.AddRange(p.Parsers); - break; - - default: - list.Add(parser); - break; - } - } - - if (list.Count == parsers.Length) - break; - } - - if (list.Count == 1) - return list[0]; - - if (typeof(T) == typeof(char) || typeof(T) == typeof(Unit)) - { - var count = 0; - foreach (var parser in list) - if (parser is ICharClassSupport) - count++; - - if (count > 1) - { - var @class = new CharClass(CharClassUnicodeCategory.Create(0)); - for (var i = list.Count - 1; i >= 0; i--) - { - if (list[i] is ICharClassSupport s) - { - @class = @class.MergeClasses(s.GetCharClass()); - list.RemoveAt(i); - } - } - - var p = Set(@class); - list.Insert(0, - (Parser)(object)( - typeof(T) == typeof(Unit) ? p.Void() : p) - ); - } - - parsers = list.ToArray(); - } - - if (parsers.Length == 1) - return parsers[0]; - - foreach (var parser in parsers) - if (parser.Name is null) - return new ChoiceParser(parsers); - - return new DeferredDiagnosticChoiceParser(parsers); - } - - #region Inner type: ChoiceParser - - /// - /// Represents a parser that attempts to parse using each of the specified parsers in sequence, - /// returning the result of the first parser that succeeds. - /// - /// The type of the value produced by each parser. - /// An array of sub-parsers that are tried in sequence. - private sealed class ChoiceParser(Parser[] parsers) : Parser - { - /// - /// Gets the array of parsers that are tried in sequence. - /// - public Parser[] Parsers => parsers; - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var state = context.SuppressDiagnosticsIfNamed(Name); - - foreach (var parser in parsers) - { - if (parser.TryParse(ref context, out value)) - { - context.RestoreDiagnosticState(state); - return true; - } - } - - context.RestoreDiagnosticState(state); - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new ChoiceParser(parsers) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new ChoiceParser([..parsers.Select(p => p.Void())]) { Name = Name }; - } - - #endregion - - #region Inner type: DeferredDiagnosticChoiceParser - - /// - /// Represents a parser that attempts to parse the input by sequentially - /// using each of the specified sub-parsers. This variant is designed - /// for scenarios where all sub-parsers are named. - /// - /// - /// - /// The is an optimization - /// that takes effect when all sub-parsers have names. - /// - /// - /// Instead of reporting a diagnostic message every time a sub-parser fails, - /// this parser silently attempts the next one, suppressing diagnostics in the process. - /// Only if all sub-parsers ultimately fail does it produce an error message - /// listing all parser names (i.e., every expected token). As a result, - /// if any sub-parser succeeds, no diagnostics are reported. - /// - /// - /// This approach enhances performance by avoiding multiple "failed parse" errors, - /// reducing unnecessary memory allocations, and keeping the error log - /// free of clutter when parsing is successful. - /// - /// - /// The type of the value produced by each parser. - private sealed class DeferredDiagnosticChoiceParser : Parser - { - private readonly Parser[] _parsers; - private readonly string[] _expected; - - /// - /// Initializes a new instance of the class. - /// - /// An array of sub-parsers that are tried in sequence. - /// An array of expected tokens for error reporting. - private DeferredDiagnosticChoiceParser(Parser[] parsers, string[] expected) => - (_parsers, _expected) = (parsers, expected); - - /// - /// Initializes a new instance of the class. - /// - /// An array of sub-parsers that are tried in sequence. - public DeferredDiagnosticChoiceParser(Parser[] parsers) - { - _parsers = parsers; - - var expected = new string[parsers.Length]; - if (expected.Length == parsers.Length) - for (var i = 0; i < parsers.Length; i++) - expected[i] = parsers[i].Name!; - - _expected = expected; - } - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var state = context.SuppressDiagnostics(); - - foreach (var parser in _parsers) - { - if (parser.TryParse(ref context, out value)) - { - context.RestoreDiagnosticState(state); - return true; - } - } - - context.RestoreDiagnosticState(state); - - switch (Name) - { - case not null: - context.ReportExpected(Name); - break; - - default: - context.ReportExpected(_expected); - break; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DeferredDiagnosticChoiceParser(_parsers, _expected) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new DeferredDiagnosticChoiceParser([.._parsers.Select(p => p.Void())], _expected) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that attempts to parse using each of the specified parsers in sequence, + /// returning the result of the first parser that succeeds. + /// + /// The type of the value produced by each parser. + /// An array of parsers that are tried in sequence. + /// + /// A that tries each parser in sequentially + /// and returns the result of the first parser that successfully parses the input. + /// + public static Parser Choice(params Parser[] parsers) + { + Argument.ThrowIfNullOrEmpty(parsers); + + var list = new List>(); + + while (true) + { + foreach (var parser in parsers) + { + switch (parser) + { + case ChoiceParser p: + list.AddRange(p.Parsers); + break; + + default: + list.Add(parser); + break; + } + } + + if (list.Count == parsers.Length) + break; + } + + if (list.Count == 1) + return list[0]; + + if (typeof(T) == typeof(char) || typeof(T) == typeof(Unit)) + { + var count = 0; + foreach (var parser in list) + if (parser is ICharClassSupport) + count++; + + if (count > 1) + { + var @class = new CharClass(CharClassUnicodeCategory.Create(0)); + for (var i = list.Count - 1; i >= 0; i--) + { + if (list[i] is ICharClassSupport s) + { + @class = @class.MergeClasses(s.GetCharClass()); + list.RemoveAt(i); + } + } + + var p = Set(@class); + list.Insert(0, + (Parser)(object)( + typeof(T) == typeof(Unit) ? p.Void() : p) + ); + } + + parsers = list.ToArray(); + } + + if (parsers.Length == 1) + return parsers[0]; + + foreach (var parser in parsers) + if (parser.Name is null) + return new ChoiceParser(parsers); + + return new DeferredDiagnosticChoiceParser(parsers); + } + + #region Inner type: ChoiceParser + + /// + /// Represents a parser that attempts to parse using each of the specified parsers in sequence, + /// returning the result of the first parser that succeeds. + /// + /// The type of the value produced by each parser. + /// An array of sub-parsers that are tried in sequence. + private sealed class ChoiceParser(Parser[] parsers) : Parser + { + /// + /// Gets the array of parsers that are tried in sequence. + /// + public Parser[] Parsers => parsers; + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var state = context.SuppressDiagnosticsIfNamed(Name); + + foreach (var parser in parsers) + { + if (parser.TryParse(ref context, out value)) + { + context.RestoreDiagnosticState(state); + return true; + } + } + + context.RestoreDiagnosticState(state); + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new ChoiceParser(parsers) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new ChoiceParser([..parsers.Select(p => p.Void())]) { Name = Name }; + } + + #endregion + + #region Inner type: DeferredDiagnosticChoiceParser + + /// + /// Represents a parser that attempts to parse the input by sequentially + /// using each of the specified sub-parsers. This variant is designed + /// for scenarios where all sub-parsers are named. + /// + /// + /// + /// The is an optimization + /// that takes effect when all sub-parsers have names. + /// + /// + /// Instead of reporting a diagnostic message every time a sub-parser fails, + /// this parser silently attempts the next one, suppressing diagnostics in the process. + /// Only if all sub-parsers ultimately fail does it produce an error message + /// listing all parser names (i.e., every expected token). As a result, + /// if any sub-parser succeeds, no diagnostics are reported. + /// + /// + /// This approach enhances performance by avoiding multiple "failed parse" errors, + /// reducing unnecessary memory allocations, and keeping the error log + /// free of clutter when parsing is successful. + /// + /// + /// The type of the value produced by each parser. + private sealed class DeferredDiagnosticChoiceParser : Parser + { + private readonly Parser[] _parsers; + private readonly string[] _expected; + + /// + /// Initializes a new instance of the class. + /// + /// An array of sub-parsers that are tried in sequence. + /// An array of expected tokens for error reporting. + private DeferredDiagnosticChoiceParser(Parser[] parsers, string[] expected) => + (_parsers, _expected) = (parsers, expected); + + /// + /// Initializes a new instance of the class. + /// + /// An array of sub-parsers that are tried in sequence. + public DeferredDiagnosticChoiceParser(Parser[] parsers) + { + _parsers = parsers; + + var expected = new string[parsers.Length]; + if (expected.Length == parsers.Length) + for (var i = 0; i < parsers.Length; i++) + expected[i] = parsers[i].Name!; + + _expected = expected; + } + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var state = context.SuppressDiagnostics(); + + foreach (var parser in _parsers) + { + if (parser.TryParse(ref context, out value)) + { + context.RestoreDiagnosticState(state); + return true; + } + } + + context.RestoreDiagnosticState(state); + + switch (Name) + { + case not null: + context.ReportExpected(Name); + break; + + default: + context.ReportExpected(_expected); + break; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DeferredDiagnosticChoiceParser(_parsers, _expected) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new DeferredDiagnosticChoiceParser([.._parsers.Select(p => p.Void())], _expected) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.DefaultOnFail.cs b/src/Ramstack.Parsing/Parser.DefaultOnFail.cs index 691d0cd..2d4be37 100644 --- a/src/Ramstack.Parsing/Parser.DefaultOnFail.cs +++ b/src/Ramstack.Parsing/Parser.DefaultOnFail.cs @@ -1,78 +1,78 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates an optional parser that always succeeds, returning the parsed value if successful, - /// or the default value of if the specified parser fails. - /// - /// The type of value produced by the initial parser. - /// The parser to be treated as optional. - /// If parsing fails, the resulting parser returns the default value of . - /// - /// A parser that always succeeds, either by producing a value from the original parser - /// or by returning the default value of . - /// - public static Parser DefaultOnFail(this Parser parser) => - parser.DefaultOnFail(default!)!; - - /// - /// Creates an optional parser that always succeeds, returning the parsed value if successful, - /// or a default value if the specified parser fails. - /// - /// The type of value produced by the initial parser. - /// The parser to be treated as optional. - /// If parsing fails, the resulting parser returns . - /// The default value to return if the specified parser fails to parse. - /// - /// A parser that always succeeds, either by producing a value from the initial parser - /// or by returning the specified default value. - /// - public static Parser DefaultOnFail(this Parser parser, T defaultValue) - { - if (parser is DefaultOnFailParser p) - parser = p.Parser; - - return new DefaultOnFailParser(parser, defaultValue); - } - - #region Inner type: DefaultOnFailParser - - /// - /// Represents a parser that always succeeds, returning the parsed value if successful, - /// or a default value if the specified parser fails. - /// - /// The type of value produced by the initial parser. - /// The parser to be treated as optional. - /// If parsing fails, the resulting parser returns . - /// The default value to return if the initial parser fails to parse. - private sealed class DefaultOnFailParser(Parser parser, T defaultValue) : Parser - { - /// - /// Gets the initial parser. - /// - public Parser Parser => parser; - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - if (!parser.TryParse(ref context, out value)) - { - value = defaultValue!; - context.Advance(0); - } - - return true; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DefaultOnFailParser(parser.ToNamedParser(name), defaultValue) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new DefaultOnFailParser(parser.Void(), Unit.Value) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates an optional parser that always succeeds, returning the parsed value if successful, + /// or the default value of if the specified parser fails. + /// + /// The type of value produced by the initial parser. + /// The parser to be treated as optional. + /// If parsing fails, the resulting parser returns the default value of . + /// + /// A parser that always succeeds, either by producing a value from the original parser + /// or by returning the default value of . + /// + public static Parser DefaultOnFail(this Parser parser) => + parser.DefaultOnFail(default!)!; + + /// + /// Creates an optional parser that always succeeds, returning the parsed value if successful, + /// or a default value if the specified parser fails. + /// + /// The type of value produced by the initial parser. + /// The parser to be treated as optional. + /// If parsing fails, the resulting parser returns . + /// The default value to return if the specified parser fails to parse. + /// + /// A parser that always succeeds, either by producing a value from the initial parser + /// or by returning the specified default value. + /// + public static Parser DefaultOnFail(this Parser parser, T defaultValue) + { + if (parser is DefaultOnFailParser p) + parser = p.Parser; + + return new DefaultOnFailParser(parser, defaultValue); + } + + #region Inner type: DefaultOnFailParser + + /// + /// Represents a parser that always succeeds, returning the parsed value if successful, + /// or a default value if the specified parser fails. + /// + /// The type of value produced by the initial parser. + /// The parser to be treated as optional. + /// If parsing fails, the resulting parser returns . + /// The default value to return if the initial parser fails to parse. + private sealed class DefaultOnFailParser(Parser parser, T defaultValue) : Parser + { + /// + /// Gets the initial parser. + /// + public Parser Parser => parser; + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + if (!parser.TryParse(ref context, out value)) + { + value = defaultValue!; + context.Advance(0); + } + + return true; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DefaultOnFailParser(parser.ToNamedParser(name), defaultValue) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new DefaultOnFailParser(parser.Void(), Unit.Value) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Deferred.cs b/src/Ramstack.Parsing/Parser.Deferred.cs index c58c831..adbebfc 100644 --- a/src/Ramstack.Parsing/Parser.Deferred.cs +++ b/src/Ramstack.Parsing/Parser.Deferred.cs @@ -1,25 +1,25 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that can be defined later. - /// - /// The type of the value produced by the parser. - /// - /// A parser that can be defined later. - /// - public static DeferredParser Deferred() => - new DeferredParser(); - - /// - /// Creates a parser that references itself to support recursive definitions. - /// - /// The type of the value produced by the parser. - /// A function that accepts a reference to the deferred parser and returns the resulting parser. - /// - /// A parser that can be recursively defined. - /// - public static Parser Recursive(Func, Parser> parser) => - new DeferredParser(parser); -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that can be defined later. + /// + /// The type of the value produced by the parser. + /// + /// A parser that can be defined later. + /// + public static DeferredParser Deferred() => + new DeferredParser(); + + /// + /// Creates a parser that references itself to support recursive definitions. + /// + /// The type of the value produced by the parser. + /// A function that accepts a reference to the deferred parser and returns the resulting parser. + /// + /// A parser that can be recursively defined. + /// + public static Parser Recursive(Func, Parser> parser) => + new DeferredParser(parser); +} diff --git a/src/Ramstack.Parsing/Parser.Do.Generated.cs b/src/Ramstack.Parsing/Parser.Do.Generated.cs index fb4ccc3..453cf87 100644 --- a/src/Ramstack.Parsing/Parser.Do.Generated.cs +++ b/src/Ramstack.Parsing/Parser.Do.Generated.cs @@ -1,411 +1,411 @@ -//----------------------------------------------------------------------------// -// // -// This code was generated by a tool. // -// // -// Changes to this file may cause incorrect behavior // -// and will be lost if the code is regenerated. // -// // -//----------------------------------------------------------------------------// - -#nullable enable - -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> parser, Func func) => - new DoParser(parser, func); - - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> parser, Func func) => - new DoParser(parser, func); - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8, v.Item9)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8, v.Item9, v.Item10)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion -} +//----------------------------------------------------------------------------// +// // +// This code was generated by a tool. // +// // +// Changes to this file may cause incorrect behavior // +// and will be lost if the code is regenerated. // +// // +//----------------------------------------------------------------------------// + +#nullable enable + +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> parser, Func func) => + new DoParser(parser, func); + + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> parser, Func func) => + new DoParser(parser, func); + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8, v.Item9)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v.Item1, v.Item2, v.Item3, v.Item4, v.Item5, v.Item6, v.Item7, v.Item8, v.Item9, v.Item10)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Do.cs b/src/Ramstack.Parsing/Parser.Do.cs index 997ecec..ac17bbe 100644 --- a/src/Ramstack.Parsing/Parser.Do.cs +++ b/src/Ramstack.Parsing/Parser.Do.cs @@ -1,52 +1,52 @@ -namespace Ramstack.Parsing; - -public static partial class Parser -{ - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The type of the value produced by the initial parser. - /// The type of the value produced after applying the transformation function. - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do(this Parser parser, Func func) => - new DoParser(parser, func); - - #region Inner type: DoParser - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The type of the value produced by the parser. - /// The type of the value produced after applying the transformation function. - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser(Parser parser, Func func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(v)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion -} +namespace Ramstack.Parsing; + +public static partial class Parser +{ + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The type of the value produced by the initial parser. + /// The type of the value produced after applying the transformation function. + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do(this Parser parser, Func func) => + new DoParser(parser, func); + + #region Inner type: DoParser + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The type of the value produced by the parser. + /// The type of the value produced after applying the transformation function. + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser(Parser parser, Func func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(v)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Do.tt b/src/Ramstack.Parsing/Parser.Do.tt index ff0b647..f2669be 100644 --- a/src/Ramstack.Parsing/Parser.Do.tt +++ b/src/Ramstack.Parsing/Parser.Do.tt @@ -1,70 +1,70 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ include file="Properties\Autogenerated.ttinclude" #> -#nullable enable - -namespace Ramstack.Parsing; - -partial class Parser -{ -<# - for (var n = 2; n <= 10; n++) - { - WriteSeparatorLine(n != 2); - GenerateMethod(n); - } - - for (var n = 2; n <= 10; n++) - { - WriteLine(); - GenerateClass(n); - } -#> -} -<#+ private void GenerateMethod(int arity) { #> - /// - /// Creates a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - /// - /// A parser that applies a transformation function to the value produced by the specified parser. - /// - public static Parser Do<<#=GenerateGenericParameters(arity)#>, TResult>(this Parser<(<#=GenerateTupleParameters(arity)#>)> parser, Func<<#=GenerateGenericParameters(arity)#>, TResult> func) => - new DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(parser, func); -<#+ } #> -<#+ private void GenerateClass(int arity) { #> - #region Inner type: DoParser<<#=GenerateGenericParameters(arity)#>, TResult> - - /// - /// Represents a parser that applies a transformation function to the value produced by the specified parser. - /// - /// The parser whose output will be transformed. - /// The function used to transform the parser's output. - private sealed class DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(Parser<(<#=GenerateGenericParameters(arity)#>)> parser, Func<<#=GenerateGenericParameters(arity)#>, TResult> func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var v)) - { - value = func(<#=GenerateList(arity, n => $"v.Item{n}", from: 1)#>)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion -<#+ } #> -<#+ private string GenerateGenericParameters(int arity) => GenerateList(arity, n => $"T{n}", from: 1); #> -<#+ private string GenerateTupleParameters(int arity) => GenerateList(arity, n => $"T{n}", from: 1); #> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ include file="Properties\Autogenerated.ttinclude" #> +#nullable enable + +namespace Ramstack.Parsing; + +partial class Parser +{ +<# + for (var n = 2; n <= 10; n++) + { + WriteSeparatorLine(n != 2); + GenerateMethod(n); + } + + for (var n = 2; n <= 10; n++) + { + WriteLine(); + GenerateClass(n); + } +#> +} +<#+ private void GenerateMethod(int arity) { #> + /// + /// Creates a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + /// + /// A parser that applies a transformation function to the value produced by the specified parser. + /// + public static Parser Do<<#=GenerateGenericParameters(arity)#>, TResult>(this Parser<(<#=GenerateTupleParameters(arity)#>)> parser, Func<<#=GenerateGenericParameters(arity)#>, TResult> func) => + new DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(parser, func); +<#+ } #> +<#+ private void GenerateClass(int arity) { #> + #region Inner type: DoParser<<#=GenerateGenericParameters(arity)#>, TResult> + + /// + /// Represents a parser that applies a transformation function to the value produced by the specified parser. + /// + /// The parser whose output will be transformed. + /// The function used to transform the parser's output. + private sealed class DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(Parser<(<#=GenerateGenericParameters(arity)#>)> parser, Func<<#=GenerateGenericParameters(arity)#>, TResult> func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var v)) + { + value = func(<#=GenerateList(arity, n => $"v.Item{n}", from: 1)#>)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new DoParser<<#=GenerateGenericParameters(arity)#>, TResult>(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion +<#+ } #> +<#+ private string GenerateGenericParameters(int arity) => GenerateList(arity, n => $"T{n}", from: 1); #> +<#+ private string GenerateTupleParameters(int arity) => GenerateList(arity, n => $"T{n}", from: 1); #> diff --git a/src/Ramstack.Parsing/Parser.Eof.cs b/src/Ramstack.Parsing/Parser.Eof.cs index 40e03a7..6f6c9df 100644 --- a/src/Ramstack.Parsing/Parser.Eof.cs +++ b/src/Ramstack.Parsing/Parser.Eof.cs @@ -1,38 +1,38 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Gets a parser that matches the end of input. - /// - public static Parser Eof { get; } = new EofParser(); - - #region Inner type: EofParser - - /// - /// Represents a parser that matches the end of input. - /// - private sealed class EofParser() : Parser("end of input") - { - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - Unsafe.SkipInit(out value); - - if (context.Position >= context.Source.Length) - { - context.Advance(0); - return true; - } - - context.ReportExpected(Name); - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new EofParser { Name = name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Gets a parser that matches the end of input. + /// + public static Parser Eof { get; } = new EofParser(); + + #region Inner type: EofParser + + /// + /// Represents a parser that matches the end of input. + /// + private sealed class EofParser() : Parser("end of input") + { + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + Unsafe.SkipInit(out value); + + if (context.Position >= context.Source.Length) + { + context.Advance(0); + return true; + } + + context.ReportExpected(Name); + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new EofParser { Name = name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Eol.cs b/src/Ramstack.Parsing/Parser.Eol.cs index ae0934d..de4540a 100644 --- a/src/Ramstack.Parsing/Parser.Eol.cs +++ b/src/Ramstack.Parsing/Parser.Eol.cs @@ -1,58 +1,58 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Gets a parser that matches and consumes a line break, or succeeds at the end of input. - /// - public static Parser Eol { get; } = new EolParser(); - - #region Inner type: EolParser - - /// - /// Represents a parser that matches and consumes a line break, or succeeds at the end of input. - /// - private sealed class EolParser() : Parser("end of line") - { - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - Unsafe.SkipInit(out value); - - var s = context.Source; - var p = context.Position; - var count = 0; - - if ((uint)p < (uint)s.Length) - { - switch (s[p]) - { - case '\r' when (uint)p + 1 < (uint)s.Length && s[p + 1] == '\n': - count = 2; - break; - - case '\n': - case '\u0085': - case '\u2028': - case '\u2029': - case '\r': - count = 1; - break; - - default: - context.ReportExpected(Name); - return false; - } - } - - context.Advance(count); - return true; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new EolParser { Name = name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Gets a parser that matches and consumes a line break, or succeeds at the end of input. + /// + public static Parser Eol { get; } = new EolParser(); + + #region Inner type: EolParser + + /// + /// Represents a parser that matches and consumes a line break, or succeeds at the end of input. + /// + private sealed class EolParser() : Parser("end of line") + { + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + Unsafe.SkipInit(out value); + + var s = context.Source; + var p = context.Position; + var count = 0; + + if ((uint)p < (uint)s.Length) + { + switch (s[p]) + { + case '\r' when (uint)p + 1 < (uint)s.Length && s[p + 1] == '\n': + count = 2; + break; + + case '\n': + case '\u0085': + case '\u2028': + case '\u2029': + case '\r': + count = 1; + break; + + default: + context.ReportExpected(Name); + return false; + } + } + + context.Advance(count); + return true; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new EolParser { Name = name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Error.cs b/src/Ramstack.Parsing/Parser.Error.cs index 078f8f6..bd2b6fa 100644 --- a/src/Ramstack.Parsing/Parser.Error.cs +++ b/src/Ramstack.Parsing/Parser.Error.cs @@ -1,42 +1,42 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that always fails with a report of a missing expected sequence or rule. - /// - /// The type of the value produced by the parser. - /// The expected sequence or rule. - /// - /// A parser that always fails with a report of a missing expected sequence or rule. - /// - public static Parser Error(string expected) => - new ErrorParser { Name = expected }; - - #region Inner type: ErrorParser - - /// - /// Represents a parser that always fails with a report of a missing expected sequence or rule. - /// - /// The type of the value produced by the parser. - private sealed class ErrorParser : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - value = default; - context.ReportExpected(Name); - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new ErrorParser { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new ErrorParser { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that always fails with a report of a missing expected sequence or rule. + /// + /// The type of the value produced by the parser. + /// The expected sequence or rule. + /// + /// A parser that always fails with a report of a missing expected sequence or rule. + /// + public static Parser Error(string expected) => + new ErrorParser { Name = expected }; + + #region Inner type: ErrorParser + + /// + /// Represents a parser that always fails with a report of a missing expected sequence or rule. + /// + /// The type of the value produced by the parser. + private sealed class ErrorParser : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + value = default; + context.ReportExpected(Name); + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new ErrorParser { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new ErrorParser { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Fail.cs b/src/Ramstack.Parsing/Parser.Fail.cs index 3eeef46..2a6524e 100644 --- a/src/Ramstack.Parsing/Parser.Fail.cs +++ b/src/Ramstack.Parsing/Parser.Fail.cs @@ -1,47 +1,47 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that forcibly terminates parsing with the specified error message. - /// - /// The type of value produced by the parser. - /// The error message to be thrown. - /// - /// A parser that forcibly terminates parsing with the specified error message. - /// - public static Parser Fail(string message) - { - Argument.ThrowIfNullOrEmpty(message); - return new FailParser(message); - } - - /// - /// Forcibly terminates parsing with the specified error message. - /// - /// The error message to be thrown. - [DoesNotReturn] - public static void FatalError(string message) => - throw new FatalErrorException(message); - - #region Inner type: FailParser - - /// - /// Represents a parser that always throws a - /// with the specified error message when invoked. - /// - /// The type of value produced by the parser. - /// The error message to be thrown. - private sealed class FailParser(string message) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) => - throw new FatalErrorException(message); - - /// - protected internal override Parser ToVoidParser() => - new FailParser(message); - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that forcibly terminates parsing with the specified error message. + /// + /// The type of value produced by the parser. + /// The error message to be thrown. + /// + /// A parser that forcibly terminates parsing with the specified error message. + /// + public static Parser Fail(string message) + { + Argument.ThrowIfNullOrEmpty(message); + return new FailParser(message); + } + + /// + /// Forcibly terminates parsing with the specified error message. + /// + /// The error message to be thrown. + [DoesNotReturn] + public static void FatalError(string message) => + throw new FatalErrorException(message); + + #region Inner type: FailParser + + /// + /// Represents a parser that always throws a + /// with the specified error message when invoked. + /// + /// The type of value produced by the parser. + /// The error message to be thrown. + private sealed class FailParser(string message) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) => + throw new FatalErrorException(message); + + /// + protected internal override Parser ToVoidParser() => + new FailParser(message); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Fold.cs b/src/Ramstack.Parsing/Parser.Fold.cs index a8b47ff..da8c7c1 100644 --- a/src/Ramstack.Parsing/Parser.Fold.cs +++ b/src/Ramstack.Parsing/Parser.Fold.cs @@ -1,170 +1,170 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a left-associative parser. - /// - /// - /// - /// // Example: Number ([+-] Number)* - /// // 1 + 2 + 3 + 4 => (((1 + 2) + 3) + 4) - /// var sum = number.Fold(OneOf("+-"), (l, r, op) => op == '+' ? l + r : l - r); - /// - /// If an operator-operand pair consumes no input, it is discarded and parsing stops - /// without calling the reduction function for that pair. - /// - /// The type of the value produced by the main parser. - /// The type of the operator token produced by the parser. - /// The main parser that matches a value. - /// The parser that matches an operator token. - /// A reduction function. - /// - /// A parser that performs left-associative folding. - /// - public static Parser Fold(this Parser parser, Parser op, Func reduce) => - new FoldParser(parser, op, reduce); - - /// - /// Creates a right-associative parser. - /// - /// - /// - /// // 2 ^ 3 ^ 4 ^ 1 => (2 ^ (3 ^ (4 ^ 1))) - /// // a = b = c = d => (a = (b = (c = d))) - /// - /// // Number ("^" Number)* - /// var power = number.FoldR(L('^'), (l, r, op) => Math.Pow(l, r)); - /// - /// If an operator-operand pair consumes no input, it is discarded and parsing stops - /// without calling the reduction function for that pair. - /// - /// The type of value produced by the main parser. - /// The type of the operator token produced by the parser. - /// The main parser that matches a value. - /// The operator parser that matches an operator token. - /// A reduction function. - /// - /// A parser that performs right-associative folding. - /// - public static Parser FoldR(this Parser parser, Parser op, Func reduce) => - new FoldRParser(parser, op, reduce); - - #region Inner type: FoldParser - - /// - /// Represents a left-associative parser. - /// - /// The type of value produced by the main parser. - /// The type of the operator token produced by the parser. - /// The main parser that matches a value. - /// The operator parser that matches an operator token. - /// A reduction function. - private sealed class FoldParser(Parser parser, Parser op, Func reduce) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var bookmark = context.BookmarkPosition(); - - if (parser.TryParse(ref context, out var v)) - { - var result = v; - - while (true) - { - var rollback = context.BookmarkPosition(); - - if (op.TryParse(ref context, out var o) - && parser.TryParse(ref context, out v) - && context.Position != rollback.Position) - { - result = reduce(result, v, o); - continue; - } - - context.RestorePosition(rollback); - break; - } - - context.SetMatched(bookmark); - value = result!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() - { - var p = parser.Void(); - return Seq(p, Seq(op.Void(), p).ZeroOrMore()); - } - } - - #endregion - - #region Inner type: FoldRParser - - /// - /// Represents a right-associative parser. - /// - /// The type of value produced by the main parser. - /// The type of the operator token produced by the parser. - /// The main parser that matches a value. - /// The operator parser that matches an operator token. - /// A reduction function. - private sealed class FoldRParser(Parser parser, Parser op, Func reduce) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var bookmark = context.BookmarkPosition(); - - if (parser.TryParse(ref context, out var v)) - { - var list = new ArrayBuilder<(TOperator op, T value)>(); - list.Add((default!, v)); - - while (true) - { - var rollback = context.BookmarkPosition(); - - if (op.TryParse(ref context, out var o) - && parser.TryParse(ref context, out v) - && context.Position != rollback.Position) - { - list.Add((o, v)); - continue; - } - - context.RestorePosition(rollback); - break; - } - - context.SetMatched(bookmark); - - var result = list[^1].value; - for (var i = list.Count - 1; i > 0; i--) - result = reduce(list[i - 1].value, result, list[i].op); - - value = result!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() - { - var p = parser.Void(); - return Seq(p, Seq(op.Void(), p).ZeroOrMore()); - } - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a left-associative parser. + /// + /// + /// + /// // Example: Number ([+-] Number)* + /// // 1 + 2 + 3 + 4 => (((1 + 2) + 3) + 4) + /// var sum = number.Fold(OneOf("+-"), (l, r, op) => op == '+' ? l + r : l - r); + /// + /// If an operator-operand pair consumes no input, it is discarded and parsing stops + /// without calling the reduction function for that pair. + /// + /// The type of the value produced by the main parser. + /// The type of the operator token produced by the parser. + /// The main parser that matches a value. + /// The parser that matches an operator token. + /// A reduction function. + /// + /// A parser that performs left-associative folding. + /// + public static Parser Fold(this Parser parser, Parser op, Func reduce) => + new FoldParser(parser, op, reduce); + + /// + /// Creates a right-associative parser. + /// + /// + /// + /// // 2 ^ 3 ^ 4 ^ 1 => (2 ^ (3 ^ (4 ^ 1))) + /// // a = b = c = d => (a = (b = (c = d))) + /// + /// // Number ("^" Number)* + /// var power = number.FoldR(L('^'), (l, r, op) => Math.Pow(l, r)); + /// + /// If an operator-operand pair consumes no input, it is discarded and parsing stops + /// without calling the reduction function for that pair. + /// + /// The type of value produced by the main parser. + /// The type of the operator token produced by the parser. + /// The main parser that matches a value. + /// The operator parser that matches an operator token. + /// A reduction function. + /// + /// A parser that performs right-associative folding. + /// + public static Parser FoldR(this Parser parser, Parser op, Func reduce) => + new FoldRParser(parser, op, reduce); + + #region Inner type: FoldParser + + /// + /// Represents a left-associative parser. + /// + /// The type of value produced by the main parser. + /// The type of the operator token produced by the parser. + /// The main parser that matches a value. + /// The operator parser that matches an operator token. + /// A reduction function. + private sealed class FoldParser(Parser parser, Parser op, Func reduce) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var bookmark = context.BookmarkPosition(); + + if (parser.TryParse(ref context, out var v)) + { + var result = v; + + while (true) + { + var rollback = context.BookmarkPosition(); + + if (op.TryParse(ref context, out var o) + && parser.TryParse(ref context, out v) + && context.Position != rollback.Position) + { + result = reduce(result, v, o); + continue; + } + + context.RestorePosition(rollback); + break; + } + + context.SetMatched(bookmark); + value = result!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() + { + var p = parser.Void(); + return Seq(p, Seq(op.Void(), p).ZeroOrMore()); + } + } + + #endregion + + #region Inner type: FoldRParser + + /// + /// Represents a right-associative parser. + /// + /// The type of value produced by the main parser. + /// The type of the operator token produced by the parser. + /// The main parser that matches a value. + /// The operator parser that matches an operator token. + /// A reduction function. + private sealed class FoldRParser(Parser parser, Parser op, Func reduce) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var bookmark = context.BookmarkPosition(); + + if (parser.TryParse(ref context, out var v)) + { + var list = new ArrayBuilder<(TOperator op, T value)>(); + list.Add((default!, v)); + + while (true) + { + var rollback = context.BookmarkPosition(); + + if (op.TryParse(ref context, out var o) + && parser.TryParse(ref context, out v) + && context.Position != rollback.Position) + { + list.Add((o, v)); + continue; + } + + context.RestorePosition(rollback); + break; + } + + context.SetMatched(bookmark); + + var result = list[^1].value; + for (var i = list.Count - 1; i > 0; i--) + result = reduce(list[i - 1].value, result, list[i].op); + + value = result!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() + { + var p = parser.Void(); + return Seq(p, Seq(op.Void(), p).ZeroOrMore()); + } + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Map.cs b/src/Ramstack.Parsing/Parser.Map.cs index 70dfdec..2de9f4d 100644 --- a/src/Ramstack.Parsing/Parser.Map.cs +++ b/src/Ramstack.Parsing/Parser.Map.cs @@ -1,101 +1,101 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that applies the specified map function to the matched segment (ignoring the parsed value). - /// - /// The type of value produced by the initial parser. - /// The type of the value produced by applying the map function. - /// The used to match the input. - /// The function that maps the matched segment to a . - /// - /// A parser that produces by mapping the matched segment through . - /// - public static Parser Map(this Parser parser, MapFunc func) => - new MapParser(parser.Void(), func) { Name = parser.Name }; - - /// - /// Creates a parser that applies the specified map function to both the matched segment and the parsed value. - /// - /// The type of value produced by the initial parser. - /// The type of the value produced by applying the map function. - /// The used to match and parse the input. - /// The function that maps the matched segment and the parsed value to a . - /// - /// A parser that produces by mapping the matched segment - /// and the parsed value through . - /// - public static Parser Map(this Parser parser, MapFunc func) => - new MapParser(parser, func) { Name = parser.Name }; - - #region Inner type: MapParser - - /// - /// Represents a parser that applies a to the matched segment, - /// ignoring the original parsed value. - /// - /// The type of the value produced by applying the map function. - /// A parser that matches the input but discards its value. - /// A function transforming the matched segment into . - private sealed class MapParser(Parser parser, MapFunc func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - if (parser.TryParse(ref context, out _)) - { - value = func(context.MatchedSegment)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new MapParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion - - #region Inner type: MapParser - - /// - /// Represents a parser that applies a to both the matched segment and the parsed value. - /// - /// The type of value produced by the initial parser. - /// The type of the value produced by applying the map function. - /// A parser that matches and parses the input to a value of type . - /// A function transforming the matched segment and the parsed value into . - private sealed class MapParser(Parser parser, MapFunc func) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) - { - if (parser.TryParse(ref context, out var parsedValue)) - { - value = func(context.MatchedSegment, parsedValue)!; - return true; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new MapParser(parser.ToNamedParser(name), func) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - parser.Void(); - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that applies the specified map function to the matched segment (ignoring the parsed value). + /// + /// The type of value produced by the initial parser. + /// The type of the value produced by applying the map function. + /// The used to match the input. + /// The function that maps the matched segment to a . + /// + /// A parser that produces by mapping the matched segment through . + /// + public static Parser Map(this Parser parser, MapFunc func) => + new MapParser(parser.Void(), func) { Name = parser.Name }; + + /// + /// Creates a parser that applies the specified map function to both the matched segment and the parsed value. + /// + /// The type of value produced by the initial parser. + /// The type of the value produced by applying the map function. + /// The used to match and parse the input. + /// The function that maps the matched segment and the parsed value to a . + /// + /// A parser that produces by mapping the matched segment + /// and the parsed value through . + /// + public static Parser Map(this Parser parser, MapFunc func) => + new MapParser(parser, func) { Name = parser.Name }; + + #region Inner type: MapParser + + /// + /// Represents a parser that applies a to the matched segment, + /// ignoring the original parsed value. + /// + /// The type of the value produced by applying the map function. + /// A parser that matches the input but discards its value. + /// A function transforming the matched segment into . + private sealed class MapParser(Parser parser, MapFunc func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + if (parser.TryParse(ref context, out _)) + { + value = func(context.MatchedSegment)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new MapParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion + + #region Inner type: MapParser + + /// + /// Represents a parser that applies a to both the matched segment and the parsed value. + /// + /// The type of value produced by the initial parser. + /// The type of the value produced by applying the map function. + /// A parser that matches and parses the input to a value of type . + /// A function transforming the matched segment and the parsed value into . + private sealed class MapParser(Parser parser, MapFunc func) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out TResult? value) + { + if (parser.TryParse(ref context, out var parsedValue)) + { + value = func(context.MatchedSegment, parsedValue)!; + return true; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new MapParser(parser.ToNamedParser(name), func) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + parser.Void(); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Not.cs b/src/Ramstack.Parsing/Parser.Not.cs index 734ffeb..effab2e 100644 --- a/src/Ramstack.Parsing/Parser.Not.cs +++ b/src/Ramstack.Parsing/Parser.Not.cs @@ -1,17 +1,17 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a predicate parser that succeeds only if the specified parser fails. - /// - /// - /// The predicate parser does not advance the parsing position. - /// - /// The parser that is expected to fail. - /// - /// A predicate parser that succeeds if the specified parser fails. - /// - public static Parser Not(Parser parser) => - new AssertParser(parser.Void()); -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a predicate parser that succeeds only if the specified parser fails. + /// + /// + /// The predicate parser does not advance the parsing position. + /// + /// The parser that is expected to fail. + /// + /// A predicate parser that succeeds if the specified parser fails. + /// + public static Parser Not(Parser parser) => + new AssertParser(parser.Void()); +} diff --git a/src/Ramstack.Parsing/Parser.Optional.cs b/src/Ramstack.Parsing/Parser.Optional.cs index cfc7ea4..66b0409 100644 --- a/src/Ramstack.Parsing/Parser.Optional.cs +++ b/src/Ramstack.Parsing/Parser.Optional.cs @@ -1,65 +1,65 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates an optional parser that always succeeds, - /// regardless of whether the specified parser succeeds or fails. - /// - /// The type of value produced by the initial parser. - /// The parser to be treated as optional. - /// - /// A parser representing an optional version of the specified parser. - /// - public static Parser> Optional(this Parser parser) => - new OptionalParser(parser); - - /// - /// Returns the input parser as-is when it is already an optional parser. - /// - /// The type of value produced by the initial parser. - /// The optional parser that will be returned unchanged. - /// - /// The same parser that was passed in as an argument. - /// - public static Parser> Optional(this Parser> parser) => - parser; - - #region Inner type: OptionalParser - - /// - /// Represents a parser that always succeeds, regardless of whether the specified parser succeeds or fails. - /// - /// The type of value produced by the initial parser. - /// The parser to be treated as optional. - private sealed class OptionalParser(Parser parser) : Parser> - { - /// - public override bool TryParse(ref ParseContext context, out OptionalValue value) - { - value = default; - - if (parser.TryParse(ref context, out var result)) - { - Unsafe.AsRef(in value.Value) = result; - Unsafe.AsRef(in value.HasValue) = true; - } - else - { - context.Advance(0); - } - - return true; - } - - /// - protected internal override Parser> ToNamedParser(string? name) => - new OptionalParser(parser.ToNamedParser(name)) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new DefaultOnFailParser(parser.Void(), Unit.Value) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates an optional parser that always succeeds, + /// regardless of whether the specified parser succeeds or fails. + /// + /// The type of value produced by the initial parser. + /// The parser to be treated as optional. + /// + /// A parser representing an optional version of the specified parser. + /// + public static Parser> Optional(this Parser parser) => + new OptionalParser(parser); + + /// + /// Returns the input parser as-is when it is already an optional parser. + /// + /// The type of value produced by the initial parser. + /// The optional parser that will be returned unchanged. + /// + /// The same parser that was passed in as an argument. + /// + public static Parser> Optional(this Parser> parser) => + parser; + + #region Inner type: OptionalParser + + /// + /// Represents a parser that always succeeds, regardless of whether the specified parser succeeds or fails. + /// + /// The type of value produced by the initial parser. + /// The parser to be treated as optional. + private sealed class OptionalParser(Parser parser) : Parser> + { + /// + public override bool TryParse(ref ParseContext context, out OptionalValue value) + { + value = default; + + if (parser.TryParse(ref context, out var result)) + { + Unsafe.AsRef(in value.Value) = result; + Unsafe.AsRef(in value.HasValue) = true; + } + else + { + context.Advance(0); + } + + return true; + } + + /// + protected internal override Parser> ToNamedParser(string? name) => + new OptionalParser(parser.ToNamedParser(name)) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new DefaultOnFailParser(parser.Void(), Unit.Value) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Or.cs b/src/Ramstack.Parsing/Parser.Or.cs index 1b961a4..eafc6b9 100644 --- a/src/Ramstack.Parsing/Parser.Or.cs +++ b/src/Ramstack.Parsing/Parser.Or.cs @@ -1,18 +1,18 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that attempts to parse the input with the specified parser - /// or its alternative in sequence, returning the result of the first one that succeeds. - /// - /// The type of the value produced by each parser. - /// The main parser. - /// The alternative parser. - /// - /// A that tries each parser sequentially and returns the result - /// of the first parser that successfully parses the input. - /// - public static Parser Or(this Parser parser, Parser alternative) => - Choice(parser, alternative); -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that attempts to parse the input with the specified parser + /// or its alternative in sequence, returning the result of the first one that succeeds. + /// + /// The type of the value produced by each parser. + /// The main parser. + /// The alternative parser. + /// + /// A that tries each parser sequentially and returns the result + /// of the first parser that successfully parses the input. + /// + public static Parser Or(this Parser parser, Parser alternative) => + Choice(parser, alternative); +} diff --git a/src/Ramstack.Parsing/Parser.Regex.cs b/src/Ramstack.Parsing/Parser.Regex.cs index 5c72c2a..5c30bd5 100644 --- a/src/Ramstack.Parsing/Parser.Regex.cs +++ b/src/Ramstack.Parsing/Parser.Regex.cs @@ -1,80 +1,80 @@ -#if NET7_0_OR_GREATER - -using System.Text.RegularExpressions; - -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that matches the specified regular expression pattern. - /// - /// - /// The regular expression pattern must start with the "^" anchor character. - /// - /// The regular expression. - /// - /// A parser that parses the specified regular expression pattern. - /// - public static Parser Regex(Regex regex) - { - if (!regex.ToString().StartsWith('^')) - throw new ArgumentException("The pattern must start with the '^' character."); - - return new RegexParser(regex); - } - - /// - /// Represents a parser that matches the specified regular expression pattern. - /// - private sealed class RegexParser : Parser - { - /// - /// Gets the regular expression. - /// - public Regex Expression { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The regular expression. - public RegexParser(Regex regex) => - (Expression, Name) = (regex, regex.ToString()); - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var source = context.Remaining; - if (source.Length != 0) - { - var e = Expression.EnumerateMatches(source); - if (e.MoveNext() && e.Current.Index == 0) - { - context.Advance(e.Current.Length); - - if (typeof(T) != typeof(Unit)) - value = ((T)(object)context.MatchedSegment.ToString())!; - else - value = default!; - - return true; - } - } - - value = default; - context.ReportExpected(Name); - - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new RegexParser(Expression) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new RegexParser(Expression) { Name = Name }; - } -} - -#endif +#if NET7_0_OR_GREATER + +using System.Text.RegularExpressions; + +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that matches the specified regular expression pattern. + /// + /// + /// The regular expression pattern must start with the "^" anchor character. + /// + /// The regular expression. + /// + /// A parser that parses the specified regular expression pattern. + /// + public static Parser Regex(Regex regex) + { + if (!regex.ToString().StartsWith('^')) + throw new ArgumentException("The pattern must start with the '^' character."); + + return new RegexParser(regex); + } + + /// + /// Represents a parser that matches the specified regular expression pattern. + /// + private sealed class RegexParser : Parser + { + /// + /// Gets the regular expression. + /// + public Regex Expression { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The regular expression. + public RegexParser(Regex regex) => + (Expression, Name) = (regex, regex.ToString()); + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var source = context.Remaining; + if (source.Length != 0) + { + var e = Expression.EnumerateMatches(source); + if (e.MoveNext() && e.Current.Index == 0) + { + context.Advance(e.Current.Length); + + if (typeof(T) != typeof(Unit)) + value = ((T)(object)context.MatchedSegment.ToString())!; + else + value = default!; + + return true; + } + } + + value = default; + context.ReportExpected(Name); + + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new RegexParser(Expression) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new RegexParser(Expression) { Name = Name }; + } +} + +#endif diff --git a/src/Ramstack.Parsing/Parser.Repeat.cs b/src/Ramstack.Parsing/Parser.Repeat.cs index 220c471..df64bcb 100644 --- a/src/Ramstack.Parsing/Parser.Repeat.cs +++ b/src/Ramstack.Parsing/Parser.Repeat.cs @@ -1,602 +1,602 @@ -using System.Numerics; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that applies the specified parser zero or more times. - /// - /// The type of the value produced by the specified parser. - /// The to apply zero or more times. - /// - /// A parser that applies the specified parser zero or more times. - /// - public static Parser> ZeroOrMore(this Parser parser) => - Repeat(parser, 0, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser zero or more times. - /// - /// The to apply zero or more times. - /// - /// A parser that applies the specified parser zero or more times. - /// - public static Parser ZeroOrMore(this Parser parser) => - Repeat(parser, 0, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser at least once. - /// - /// The type of the value produced by the specified parser. - /// The to apply at least once. - /// - /// A parser that applies the specified parser at least once. - /// - public static Parser> OneOrMore(this Parser parser) => - Repeat(parser, 1, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser at least once. - /// - /// The to apply at least once. - /// - /// A parser that applies the specified parser at least once. - /// - public static Parser OneOrMore(this Parser parser) => - Repeat(parser, 1, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser zero or more times. - /// - /// The type of the value produced by the specified parser. - /// The to apply zero or more times. - /// - /// A parser that applies the specified parser zero or more times. - /// - public static Parser> Many(this Parser parser) => - Repeat(parser, 0, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser zero or more times. - /// - /// The to apply zero or more times. - /// - /// A parser that applies the specified parser zero or more times. - /// - public static Parser Many(this Parser parser) => - Repeat(parser, 0, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser at least the defined number of times. - /// - /// The type of the value produced by the specified parser. - /// The to apply at least the specified number of times. - /// The minimum number of repetitions. - /// - /// A parser that applies the specified parser at least a defined number of times. - /// - public static Parser> AtLeast(this Parser parser, int count) => - Repeat(parser, count, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser at least the specified number of times. - /// - /// The to apply at least the specified number of times. - /// The minimum number of repetitions. - /// - /// A parser that applies the specified parser at least a defined number of times. - /// - public static Parser AtLeast(this Parser parser, int count) => - Repeat(parser, count, int.MaxValue); - - /// - /// Creates a parser that applies the specified parser a defined number of times. - /// - /// The type of the value produced by the specified parser. - /// The to apply the specified number of times. - /// The number of repetitions. - /// - /// A parser that applies the specified parser a defined number of times. - /// - public static Parser> Repeat(this Parser parser, int count) => - Repeat(parser, count, count); - - /// - /// Creates a parser that applies the specified parser a defined number of times. - /// - /// The to apply the specified number of times. - /// The number of repetitions. - /// - /// A parser that applies the specified parser a defined number of times. - /// - public static Parser Repeat(this Parser parser, int count) => - Repeat(parser, count, count); - - /// - /// Creates a parser that applies the specified parser between and times, inclusive. - /// - /// The type of the value produced by the specified parser. - /// The parser to apply repeatedly. - /// The minimum number of repetitions. - /// The maximum number of repetitions. - /// - /// A parser that applies the specified parser between and times, inclusive. - /// - public static Parser> Repeat(this Parser parser, int min, int max) - { - Argument.ThrowIfNegative(min); - Argument.ThrowIfNegativeOrZero(max); - Argument.ThrowIfGreaterThan(min, max); - - return parser is ICharClassSupport s - ? (Parser>)(object)CreateRepeatParser(s.GetCharClass(), min, max) - : new RepeatParser(parser, min, max); - } - - /// - /// Creates a parser that applies the specified parser between and times, inclusive. - /// - /// The parser to apply repeatedly. - /// The minimum number of repetitions. - /// The maximum number of repetitions. - /// - /// A parser that applies the specified parser between and times, inclusive. - /// - public static Parser Repeat(this Parser parser, int min, int max) - { - Argument.ThrowIfNegative(min); - Argument.ThrowIfNegativeOrZero(max); - Argument.ThrowIfGreaterThan(min, max); - - return parser is ICharClassSupport s - ? CreateRepeatParser(s.GetCharClass(), min, max).Void() - : new VoidRepeatParser(parser, min, max); - } - - private static Parser> CreateRepeatParser(CharClass @class, int min, int max) - { - if (@class.Ranges.Length == 0) - @class = @class.EnrichRanges(); - - var errors = @class.ToPrintable(); - var ranges = @class.Ranges; - - #if NET8_0_OR_GREATER - if (ranges.Length != 0) - { - // SearchValues.IndexOfAnyExcept is highly optimized for: - // - A single range, e.g. [A..Z] - // - The ASCII character set ([0..127]) - // - A total character count between 1 and 5 - - // While we could use SearchValues for other cases as well, - // it may allocate a significant amount of memory - // and may not be as efficient as desired. - // Therefore, for other cases, we use a different search strategy. - - if (ranges.Length == 1 || ranges[^1].Hi <= 127 || @class.SymbolCount <= 5) - { - return new RepeatCharClassParser( - new SearchValuesSearcher(System.Buffers.SearchValues.Create(@class.GetSymbols())), - @class.UnicodeCategories, - min, max) { Name = errors }; - } - } - #endif - - // - // TODO: For ASCII, it might be worth creating a specialized version with SIMD support for .NET6 and .NET7 - // - - if (@class.OverallWidth <= 128) - return new RepeatCharClassParser>( - new BitVectorSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors }; - - if (@class.OverallWidth <= 256) - return new RepeatCharClassParser>( - new BitVectorSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors }; - - if (@class.OverallWidth <= 512) - return new RepeatCharClassParser>( - new BitVectorSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors }; - - var list = new List<(int comparisons, int consumption, Parser> parser)>(); - - if (true) - { - var count = @class.SymbolCount; - var width = Avx2.IsSupported - ? Vector256.Count - : Sse41.IsSupported || AdvSimd.IsSupported - ? Vector128.Count - : 0; - - list.Add( - ( - comparisons: width != 0 ? (count + width - 1 & -width) >>> BitOperations.Log2((uint)width) : count, - consumption: width != 0 ? (count + width - 1) & -width : count, - parser: new RepeatCharClassParser( - new ContainsSearcher(@class.GetSymbols(simdAligned: true)), - @class.UnicodeCategories, min, max) { Name = errors } - ) - ); - } - - if (Avx2.IsSupported) - { - var count = ranges.Length + 15 & -16; - list.Add( - ( - comparisons: count >>> 4, - consumption: count * 2, - parser: new RepeatCharClassParser>( - new SimdRangeSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors } - ) - ); - } - - if (Sse41.IsSupported || AdvSimd.IsSupported) - { - var count = ranges.Length + 7 & -8; - list.Add( - ( - comparisons: count >>> 3, - consumption: count * 2, - parser: new RepeatCharClassParser>( - new SimdRangeSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors } - ) - ); - } - - if (true) - { - var count = (int)BitOperations.RoundUpToPowerOf2( - (uint)BitOperations.Log2((uint)ranges.Length) - ); - - var overhead = Avx2.IsSupported - ? 3 - : Sse41.IsSupported || AdvSimd.IsSupported - ? 2 - : 0; - - list.Add( - ( - comparisons: count + overhead, - consumption: ranges.Length * 2, - parser: new RepeatCharClassParser( - new BinaryRangeSearcher(ranges), - @class.UnicodeCategories, min, max) { Name = errors } - ) - ); - } - - return list - .OrderBy(v => v.comparisons) - .ThenBy(v => v.consumption) - .First() - .parser; - } - - - #region Inner type: RepeatParser - - /// - /// Represents a parser that applies the specified parser a defined number of times. - /// - /// The type of the value produced by the specified parser. - private sealed class RepeatParser : Parser> - { - private readonly Parser _parser; - private readonly int _min; - private readonly int _max; - - /// - /// Represents a parser that applies the specified parser a defined number of times. - /// - /// The to be applied a specified number of times. - /// The minimum number of repetitions. - /// The maximum number of repetitions. - public RepeatParser(Parser parser, int min, int max) => - (_parser, _min, _max) = (parser, min, max); - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) - { - var list = new List(); - var bookmark = context.BookmarkPosition(); - var parser = _parser; - - do - { - var last = context.Position; - - if (!parser.TryParse(ref context, out var result)) - break; - - list.Add(result); - - // - // Prevent infinite loop - // - if (list.Count >= _min) - { - // - // Parsing failed in this case because: - // 1. The parser matched, but the position remained unchanged. - // 2. Rechecking would yield the same result, making it redundant. - // 3. If a parser matches but the position remains unchanged, it results in an infinite loop. - // - if (context.Position == last) - break; - } - } - while (list.Count < _max); - - if (list.Count >= _min) - { - context.SetMatched(bookmark); - - value = list; - return true; - } - - value = null; - context.RestorePosition(bookmark); - - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidRepeatParser(_parser.Void(), _min, _max) { Name = Name }; - } - - #endregion - - #region Inner type: VoidRepeatParser - - /// - /// Represents a specialized parser used as an optimization that discards the parsed result, - /// while applying the specified parser a defined number of times. - /// - private sealed class VoidRepeatParser : Parser - { - private readonly Parser _parser; - private readonly int _min; - private readonly int _max; - - /// - /// Initializes a new instance of the class. - /// - /// The to be applied a specified number of times. - /// The minimum number of repetitions. - /// The maximum number of repetitions. - public VoidRepeatParser(Parser parser, int min, int max) => - (_parser, _min, _max) = (parser, min, max); - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var count = 0; - var bookmark = context.BookmarkPosition(); - var parser = _parser; - - do - { - var last = context.Position; - if (!parser.TryParse(ref context, out value)) - break; - - // - // Prevent infinite loop - // - if (context.Position != last) - continue; - - count = _min; - break; - } - while (++count < _max); - - if (count >= _min) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: RepeatCharClassParser - - private sealed class RepeatCharClassParser : Parser> where TSearcher : struct, ICharClassRangeSearcher - { - private TSearcher _searcher; - private readonly CharClassUnicodeCategory _categories; - private readonly int _min; - private readonly int _max; - - public RepeatCharClassParser(TSearcher searcher, CharClassUnicodeCategory categories, int min, int max) - { - Debug.Assert(min >= 0 && min <= max); - - _searcher = searcher; - _categories = categories; - _min = min; - _max = max; - } - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) - { - value = null; - - var s = context.Remaining; - var count = 0; - - while (s.Length != 0) - { - var index = _searcher.IndexOfAnyExcept(s); - - if ((uint)index < (uint)s.Length) - { - count += index; - - if (!_categories.IsInclude(s[index])) - break; - - index++; - count++; - - // At this point, "index" is guaranteed to be within [0..s.Length]. - // While the Slice method performs its own bounds check and would throw if out of range, - // our explicit check helps the JIT recognize that the call to Slice is safe. - // This allows the JIT to eliminate the internal bounds check - // and avoid generating unnecessary exception-handling code inside Slice. - // - // In other words, the check would happen anyway, but by placing it here explicitly, - // we eliminate the need for exception code inside Slice itself. - // - // Even though logically (uint)index < (uint)s.Length implies (uint)index + 1 <= (uint)s.Length, - // the JIT cannot currently prove this identity and therefore cannot optimize away - // the redundant check in Slice. - - if ((uint)index <= (uint)s.Length) - s = s.Slice(index); - } - else - { - count += s.Length; - break; - } - } - - if (count >= _min) - { - if (count < _max) - context.ReportExpected(context.Position + count, Name); - - count = Math.Min(count, _max); - context.Advance(count); - - value = ListFactory.CreateList(context.MatchedSegment); - } - else - { - context.ReportExpected(context.Position + count, Name); - } - - return count >= _min; - } - - /// - protected internal override Parser> ToNamedParser(string? name) => - new RepeatCharClassParser(_searcher, _categories, _min, _max) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new VoidRepeatCharClassParser(_searcher, _categories, _min, _max) { Name = Name }; - } - - #endregion - - #region Inner type: VoidRepeatCharClassParser - - private sealed class VoidRepeatCharClassParser : Parser where TSearcher : struct, ICharClassRangeSearcher - { - private TSearcher _searcher; - private readonly CharClassUnicodeCategory _categories; - private readonly int _min; - private readonly int _max; - - public VoidRepeatCharClassParser(TSearcher searcher, CharClassUnicodeCategory categories, int min, int max) - { - Debug.Assert(min >= 0 && min <= max); - - _searcher = searcher; - _categories = categories; - _min = min; - _max = max; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - Unsafe.SkipInit(out value); - - var s = context.Remaining; - var count = 0; - - while (s.Length != 0) - { - var index = _searcher.IndexOfAnyExcept(s); - if ((uint)index < (uint)s.Length) - { - count += index; - - if (!_categories.IsInclude(s[index])) - break; - - index++; - count++; - - // At this point, "index" is guaranteed to be within [0..s.Length]. - // While the Slice method performs its own bounds check and would throw if out of range, - // our explicit check helps the JIT recognize that the call to Slice is safe. - // This allows the JIT to eliminate the internal bounds check - // and avoid generating unnecessary exception-handling code inside Slice. - // - // In other words, the check would happen anyway, but by placing it here explicitly, - // we eliminate the need for exception code inside Slice itself. - // - // Even though logically (uint)index < (uint)s.Length implies (uint)index + 1 <= (uint)s.Length, - // the JIT cannot currently prove this identity and therefore cannot optimize away - // the redundant check in Slice. - - if ((uint)index <= (uint)s.Length) - s = s.Slice(index); - } - else - { - count += s.Length; - break; - } - } - - if (count >= _min) - { - if (count < _max) - context.ReportExpected(context.Position + count, Name); - - count = Math.Min(count, _max); - context.Advance(count); - } - else - { - context.ReportExpected(context.Position + count, Name); - } - - return count >= _min; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new VoidRepeatCharClassParser(_searcher, _categories, _min, _max) { Name = name }; - } - - #endregion -} +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that applies the specified parser zero or more times. + /// + /// The type of the value produced by the specified parser. + /// The to apply zero or more times. + /// + /// A parser that applies the specified parser zero or more times. + /// + public static Parser> ZeroOrMore(this Parser parser) => + Repeat(parser, 0, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser zero or more times. + /// + /// The to apply zero or more times. + /// + /// A parser that applies the specified parser zero or more times. + /// + public static Parser ZeroOrMore(this Parser parser) => + Repeat(parser, 0, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser at least once. + /// + /// The type of the value produced by the specified parser. + /// The to apply at least once. + /// + /// A parser that applies the specified parser at least once. + /// + public static Parser> OneOrMore(this Parser parser) => + Repeat(parser, 1, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser at least once. + /// + /// The to apply at least once. + /// + /// A parser that applies the specified parser at least once. + /// + public static Parser OneOrMore(this Parser parser) => + Repeat(parser, 1, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser zero or more times. + /// + /// The type of the value produced by the specified parser. + /// The to apply zero or more times. + /// + /// A parser that applies the specified parser zero or more times. + /// + public static Parser> Many(this Parser parser) => + Repeat(parser, 0, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser zero or more times. + /// + /// The to apply zero or more times. + /// + /// A parser that applies the specified parser zero or more times. + /// + public static Parser Many(this Parser parser) => + Repeat(parser, 0, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser at least the defined number of times. + /// + /// The type of the value produced by the specified parser. + /// The to apply at least the specified number of times. + /// The minimum number of repetitions. + /// + /// A parser that applies the specified parser at least a defined number of times. + /// + public static Parser> AtLeast(this Parser parser, int count) => + Repeat(parser, count, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser at least the specified number of times. + /// + /// The to apply at least the specified number of times. + /// The minimum number of repetitions. + /// + /// A parser that applies the specified parser at least a defined number of times. + /// + public static Parser AtLeast(this Parser parser, int count) => + Repeat(parser, count, int.MaxValue); + + /// + /// Creates a parser that applies the specified parser a defined number of times. + /// + /// The type of the value produced by the specified parser. + /// The to apply the specified number of times. + /// The number of repetitions. + /// + /// A parser that applies the specified parser a defined number of times. + /// + public static Parser> Repeat(this Parser parser, int count) => + Repeat(parser, count, count); + + /// + /// Creates a parser that applies the specified parser a defined number of times. + /// + /// The to apply the specified number of times. + /// The number of repetitions. + /// + /// A parser that applies the specified parser a defined number of times. + /// + public static Parser Repeat(this Parser parser, int count) => + Repeat(parser, count, count); + + /// + /// Creates a parser that applies the specified parser between and times, inclusive. + /// + /// The type of the value produced by the specified parser. + /// The parser to apply repeatedly. + /// The minimum number of repetitions. + /// The maximum number of repetitions. + /// + /// A parser that applies the specified parser between and times, inclusive. + /// + public static Parser> Repeat(this Parser parser, int min, int max) + { + Argument.ThrowIfNegative(min); + Argument.ThrowIfNegativeOrZero(max); + Argument.ThrowIfGreaterThan(min, max); + + return parser is ICharClassSupport s + ? (Parser>)(object)CreateRepeatParser(s.GetCharClass(), min, max) + : new RepeatParser(parser, min, max); + } + + /// + /// Creates a parser that applies the specified parser between and times, inclusive. + /// + /// The parser to apply repeatedly. + /// The minimum number of repetitions. + /// The maximum number of repetitions. + /// + /// A parser that applies the specified parser between and times, inclusive. + /// + public static Parser Repeat(this Parser parser, int min, int max) + { + Argument.ThrowIfNegative(min); + Argument.ThrowIfNegativeOrZero(max); + Argument.ThrowIfGreaterThan(min, max); + + return parser is ICharClassSupport s + ? CreateRepeatParser(s.GetCharClass(), min, max).Void() + : new VoidRepeatParser(parser, min, max); + } + + private static Parser> CreateRepeatParser(CharClass @class, int min, int max) + { + if (@class.Ranges.Length == 0) + @class = @class.EnrichRanges(); + + var errors = @class.ToPrintable(); + var ranges = @class.Ranges; + + #if NET8_0_OR_GREATER + if (ranges.Length != 0) + { + // SearchValues.IndexOfAnyExcept is highly optimized for: + // - A single range, e.g. [A..Z] + // - The ASCII character set ([0..127]) + // - A total character count between 1 and 5 + + // While we could use SearchValues for other cases as well, + // it may allocate a significant amount of memory + // and may not be as efficient as desired. + // Therefore, for other cases, we use a different search strategy. + + if (ranges.Length == 1 || ranges[^1].Hi <= 127 || @class.SymbolCount <= 5) + { + return new RepeatCharClassParser( + new SearchValuesSearcher(System.Buffers.SearchValues.Create(@class.GetSymbols())), + @class.UnicodeCategories, + min, max) { Name = errors }; + } + } + #endif + + // + // TODO: For ASCII, it might be worth creating a specialized version with SIMD support for .NET6 and .NET7 + // + + if (@class.OverallWidth <= 128) + return new RepeatCharClassParser>( + new BitVectorSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors }; + + if (@class.OverallWidth <= 256) + return new RepeatCharClassParser>( + new BitVectorSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors }; + + if (@class.OverallWidth <= 512) + return new RepeatCharClassParser>( + new BitVectorSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors }; + + var list = new List<(int comparisons, int consumption, Parser> parser)>(); + + if (true) + { + var count = @class.SymbolCount; + var width = Avx2.IsSupported + ? Vector256.Count + : Sse41.IsSupported || AdvSimd.IsSupported + ? Vector128.Count + : 0; + + list.Add( + ( + comparisons: width != 0 ? (count + width - 1 & -width) >>> BitOperations.Log2((uint)width) : count, + consumption: width != 0 ? (count + width - 1) & -width : count, + parser: new RepeatCharClassParser( + new ContainsSearcher(@class.GetSymbols(simdAligned: true)), + @class.UnicodeCategories, min, max) { Name = errors } + ) + ); + } + + if (Avx2.IsSupported) + { + var count = ranges.Length + 15 & -16; + list.Add( + ( + comparisons: count >>> 4, + consumption: count * 2, + parser: new RepeatCharClassParser>( + new SimdRangeSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors } + ) + ); + } + + if (Sse41.IsSupported || AdvSimd.IsSupported) + { + var count = ranges.Length + 7 & -8; + list.Add( + ( + comparisons: count >>> 3, + consumption: count * 2, + parser: new RepeatCharClassParser>( + new SimdRangeSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors } + ) + ); + } + + if (true) + { + var count = (int)BitOperations.RoundUpToPowerOf2( + (uint)BitOperations.Log2((uint)ranges.Length) + ); + + var overhead = Avx2.IsSupported + ? 3 + : Sse41.IsSupported || AdvSimd.IsSupported + ? 2 + : 0; + + list.Add( + ( + comparisons: count + overhead, + consumption: ranges.Length * 2, + parser: new RepeatCharClassParser( + new BinaryRangeSearcher(ranges), + @class.UnicodeCategories, min, max) { Name = errors } + ) + ); + } + + return list + .OrderBy(v => v.comparisons) + .ThenBy(v => v.consumption) + .First() + .parser; + } + + + #region Inner type: RepeatParser + + /// + /// Represents a parser that applies the specified parser a defined number of times. + /// + /// The type of the value produced by the specified parser. + private sealed class RepeatParser : Parser> + { + private readonly Parser _parser; + private readonly int _min; + private readonly int _max; + + /// + /// Represents a parser that applies the specified parser a defined number of times. + /// + /// The to be applied a specified number of times. + /// The minimum number of repetitions. + /// The maximum number of repetitions. + public RepeatParser(Parser parser, int min, int max) => + (_parser, _min, _max) = (parser, min, max); + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) + { + var list = new List(); + var bookmark = context.BookmarkPosition(); + var parser = _parser; + + do + { + var last = context.Position; + + if (!parser.TryParse(ref context, out var result)) + break; + + list.Add(result); + + // + // Prevent infinite loop + // + if (list.Count >= _min) + { + // + // Parsing failed in this case because: + // 1. The parser matched, but the position remained unchanged. + // 2. Rechecking would yield the same result, making it redundant. + // 3. If a parser matches but the position remains unchanged, it results in an infinite loop. + // + if (context.Position == last) + break; + } + } + while (list.Count < _max); + + if (list.Count >= _min) + { + context.SetMatched(bookmark); + + value = list; + return true; + } + + value = null; + context.RestorePosition(bookmark); + + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidRepeatParser(_parser.Void(), _min, _max) { Name = Name }; + } + + #endregion + + #region Inner type: VoidRepeatParser + + /// + /// Represents a specialized parser used as an optimization that discards the parsed result, + /// while applying the specified parser a defined number of times. + /// + private sealed class VoidRepeatParser : Parser + { + private readonly Parser _parser; + private readonly int _min; + private readonly int _max; + + /// + /// Initializes a new instance of the class. + /// + /// The to be applied a specified number of times. + /// The minimum number of repetitions. + /// The maximum number of repetitions. + public VoidRepeatParser(Parser parser, int min, int max) => + (_parser, _min, _max) = (parser, min, max); + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var count = 0; + var bookmark = context.BookmarkPosition(); + var parser = _parser; + + do + { + var last = context.Position; + if (!parser.TryParse(ref context, out value)) + break; + + // + // Prevent infinite loop + // + if (context.Position != last) + continue; + + count = _min; + break; + } + while (++count < _max); + + if (count >= _min) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: RepeatCharClassParser + + private sealed class RepeatCharClassParser : Parser> where TSearcher : struct, ICharClassRangeSearcher + { + private TSearcher _searcher; + private readonly CharClassUnicodeCategory _categories; + private readonly int _min; + private readonly int _max; + + public RepeatCharClassParser(TSearcher searcher, CharClassUnicodeCategory categories, int min, int max) + { + Debug.Assert(min >= 0 && min <= max); + + _searcher = searcher; + _categories = categories; + _min = min; + _max = max; + } + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) + { + value = null; + + var s = context.Remaining; + var count = 0; + + while (s.Length != 0) + { + var index = _searcher.IndexOfAnyExcept(s); + + if ((uint)index < (uint)s.Length) + { + count += index; + + if (!_categories.IsInclude(s[index])) + break; + + index++; + count++; + + // At this point, "index" is guaranteed to be within [0..s.Length]. + // While the Slice method performs its own bounds check and would throw if out of range, + // our explicit check helps the JIT recognize that the call to Slice is safe. + // This allows the JIT to eliminate the internal bounds check + // and avoid generating unnecessary exception-handling code inside Slice. + // + // In other words, the check would happen anyway, but by placing it here explicitly, + // we eliminate the need for exception code inside Slice itself. + // + // Even though logically (uint)index < (uint)s.Length implies (uint)index + 1 <= (uint)s.Length, + // the JIT cannot currently prove this identity and therefore cannot optimize away + // the redundant check in Slice. + + if ((uint)index <= (uint)s.Length) + s = s.Slice(index); + } + else + { + count += s.Length; + break; + } + } + + if (count >= _min) + { + if (count < _max) + context.ReportExpected(context.Position + count, Name); + + count = Math.Min(count, _max); + context.Advance(count); + + value = ListFactory.CreateList(context.MatchedSegment); + } + else + { + context.ReportExpected(context.Position + count, Name); + } + + return count >= _min; + } + + /// + protected internal override Parser> ToNamedParser(string? name) => + new RepeatCharClassParser(_searcher, _categories, _min, _max) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new VoidRepeatCharClassParser(_searcher, _categories, _min, _max) { Name = Name }; + } + + #endregion + + #region Inner type: VoidRepeatCharClassParser + + private sealed class VoidRepeatCharClassParser : Parser where TSearcher : struct, ICharClassRangeSearcher + { + private TSearcher _searcher; + private readonly CharClassUnicodeCategory _categories; + private readonly int _min; + private readonly int _max; + + public VoidRepeatCharClassParser(TSearcher searcher, CharClassUnicodeCategory categories, int min, int max) + { + Debug.Assert(min >= 0 && min <= max); + + _searcher = searcher; + _categories = categories; + _min = min; + _max = max; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + Unsafe.SkipInit(out value); + + var s = context.Remaining; + var count = 0; + + while (s.Length != 0) + { + var index = _searcher.IndexOfAnyExcept(s); + if ((uint)index < (uint)s.Length) + { + count += index; + + if (!_categories.IsInclude(s[index])) + break; + + index++; + count++; + + // At this point, "index" is guaranteed to be within [0..s.Length]. + // While the Slice method performs its own bounds check and would throw if out of range, + // our explicit check helps the JIT recognize that the call to Slice is safe. + // This allows the JIT to eliminate the internal bounds check + // and avoid generating unnecessary exception-handling code inside Slice. + // + // In other words, the check would happen anyway, but by placing it here explicitly, + // we eliminate the need for exception code inside Slice itself. + // + // Even though logically (uint)index < (uint)s.Length implies (uint)index + 1 <= (uint)s.Length, + // the JIT cannot currently prove this identity and therefore cannot optimize away + // the redundant check in Slice. + + if ((uint)index <= (uint)s.Length) + s = s.Slice(index); + } + else + { + count += s.Length; + break; + } + } + + if (count >= _min) + { + if (count < _max) + context.ReportExpected(context.Position + count, Name); + + count = Math.Min(count, _max); + context.Advance(count); + } + else + { + context.ReportExpected(context.Position + count, Name); + } + + return count >= _min; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new VoidRepeatCharClassParser(_searcher, _categories, _min, _max) { Name = name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Return.cs b/src/Ramstack.Parsing/Parser.Return.cs index 474a8e2..8aaf35f 100644 --- a/src/Ramstack.Parsing/Parser.Return.cs +++ b/src/Ramstack.Parsing/Parser.Return.cs @@ -1,42 +1,42 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that does not consume any input and returns the . - /// - /// The type of the value produced by the parser. - /// The value to return. - /// - /// A parser that does not consume any input and returns the . - /// - public static Parser Return(T value) => - new ReturnParser(value); - - #region Inner type: ReturnParser - - /// - /// Creates a parser that does not consume any input and returns the . - /// - /// The type of the value produced by the parser. - /// The value to return. - private sealed class ReturnParser(T returnValue) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - value = returnValue!; - return true; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new ReturnParser(returnValue) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new ReturnParser(Unit.Value) { Name = Name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that does not consume any input and returns the . + /// + /// The type of the value produced by the parser. + /// The value to return. + /// + /// A parser that does not consume any input and returns the . + /// + public static Parser Return(T value) => + new ReturnParser(value); + + #region Inner type: ReturnParser + + /// + /// Creates a parser that does not consume any input and returns the . + /// + /// The type of the value produced by the parser. + /// The value to return. + private sealed class ReturnParser(T returnValue) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + value = returnValue!; + return true; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new ReturnParser(returnValue) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new ReturnParser(Unit.Value) { Name = Name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.S.cs b/src/Ramstack.Parsing/Parser.S.cs index e64eb49..7902f98 100644 --- a/src/Ramstack.Parsing/Parser.S.cs +++ b/src/Ramstack.Parsing/Parser.S.cs @@ -1,49 +1,49 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Gets a parser that matches zero or more whitespace characters. - /// - /// - /// Character.WhiteSpace.ZeroOrMore().Void() - /// - public static Parser S { get; } = new SParser(); - - #region Inner type: SParser - - /// - /// Represents a parser that matches zero or more whitespace characters. - /// - /// - /// This parser is functionally equivalent to Character.WhiteSpace.ZeroOrMore().Void(), - /// but it is only 1-3% faster. The only reason for its existence is that, - /// unlike the general implementation, it does not allocate memory for internal data, - /// saving about 36 bytes. - /// - private sealed class SParser : Parser - { - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - Unsafe.SkipInit(out value); - - var s = context.Remaining; - - for (var i = 0; ; i++) - { - if ((uint)i < (uint)s.Length && char.IsWhiteSpace(s[i])) - continue; - - context.Advance(i); - return true; - } - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new SParser { Name = name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Gets a parser that matches zero or more whitespace characters. + /// + /// + /// Character.WhiteSpace.ZeroOrMore().Void() + /// + public static Parser S { get; } = new SParser(); + + #region Inner type: SParser + + /// + /// Represents a parser that matches zero or more whitespace characters. + /// + /// + /// This parser is functionally equivalent to Character.WhiteSpace.ZeroOrMore().Void(), + /// but it is only 1-3% faster. The only reason for its existence is that, + /// unlike the general implementation, it does not allocate memory for internal data, + /// saving about 36 bytes. + /// + private sealed class SParser : Parser + { + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + Unsafe.SkipInit(out value); + + var s = context.Remaining; + + for (var i = 0; ; i++) + { + if ((uint)i < (uint)s.Length && char.IsWhiteSpace(s[i])) + continue; + + context.Advance(i); + return true; + } + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new SParser { Name = name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Separated.cs b/src/Ramstack.Parsing/Parser.Separated.cs index 8960a8a..f565934 100644 --- a/src/Ramstack.Parsing/Parser.Separated.cs +++ b/src/Ramstack.Parsing/Parser.Separated.cs @@ -1,164 +1,164 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that repeatedly applies the main parser, interleaved with a separator specified by another parser. - /// - /// - /// If an item and its following separator both succeed without consuming input, repetition stops - /// once the minimum number of items has been matched. The successfully parsed item is included in the result. - /// - /// The type of the value produced by the main parser. - /// The type of the value produced by the separator parser. - /// The main parser. - /// The parser that identifies the separators placed between the elements parsed by the main parser. - /// if a trailing separator is allowed; otherwise, . - /// The minimum number of repetitions. Must be non-negative and no greater than . - /// The maximum number of repetitions. Must be greater than zero. - /// - /// A parser that repeatedly applies the main parser, interleaved with the specified separator. - /// - public static Parser> Separated(this Parser parser, Parser separator, bool allowTrailing = false, int min = 0, int max = int.MaxValue) - { - Argument.ThrowIfNegative(min); - Argument.ThrowIfNegativeOrZero(max); - Argument.ThrowIfGreaterThan(min, max); - - return new SeparatedParser(parser, separator.Void(), allowTrailing, min, max); - } - - #region Inner type: SeparatedParser - - /// - /// Represents a parser that repeatedly applies the main parser, interleaved with a separator specified by another parser. - /// - /// The type of the value produced by the main parser. - /// The main parser. - /// The parser that identifies the separators placed between the elements parsed by the main parser. - /// if a trailing separator is allowed; otherwise, . - /// The minimum number of repetitions. - /// The maximum number of repetitions. - private sealed class SeparatedParser(Parser parser, Parser separator, bool allowTrailing, int min, int max) : Parser> - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) - { - var list = new List(); - var bookmark = context.BookmarkPosition(); - var separatorBookmark = bookmark; - - do - { - var position = context.Position; - - if (!parser.TryParse(ref context, out var result)) - break; - - list.Add(result); - - separatorBookmark = context.BookmarkPosition(); - if (!separator.TryParse(ref context, out _)) - break; - - // Stop empty matches once the minimum count has been reached. - if (list.Count >= min && context.Position == position) - break; - } - while (list.Count < max); - - if (list.Count >= min) - { - if (!allowTrailing) - context.RestorePosition(separatorBookmark); - - context.SetMatched(bookmark); - value = list; - return true; - } - - value = null; - context.RestorePosition(bookmark); - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeparatedParser(parser.Void(), separator, allowTrailing, min, max); - } - - #endregion - - #region Inner type: VoidSeparatorParser - - /// - /// Represents a parser that repeatedly applies the main parser with separators between matches - /// and discards the parsed values. - /// - private sealed class VoidSeparatedParser : Parser - { - private readonly Parser _parser; - private readonly Parser _separator; - private readonly bool _allowTrailing; - private readonly int _min; - private readonly int _max; - - /// - /// Initializes a new instance of the class. - /// - /// The main parser. - /// The parser that identifies the separators placed between the elements parsed by the main parser. - /// if a trailing separator is allowed; otherwise, . - /// The minimum number of repetitions. - /// The maximum number of repetitions. - public VoidSeparatedParser(Parser parser, Parser separator, bool allowTrailing, int min, int max) - { - _parser = parser; - _separator = separator; - _allowTrailing = allowTrailing; - _min = min; - _max = max; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var count = 0; - var bookmark = context.BookmarkPosition(); - var separatorBookmark = bookmark; - - do - { - var position = context.Position; - - if (!_parser.TryParse(ref context, out value)) - break; - - count++; - - separatorBookmark = context.BookmarkPosition(); - if (!_separator.TryParse(ref context, out value)) - break; - - // Stop empty matches once the minimum count has been reached. - if (count >= _min && context.Position == position) - break; - } - while (count < _max); - - if (count >= _min) - { - if (!_allowTrailing) - context.RestorePosition(separatorBookmark); - - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that repeatedly applies the main parser, interleaved with a separator specified by another parser. + /// + /// + /// If an item and its following separator both succeed without consuming input, repetition stops + /// once the minimum number of items has been matched. The successfully parsed item is included in the result. + /// + /// The type of the value produced by the main parser. + /// The type of the value produced by the separator parser. + /// The main parser. + /// The parser that identifies the separators placed between the elements parsed by the main parser. + /// if a trailing separator is allowed; otherwise, . + /// The minimum number of repetitions. Must be non-negative and no greater than . + /// The maximum number of repetitions. Must be greater than zero. + /// + /// A parser that repeatedly applies the main parser, interleaved with the specified separator. + /// + public static Parser> Separated(this Parser parser, Parser separator, bool allowTrailing = false, int min = 0, int max = int.MaxValue) + { + Argument.ThrowIfNegative(min); + Argument.ThrowIfNegativeOrZero(max); + Argument.ThrowIfGreaterThan(min, max); + + return new SeparatedParser(parser, separator.Void(), allowTrailing, min, max); + } + + #region Inner type: SeparatedParser + + /// + /// Represents a parser that repeatedly applies the main parser, interleaved with a separator specified by another parser. + /// + /// The type of the value produced by the main parser. + /// The main parser. + /// The parser that identifies the separators placed between the elements parsed by the main parser. + /// if a trailing separator is allowed; otherwise, . + /// The minimum number of repetitions. + /// The maximum number of repetitions. + private sealed class SeparatedParser(Parser parser, Parser separator, bool allowTrailing, int min, int max) : Parser> + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) + { + var list = new List(); + var bookmark = context.BookmarkPosition(); + var separatorBookmark = bookmark; + + do + { + var position = context.Position; + + if (!parser.TryParse(ref context, out var result)) + break; + + list.Add(result); + + separatorBookmark = context.BookmarkPosition(); + if (!separator.TryParse(ref context, out _)) + break; + + // Stop empty matches once the minimum count has been reached. + if (list.Count >= min && context.Position == position) + break; + } + while (list.Count < max); + + if (list.Count >= min) + { + if (!allowTrailing) + context.RestorePosition(separatorBookmark); + + context.SetMatched(bookmark); + value = list; + return true; + } + + value = null; + context.RestorePosition(bookmark); + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeparatedParser(parser.Void(), separator, allowTrailing, min, max); + } + + #endregion + + #region Inner type: VoidSeparatorParser + + /// + /// Represents a parser that repeatedly applies the main parser with separators between matches + /// and discards the parsed values. + /// + private sealed class VoidSeparatedParser : Parser + { + private readonly Parser _parser; + private readonly Parser _separator; + private readonly bool _allowTrailing; + private readonly int _min; + private readonly int _max; + + /// + /// Initializes a new instance of the class. + /// + /// The main parser. + /// The parser that identifies the separators placed between the elements parsed by the main parser. + /// if a trailing separator is allowed; otherwise, . + /// The minimum number of repetitions. + /// The maximum number of repetitions. + public VoidSeparatedParser(Parser parser, Parser separator, bool allowTrailing, int min, int max) + { + _parser = parser; + _separator = separator; + _allowTrailing = allowTrailing; + _min = min; + _max = max; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var count = 0; + var bookmark = context.BookmarkPosition(); + var separatorBookmark = bookmark; + + do + { + var position = context.Position; + + if (!_parser.TryParse(ref context, out value)) + break; + + count++; + + separatorBookmark = context.BookmarkPosition(); + if (!_separator.TryParse(ref context, out value)) + break; + + // Stop empty matches once the minimum count has been reached. + if (count >= _min && context.Position == position) + break; + } + while (count < _max); + + if (count >= _min) + { + if (!_allowTrailing) + context.RestorePosition(separatorBookmark); + + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Seq.Generated.cs b/src/Ramstack.Parsing/Parser.Seq.Generated.cs index 32e7b43..3684e72 100644 --- a/src/Ramstack.Parsing/Parser.Seq.Generated.cs +++ b/src/Ramstack.Parsing/Parser.Seq.Generated.cs @@ -1,1248 +1,1248 @@ -//----------------------------------------------------------------------------// -// // -// This code was generated by a tool. // -// // -// Changes to this file may cause incorrect behavior // -// and will be lost if the code is regenerated. // -// // -//----------------------------------------------------------------------------// - -#nullable enable - -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2)> Seq(Parser parser1, Parser parser2) => - new SeqParser(parser1, parser2); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2) => - new VoidSeq2Parser(parser1, parser2); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3)> Seq(Parser parser1, Parser parser2, Parser parser3) => - new SeqParser(parser1, parser2, parser3); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3) => - new VoidSeq3Parser(parser1, parser2, parser3); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4) => - new SeqParser(parser1, parser2, parser3, parser4); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4) => - new VoidSeq4Parser(parser1, parser2, parser3, parser4); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) => - new SeqParser(parser1, parser2, parser3, parser4, parser5); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) => - new VoidSeq5Parser(parser1, parser2, parser3, parser4, parser5); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) => - new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) => - new VoidSeq6Parser(parser1, parser2, parser3, parser4, parser5, parser6); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) => - new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) => - new VoidSeq7Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) => - new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) => - new VoidSeq8Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8, T9 Value9)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) => - new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) => - new VoidSeq9Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8, T9 Value9, T10 Value10)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) => - new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9, parser10); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// The parser instance. - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) => - new VoidSeq10Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9, parser10); - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2) - { - _parser1 = parser1; - _parser2 = parser2; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq2Parser(_parser1.Void(), _parser2.Void()); - } - - #endregion - - #region Inner type: VoidSeq2Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq2Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq2Parser(Parser parser1, Parser parser2) - { - _parser1 = parser1; - _parser2 = parser2; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq3Parser(_parser1.Void(), _parser2.Void(), _parser3.Void()); - } - - #endregion - - #region Inner type: VoidSeq3Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq3Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq3Parser(Parser parser1, Parser parser2, Parser parser3) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq4Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void()); - } - - #endregion - - #region Inner type: VoidSeq4Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq4Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq4Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq5Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void()); - } - - #endregion - - #region Inner type: VoidSeq5Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq5Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq5Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - && _parser6.TryParse(ref context, out value.Item6!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq6Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void()); - } - - #endregion - - #region Inner type: VoidSeq6Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq6Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq6Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - && _parser6.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - && _parser6.TryParse(ref context, out value.Item6!) - && _parser7.TryParse(ref context, out value.Item7!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq7Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void()); - } - - #endregion - - #region Inner type: VoidSeq7Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq7Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq7Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - && _parser6.TryParse(ref context, out value) - && _parser7.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - && _parser6.TryParse(ref context, out value.Item6!) - && _parser7.TryParse(ref context, out value.Item7!) - && _parser8.TryParse(ref context, out value.Item8!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq8Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void()); - } - - #endregion - - #region Inner type: VoidSeq8Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq8Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq8Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - && _parser6.TryParse(ref context, out value) - && _parser7.TryParse(ref context, out value) - && _parser8.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - private readonly Parser _parser9; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - _parser9 = parser9; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8, T9) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - && _parser6.TryParse(ref context, out value.Item6!) - && _parser7.TryParse(ref context, out value.Item7!) - && _parser8.TryParse(ref context, out value.Item8!) - && _parser9.TryParse(ref context, out value.Item9!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq9Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void(), _parser9.Void()); - } - - #endregion - - #region Inner type: VoidSeq9Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq9Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - private readonly Parser _parser9; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq9Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - _parser9 = parser9; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - && _parser6.TryParse(ref context, out value) - && _parser7.TryParse(ref context, out value) - && _parser8.TryParse(ref context, out value) - && _parser9.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion - - #region Inner type: SeqParser - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - private readonly Parser _parser9; - private readonly Parser _parser10; - - /// - /// Initializes a new instance of the class. - /// - public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - _parser9 = parser9; - _parser10 = parser10; - } - - /// - public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) - && _parser2.TryParse(ref context, out value.Item2!) - && _parser3.TryParse(ref context, out value.Item3!) - && _parser4.TryParse(ref context, out value.Item4!) - && _parser5.TryParse(ref context, out value.Item5!) - && _parser6.TryParse(ref context, out value.Item6!) - && _parser7.TryParse(ref context, out value.Item7!) - && _parser8.TryParse(ref context, out value.Item8!) - && _parser9.TryParse(ref context, out value.Item9!) - && _parser10.TryParse(ref context, out value.Item10!) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq10Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void(), _parser9.Void(), _parser10.Void()); - } - - #endregion - - #region Inner type: VoidSeq10Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq10Parser : Parser - { - private readonly Parser _parser1; - private readonly Parser _parser2; - private readonly Parser _parser3; - private readonly Parser _parser4; - private readonly Parser _parser5; - private readonly Parser _parser6; - private readonly Parser _parser7; - private readonly Parser _parser8; - private readonly Parser _parser9; - private readonly Parser _parser10; - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq10Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) - { - _parser1 = parser1; - _parser2 = parser2; - _parser3 = parser3; - _parser4 = parser4; - _parser5 = parser5; - _parser6 = parser6; - _parser7 = parser7; - _parser8 = parser8; - _parser9 = parser9; - _parser10 = parser10; - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) - && _parser2.TryParse(ref context, out value) - && _parser3.TryParse(ref context, out value) - && _parser4.TryParse(ref context, out value) - && _parser5.TryParse(ref context, out value) - && _parser6.TryParse(ref context, out value) - && _parser7.TryParse(ref context, out value) - && _parser8.TryParse(ref context, out value) - && _parser9.TryParse(ref context, out value) - && _parser10.TryParse(ref context, out value) - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion -} +//----------------------------------------------------------------------------// +// // +// This code was generated by a tool. // +// // +// Changes to this file may cause incorrect behavior // +// and will be lost if the code is regenerated. // +// // +//----------------------------------------------------------------------------// + +#nullable enable + +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2)> Seq(Parser parser1, Parser parser2) => + new SeqParser(parser1, parser2); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2) => + new VoidSeq2Parser(parser1, parser2); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3)> Seq(Parser parser1, Parser parser2, Parser parser3) => + new SeqParser(parser1, parser2, parser3); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3) => + new VoidSeq3Parser(parser1, parser2, parser3); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4) => + new SeqParser(parser1, parser2, parser3, parser4); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4) => + new VoidSeq4Parser(parser1, parser2, parser3, parser4); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) => + new SeqParser(parser1, parser2, parser3, parser4, parser5); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) => + new VoidSeq5Parser(parser1, parser2, parser3, parser4, parser5); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) => + new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) => + new VoidSeq6Parser(parser1, parser2, parser3, parser4, parser5, parser6); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) => + new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) => + new VoidSeq7Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) => + new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) => + new VoidSeq8Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8, T9 Value9)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) => + new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) => + new VoidSeq9Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(T1 Value1, T2 Value2, T3 Value3, T4 Value4, T5 Value5, T6 Value6, T7 Value7, T8 Value8, T9 Value9, T10 Value10)> Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) => + new SeqParser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9, parser10); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// The parser instance. + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) => + new VoidSeq10Parser(parser1, parser2, parser3, parser4, parser5, parser6, parser7, parser8, parser9, parser10); + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2) + { + _parser1 = parser1; + _parser2 = parser2; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq2Parser(_parser1.Void(), _parser2.Void()); + } + + #endregion + + #region Inner type: VoidSeq2Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq2Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq2Parser(Parser parser1, Parser parser2) + { + _parser1 = parser1; + _parser2 = parser2; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq3Parser(_parser1.Void(), _parser2.Void(), _parser3.Void()); + } + + #endregion + + #region Inner type: VoidSeq3Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq3Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq3Parser(Parser parser1, Parser parser2, Parser parser3) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq4Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void()); + } + + #endregion + + #region Inner type: VoidSeq4Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq4Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq4Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq5Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void()); + } + + #endregion + + #region Inner type: VoidSeq5Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq5Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq5Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + && _parser6.TryParse(ref context, out value.Item6!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq6Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void()); + } + + #endregion + + #region Inner type: VoidSeq6Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq6Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq6Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + && _parser6.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + && _parser6.TryParse(ref context, out value.Item6!) + && _parser7.TryParse(ref context, out value.Item7!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq7Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void()); + } + + #endregion + + #region Inner type: VoidSeq7Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq7Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq7Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + && _parser6.TryParse(ref context, out value) + && _parser7.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + && _parser6.TryParse(ref context, out value.Item6!) + && _parser7.TryParse(ref context, out value.Item7!) + && _parser8.TryParse(ref context, out value.Item8!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq8Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void()); + } + + #endregion + + #region Inner type: VoidSeq8Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq8Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq8Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + && _parser6.TryParse(ref context, out value) + && _parser7.TryParse(ref context, out value) + && _parser8.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + private readonly Parser _parser9; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + _parser9 = parser9; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8, T9) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + && _parser6.TryParse(ref context, out value.Item6!) + && _parser7.TryParse(ref context, out value.Item7!) + && _parser8.TryParse(ref context, out value.Item8!) + && _parser9.TryParse(ref context, out value.Item9!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq9Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void(), _parser9.Void()); + } + + #endregion + + #region Inner type: VoidSeq9Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq9Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + private readonly Parser _parser9; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq9Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + _parser9 = parser9; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + && _parser6.TryParse(ref context, out value) + && _parser7.TryParse(ref context, out value) + && _parser8.TryParse(ref context, out value) + && _parser9.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion + + #region Inner type: SeqParser + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser : Parser<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + private readonly Parser _parser9; + private readonly Parser _parser10; + + /// + /// Initializes a new instance of the class. + /// + public SeqParser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + _parser9 = parser9; + _parser10 = parser10; + } + + /// + public override bool TryParse(ref ParseContext context, out (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) + && _parser2.TryParse(ref context, out value.Item2!) + && _parser3.TryParse(ref context, out value.Item3!) + && _parser4.TryParse(ref context, out value.Item4!) + && _parser5.TryParse(ref context, out value.Item5!) + && _parser6.TryParse(ref context, out value.Item6!) + && _parser7.TryParse(ref context, out value.Item7!) + && _parser8.TryParse(ref context, out value.Item8!) + && _parser9.TryParse(ref context, out value.Item9!) + && _parser10.TryParse(ref context, out value.Item10!) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq10Parser(_parser1.Void(), _parser2.Void(), _parser3.Void(), _parser4.Void(), _parser5.Void(), _parser6.Void(), _parser7.Void(), _parser8.Void(), _parser9.Void(), _parser10.Void()); + } + + #endregion + + #region Inner type: VoidSeq10Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq10Parser : Parser + { + private readonly Parser _parser1; + private readonly Parser _parser2; + private readonly Parser _parser3; + private readonly Parser _parser4; + private readonly Parser _parser5; + private readonly Parser _parser6; + private readonly Parser _parser7; + private readonly Parser _parser8; + private readonly Parser _parser9; + private readonly Parser _parser10; + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq10Parser(Parser parser1, Parser parser2, Parser parser3, Parser parser4, Parser parser5, Parser parser6, Parser parser7, Parser parser8, Parser parser9, Parser parser10) + { + _parser1 = parser1; + _parser2 = parser2; + _parser3 = parser3; + _parser4 = parser4; + _parser5 = parser5; + _parser6 = parser6; + _parser7 = parser7; + _parser8 = parser8; + _parser9 = parser9; + _parser10 = parser10; + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) + && _parser2.TryParse(ref context, out value) + && _parser3.TryParse(ref context, out value) + && _parser4.TryParse(ref context, out value) + && _parser5.TryParse(ref context, out value) + && _parser6.TryParse(ref context, out value) + && _parser7.TryParse(ref context, out value) + && _parser8.TryParse(ref context, out value) + && _parser9.TryParse(ref context, out value) + && _parser10.TryParse(ref context, out value) + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Seq.tt b/src/Ramstack.Parsing/Parser.Seq.tt index 4f5fa42..e7c3c28 100644 --- a/src/Ramstack.Parsing/Parser.Seq.tt +++ b/src/Ramstack.Parsing/Parser.Seq.tt @@ -1,138 +1,138 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ include file="Properties\Autogenerated.ttinclude" #> -#nullable enable - -namespace Ramstack.Parsing; - -partial class Parser -{ -<# - for (var n = 2; n <= 10; n++) - { - WriteSeparatorLine(n != 2); - GenerateMethods(n); - } - - for (var n = 2; n <= 10; n++) - { - WriteLine(); - GenerateClasses(n); - } -#> -} -<#+ private void GenerateMethods(int arity) { #> - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// -<#+ for (var n = 1; n <= arity; n++) { #> - /// The parser instance. -<#+ } #> - /// - /// A parser that applies the specified parsers sequentially and returns a tuple of their results. - /// - public static Parser<(<#=GenerateList(arity, n => $"T{n} Value{n}", from: 1)#>)> Seq<<#=GenerateList(arity, n => $"T{n}", from: 1)#>>(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) => - new SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>>(<#=GenerateList(arity, n => $"parser{n}", from: 1, separator: ", ")#>); - - /// - /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// -<#+ for (var n = 1; n <= arity; n++) { #> - /// The parser instance. -<#+ } #> - /// - /// A parser that applies the specified parsers sequentially. - /// - public static Parser Seq(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) => - new VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"parser{n}", from: 1, separator: ", ")#>); -<#+ } #> -<#+ private void GenerateClasses(int arity) { #> - #region Inner type: SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>> - - /// - /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>> : Parser<(<#=GenerateList(arity, n => $"T{n}", from: 1)#>)> - { -<#+ for (var n = 1; n <= arity; n++) { #> - private readonly Parser> _parser<#=n#>; -<#+ } #> - - /// - /// Initializes a new instance of the }"/> class. - /// - public SeqParser(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) - { -<#+ for (var n = 1; n <= arity; n++) { #> - _parser<#=n#> = parser<#=n#>; -<#+ } #> - } - - /// - public override bool TryParse(ref ParseContext context, out (<#=GenerateList(arity, n => $"T{n}", from: 1)#>) value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value.Item1!) -<#+ for (var n = 2; n <= arity; n++) { #> - && _parser<#=n#>.TryParse(ref context, out value.Item<#=n#>!) -<#+ } #> - ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - value = default; - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"_parser{n}.Void()", from: 1, separator: ", ")#>); - } - - #endregion - - #region Inner type: VoidSeq<#=arity#>Parser - - /// - /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. - /// - private sealed class VoidSeq<#=arity#>Parser : Parser - { -<#+ for (var n = 1; n <= arity; n++) { #> - private readonly Parser _parser<#=n#>; -<#+ } #> - - /// - /// Initializes a new instance of the class. - /// - public VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) - { -<#+ for (var n = 1; n <= arity; n++) { #> - _parser<#=n#> = parser<#=n#>; -<#+ } #> - } - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - if (_parser1.TryParse(ref context, out value) -<#+ for (var n = 2; n <= arity; n++) { #> - && _parser<#=n#>.TryParse(ref context, out value) -<#+ } #> ) - { - context.SetMatched(bookmark); - return true; - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion -<#+ } #> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ include file="Properties\Autogenerated.ttinclude" #> +#nullable enable + +namespace Ramstack.Parsing; + +partial class Parser +{ +<# + for (var n = 2; n <= 10; n++) + { + WriteSeparatorLine(n != 2); + GenerateMethods(n); + } + + for (var n = 2; n <= 10; n++) + { + WriteLine(); + GenerateClasses(n); + } +#> +} +<#+ private void GenerateMethods(int arity) { #> + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// +<#+ for (var n = 1; n <= arity; n++) { #> + /// The parser instance. +<#+ } #> + /// + /// A parser that applies the specified parsers sequentially and returns a tuple of their results. + /// + public static Parser<(<#=GenerateList(arity, n => $"T{n} Value{n}", from: 1)#>)> Seq<<#=GenerateList(arity, n => $"T{n}", from: 1)#>>(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) => + new SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>>(<#=GenerateList(arity, n => $"parser{n}", from: 1, separator: ", ")#>); + + /// + /// Creates a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// +<#+ for (var n = 1; n <= arity; n++) { #> + /// The parser instance. +<#+ } #> + /// + /// A parser that applies the specified parsers sequentially. + /// + public static Parser Seq(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) => + new VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"parser{n}", from: 1, separator: ", ")#>); +<#+ } #> +<#+ private void GenerateClasses(int arity) { #> + #region Inner type: SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>> + + /// + /// Represents a parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class SeqParser<<#=GenerateList(arity, n => $"T{n}", from: 1)#>> : Parser<(<#=GenerateList(arity, n => $"T{n}", from: 1)#>)> + { +<#+ for (var n = 1; n <= arity; n++) { #> + private readonly Parser> _parser<#=n#>; +<#+ } #> + + /// + /// Initializes a new instance of the }"/> class. + /// + public SeqParser(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) + { +<#+ for (var n = 1; n <= arity; n++) { #> + _parser<#=n#> = parser<#=n#>; +<#+ } #> + } + + /// + public override bool TryParse(ref ParseContext context, out (<#=GenerateList(arity, n => $"T{n}", from: 1)#>) value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value.Item1!) +<#+ for (var n = 2; n <= arity; n++) { #> + && _parser<#=n#>.TryParse(ref context, out value.Item<#=n#>!) +<#+ } #> + ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + value = default; + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"_parser{n}.Void()", from: 1, separator: ", ")#>); + } + + #endregion + + #region Inner type: VoidSeq<#=arity#>Parser + + /// + /// Represents a specialized parser that runs the specified parsers in sequence and succeeds only if they all succeed. + /// + private sealed class VoidSeq<#=arity#>Parser : Parser + { +<#+ for (var n = 1; n <= arity; n++) { #> + private readonly Parser _parser<#=n#>; +<#+ } #> + + /// + /// Initializes a new instance of the class. + /// + public VoidSeq<#=arity#>Parser(<#=GenerateList(arity, n => $"Parser parser{n}", from: 1, separator: ", ")#>) + { +<#+ for (var n = 1; n <= arity; n++) { #> + _parser<#=n#> = parser<#=n#>; +<#+ } #> + } + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + if (_parser1.TryParse(ref context, out value) +<#+ for (var n = 2; n <= arity; n++) { #> + && _parser<#=n#>.TryParse(ref context, out value) +<#+ } #> ) + { + context.SetMatched(bookmark); + return true; + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion +<#+ } #> diff --git a/src/Ramstack.Parsing/Parser.Set.cs b/src/Ramstack.Parsing/Parser.Set.cs index 34def3e..be58430 100644 --- a/src/Ramstack.Parsing/Parser.Set.cs +++ b/src/Ramstack.Parsing/Parser.Set.cs @@ -1,828 +1,828 @@ -using System.Numerics; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that matches a single character within the specified range. - /// - /// The start of the range. - /// The end of the range. - /// - /// A parser that matches any single character within the specified range. - /// - public static Parser Set(char lo, char hi) - { - Argument.ThrowIfGreaterThan(lo, hi); - - if (lo == hi) - return L(lo); - - if (lo == char.MinValue && hi == char.MaxValue) - return Any; - - var name = CharClassRange.Create(lo, hi).ToPrintable(); - return new RangeParser( - new RangeSearcher(lo, hi), - default) { Name = name }; - } - - /// - /// Creates a parser that matches any single character within the specified range set. - /// For example, use Set("a-z0-9") to match a lowercase ASCII letter or an ASCII digit. - /// - /// - /// The Set method supports a wide range of character classes and escape sequences, including: - /// - /// - /// - /// Unicode general categories:
- /// - \p{name}: any character in Unicode general category - /// (e.g., \p{L} for letters, \p{Nd} for decimal digits).
- /// Where name is a Unicode general category: https://www.unicode.org/reports/tr44/#General_Category_Values - ///
- ///
- /// - /// - /// Negation of Unicode general categories:
- /// - \P{name}: any character not in the specified Unicode general category. - ///
- ///
- /// - /// - /// Shorthand character classes:
- /// - \w: word character
- /// - \W: non-word character
- /// - \s: whitespace character
- /// - \S: non-whitespace character
- /// - \d: decimal digit
- /// - \D: non-decimal digit. - ///
- ///
- /// - /// - /// Character escapes:
- /// - \a: bell character (\u0007)
- /// - \b: backspace (\u0008)
- /// - \t: tab (\u0009)
- /// - \r: carriage return (\u000D)
- /// - \v: vertical tab (\u000B)
- /// - \f: form feed (\u000C)
- /// - \n: new line (\u000A)
- /// - \e: escape (\u001B)
- /// - \unnnn: Unicode character (exactly four hexadecimal digits represented by nnnn).
- ///
- ///
- /// - /// - /// Character ranges: 0-9, a-z, etc. - /// - /// - /// - /// - /// Literal characters: abcdefABCDEF, etc. - /// - /// - ///
- /// - /// The following features are **not supported**: - /// - /// Named Unicode blocks (e.g., \p{Cyrillic}). - /// Negation within ranges (e.g., ^0-9). - /// Exclude-style ranges (e.g., a-z-[a-f]). - /// Anchors (e.g., ^, $, \A, \Z, \z, \G, \b, \B). - /// - ///
- /// A character class without surrounding square brackets, e.g., @"A-Za-z\p{Nd}". - /// - /// A parser that matches any single character within the specified range sets. - /// - public static Parser Set(string set) - { - var @class = CharClassParser.Parse(set); - return Set(@class); - } - - internal static Parser Set(CharClass @class) - { - var name = @class.ToPrintable(); - var categories = @class.UnicodeCategories; - var ranges = @class.Ranges; - - if (ranges.Length == 0) - return new UnicodeCategoryParser(categories) { Name = name }; - - if (ranges.Length == 1) - { - if (ranges[0].IsFullRange) - return Any; - - if (categories.IsEmpty && ranges[0].IsSingleCharacter) - return L(ranges[0].Lo); - - return new RangeParser( - new RangeSearcher(ranges[0].Lo, ranges[0].Hi), - categories) { Name = name }; - } - - if (@class.OverallWidth <= 128) - return new RangeParser>( - new BitVectorSearcher(ranges), - categories) { Name = name }; - - if (@class.OverallWidth <= 256) - return new RangeParser>( - new BitVectorSearcher(ranges), - categories) { Name = name }; - - if (@class.OverallWidth <= 512) - return new RangeParser>( - new BitVectorSearcher(ranges), - categories) { Name = name }; - - var list = new List<(int comparisons, int consumption, Parser parser)>(); - - if (true) - { - var count = @class.SymbolCount; - var width = Avx2.IsSupported - ? Vector256.Count - : Sse41.IsSupported || AdvSimd.IsSupported - ? Vector128.Count - : 0; - - list.Add( - ( - comparisons: width != 0 ? (count + width - 1 & -width) >>> BitOperations.Log2((uint)width) : count, - consumption: width != 0 ? (count + width - 1 & -width) : count, - parser: new RangeParser( - new ContainsSearcher(@class.GetSymbols(simdAligned: true)), - categories) { Name = name } - ) - ); - } - - if (Avx2.IsSupported) - { - var count = ranges.Length + 15 & -16; - list.Add( - ( - comparisons: count >>> 4, - consumption: count * 2, - parser: new RangeParser>( - new SimdRangeSearcher(ranges), - categories) { Name = name } - ) - ); - } - - if (Sse41.IsSupported || AdvSimd.IsSupported) - { - var count = ranges.Length + 7 & -8; - list.Add( - ( - comparisons: count >>> 3, - consumption: count * 2, - parser: new RangeParser>( - new SimdRangeSearcher(ranges), - categories) { Name = name } - ) - ); - } - - if (true) - { - var count = (int)BitOperations.RoundUpToPowerOf2( - (uint)BitOperations.Log2((uint)ranges.Length) - ); - - var overhead = Avx2.IsSupported - ? 3 - : Sse41.IsSupported || AdvSimd.IsSupported - ? 2 - : 0; - - list.Add( - ( - comparisons: count + overhead, - consumption: ranges.Length * 2, - parser: new RangeParser( - new BinaryRangeSearcher(ranges), - categories) { Name = name } - ) - ); - } - - return list - .OrderBy(v => v.comparisons) - .ThenBy(v => v.consumption) - .First() - .parser; - } - - #region Inner type: UnicodeCategoryParser - - /// - /// Represents a parser that attempts to parse a single character by checking - /// whether it belongs to a specified Unicode category. - /// - /// The type of the value produced by the parser. - /// The Unicode categories against which a character is matched. - private sealed class UnicodeCategoryParser(CharClassUnicodeCategory categories) : Parser, ICharClassSupport where T : struct - { - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Source; - var p = context.Position; - - if ((uint)p < (uint)s.Length) - { - var c = s[p]; - if (categories.IsInclude(c)) - { - if (typeof(T) == typeof(char)) - value = (T)(object)c; - else - value = default; - - context.Advance(1); - return true; - } - } - - value = default; - context.ReportExpected(Name); - return false; - } - - /// - public CharClass GetCharClass() => - new CharClass(categories); - - /// - protected internal override Parser ToNamedParser(string? name) => - new UnicodeCategoryParser(categories) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new UnicodeCategoryParser(categories) { Name = Name }; - } - - #endregion - - #region Inner type: RangeParser - - /// - /// Represents a parser that attempts to parse a single character using a character class range searcher. - /// - /// The type of the value produced by the parser. - /// The type of the character class range searcher implementing . - private sealed class RangeParser : Parser, ICharClassSupport where T : struct where TSearcher : struct, ICharClassRangeSearcher - { - private TSearcher _searcher; - private readonly CharClassUnicodeCategory _categories; - - /// - /// Initializes a new instance of the class - /// with the specified searcher and character class categories. - /// - /// The character class range searcher used for parsing. - /// The Unicode character categories against which the character is matched - /// if it is not found in the searcher. - public RangeParser(TSearcher searcher, CharClassUnicodeCategory categories) - { - Debug.Assert(typeof(T) == typeof(char) || typeof(T) == typeof(Unit)); - - _searcher = searcher; - _categories = categories; - } - - /// - public override bool TryParse(ref ParseContext context, out T value) - { - var s = context.Source; - var p = context.Position; - - if ((uint)p < (uint)s.Length) - { - var c = s[p]; - if (_searcher.Contains(c) || _categories.IsInclude(c)) - { - if (typeof(T) == typeof(char)) - value = (T)(object)c; - else - value = default; - - context.Advance(1); - return true; - } - } - - value = default; - context.ReportExpected(Name); - return false; - } - - /// - public CharClass GetCharClass() => - _searcher.GetCharClass(_categories); - - /// - protected internal override Parser ToNamedParser(string? name) => - new RangeParser(_searcher, _categories) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new RangeParser(_searcher, _categories) { Name = Name }; - } - - #endregion - - #region Inner type: RangeSearcher - - /// - /// Represents a searcher that checks whether a character falls within a single range. - /// - private readonly struct RangeSearcher : ICharClassRangeSearcher - { - private readonly uint _start; - private readonly uint _count; - - /// - /// Initializes a new instance of the structure - /// with the specified lower and upper character bounds. - /// - /// The lower bound of the character range. - /// The upper bound of the character range. - public RangeSearcher(char lo, char hi) - { - Debug.Assert(lo <= hi); - - _start = lo; - _count = (uint)(hi - lo + 1); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - ch - _start < _count; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) - { - static int IndexOfAnyExceptCore(ref char s, int length, uint start, uint count) - { - var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); - for (var i = 0; i < span.Length; i++) - if (span[i] - start >= count) - return i; - - return -1; - } - - return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _start, _count); - } - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) - { - var lo = _start; - var hi = lo + _count - 1; - - return new CharClass( - [CharClassRange.Create((char)lo, (char)hi)], - categories); - } - } - - #endregion - - #region Inner type: ContainsSearcher - - /// - /// Represents a searcher that determines whether a character is contained - /// within a predefined set using direct lookup. - /// - /// The string containing the set of characters to search within. - private readonly struct ContainsSearcher(string chars) : ICharClassRangeSearcher - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - chars.Contains(ch); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) - { - static int IndexOfAnyExceptCore(ref char s, int length, string chars) - { - _ = chars.Length; - var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); - - #if NET7_0_OR_GREATER - return span.IndexOfAnyExcept(chars); - #else - for (var i = 0; i < span.Length; i++) - if (!chars.Contains(span[i])) - return i; - - return -1; - #endif - } - - return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, chars); - } - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) => - new CharClass(CharClassRange.Create(chars), categories); - } - - #endregion - - #region Inner type: BitVectorSearcher - - /// - /// Represents a searcher that uses bit vector to efficiently determine - /// whether a character belongs to a specified set of ranges. - /// - /// The underlying storage type for the bit vector, which must be an unmanaged type. - private readonly struct BitVectorSearcher : ICharClassRangeSearcher where TStorage : unmanaged - { - private readonly BitVector _vector; - private readonly int _offset; - - /// - /// Initializes a new instance of the structure - /// with the specified character class ranges. - /// - /// The character class ranges used for searching. - public BitVectorSearcher(CharClassRange[] ranges) - { - var vector = default(BitVector); - var offset = ranges.Length != 0 ? ranges[0].Lo : 0; - - foreach (var range in ranges) - for (var i = range.Lo; i <= range.Hi; i++) - vector.Set(i - offset); - - _vector = vector; - _offset = offset; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - _vector.IsBitSet(ch - _offset); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) - { - static int IndexOfAnyExceptCore(ref char s, int length, in BitVector vector, int offset) - { - var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); - - for (var i = 0; i < span.Length; i++) - if (!vector.IsBitSet(span[i] - offset)) - return i; - - return -1; - - } - - return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, in _vector, _offset); - } - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) => - new CharClass(_vector.ToCharClassRanges(_offset), categories); - } - - #endregion - - #region Inner type: SimdRangeSearcher - - /// - /// Represents a searcher that uses SIMD operations to efficiently determine - /// whether a character falls within a specified set of ranges. - /// - /// The SIMD vector width, which must be an unmanaged type. - private readonly struct SimdRangeSearcher : ICharClassRangeSearcher where TWidth : unmanaged - { - private readonly ushort[] _ranges; - - /// - /// Initializes a new instance of the structure - /// with the specified character class ranges. - /// - /// The character class ranges used for searching. - public SimdRangeSearcher(CharClassRange[] ranges) - { - var result = new ArrayBuilder(); - var stride = Unsafe.SizeOf() / 2; - - for (var i = 0; i < ranges.Length; i += stride) - { - for (var j = i; j < i + stride; j++) result.Add(ranges[(uint)j < (uint)ranges.Length ? j : 0].Lo); - for (var j = i; j < i + stride; j++) result.Add(ranges[(uint)j < (uint)ranges.Length ? j : 0].Hi); - } - - _ranges = result.ToArray(); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - ContainsCore(ch, _ranges); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) - { - static int IndexOfAnyExceptCore(ref char s, int length, ushort[] ranges) - { - var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); - for (var i = 0; i < span.Length; i++) - if (!ContainsCore(span[i], ranges)) - return i; - - return -1; - } - - return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _ranges); - } - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) - { - var ranges = _ranges; - var length = ranges.Length; - var @class = new ArrayBuilder(); - var stride = Unsafe.SizeOf() / 2; - - for (var i = 0; i < length; i += stride * 2) - for (var j = i; j < i + stride; j++) - @class.Add( - CharClassRange.Create( - (char)ranges[j], - (char)ranges[j + stride])); - - return new CharClass(@class.ToArray(), categories); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ContainsCore(char ch, ushort[] ranges) - { - var length = ranges.Length; - - if (Unsafe.SizeOf() == 256/8) - { - // ReSharper disable once RedundantCast - var c = Vector256.Create((ushort)ch); - ref var r = ref MemoryMarshal.GetArrayDataReference(ranges); - - for (; length > 0; r = ref Unsafe.Add(ref r, 32), length -= 32) - { - var x = Unsafe.ReadUnaligned>(ref Unsafe.As(ref r)); - var y = Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref r, 16))); - - var v = Avx2.CompareEqual( - Avx2.Min(Avx2.Max(c, x), y), - c); - - if (!Avx.TestZ(v, v)) - return true; - } - } - else if (Unsafe.SizeOf() == 128/8) - { - // ReSharper disable once RedundantCast - var c = Vector128.Create((ushort)ch); - ref var r = ref MemoryMarshal.GetArrayDataReference(ranges); - - for (; length > 0; r = ref Unsafe.Add(ref r, 16), length -= 16) - { - var x = Unsafe.ReadUnaligned>(ref Unsafe.As(ref r)); - var y = Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref r, 8))); - - if (Sse41.IsSupported) - { - var v = Sse2.CompareEqual( - Sse41.Min(Sse41.Max(c, x), y), - c); - - if (!Sse41.TestZ(v, v)) - return true; - } - else - { - // var v = AdvSimd.CompareEqual( - // AdvSimd.Min(AdvSimd.Max(c, x), y), - // c); - - var v = AdvSimd.And( - AdvSimd.CompareGreaterThanOrEqual(c, x), - AdvSimd.CompareLessThanOrEqual(c, y)); - - if (!v.Equals(Vector128.Zero)) - return true; - } - } - } - else - { - throw new NotSupportedException(); - } - - return false; - } - } - - #endregion - - #region Inner type: BinaryRangeSearcher - - /// - /// Represents a searcher that uses a binary search algorithm to determine - /// whether a character falls within a specified set of ranges. - /// - private readonly struct BinaryRangeSearcher : ICharClassRangeSearcher - { - private readonly (uint lo, uint hi)[] _ranges; - - /// - /// Initializes a new instance of the structure - /// with the specified character class ranges. - /// - /// The character class ranges used for searching. - public BinaryRangeSearcher(CharClassRange[] ranges) - { - var list = new (uint lo, uint hi)[ranges.Length]; - - // Note: The following condition is redundant, but it is included specifically - // to eliminate bounds checks generated by the JIT compiler. - if (list.Length == ranges.Length) - for (var i = 0; i < ranges.Length; i++) - list[i] = ranges[i]; - - _ranges = list; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - ContainsCore(ch, _ranges); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) - { - static int IndexOfAnyExceptCore(ref char s, int length, (uint lo, uint hi)[] ranges) - { - var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); - for (var i = 0; i < span.Length; i++) - if (!ContainsCore(span[i], ranges)) - return i; - - return -1; - } - - return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _ranges); - } - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) - { - var ranges = _ranges; - var @class = new CharClassRange[ranges.Length]; - - // Note: The following condition is redundant, but it is included specifically - // to eliminate bounds checks generated by the JIT compiler. - if (@class.Length == ranges.Length) - for (var i = 0; i < ranges.Length; i++) - @class[i] = CharClassRange.Create((char)ranges[i].lo, (char)ranges[i].hi); - - return new CharClass(@class, categories); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ContainsCore(char ch, (uint lo, uint hi)[] ranges) - { - var lo = 0; - var hi = ranges.Length - 1; - var nc = (uint)ch; - - while (lo <= hi) - { - var mi = (lo + hi) >>> 1; - if ((uint)mi >= (uint)ranges.Length) - break; - - var range = ranges[mi]; - - if (nc < range.lo) - { - hi = mi - 1; - } - else if (nc > range.hi) - { - lo = mi + 1; - } - else - { - return true; - } - } - - return false; - } - } - - #endregion - - #region Inner type: SearchValuesSearcher - - #if NET8_0_OR_GREATER - - /// - /// Represents a searcher that utilizes to efficiently - /// determine whether a character belongs to a predefined set. - /// - /// The set of characters against which searches are performed. - private readonly struct SearchValuesSearcher(System.Buffers.SearchValues values) : ICharClassRangeSearcher - { - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(char ch) => - values.Contains(ch); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOfAnyExcept(ReadOnlySpan s) => - s.IndexOfAnyExcept(values); - - /// - public CharClass GetCharClass(CharClassUnicodeCategory categories) - { - var list = new ArrayBuilder(); - for (var c = 0; c <= 127; c++) - if (values.Contains((char)c)) - list.Add(CharClassRange.Create((char)c)); - - return new CharClass(list.ToArray(), categories); - } - } - - #endif - - #endregion - - #region Inner type: ICharClassRangeSearcher - - /// - /// Provides support for searching character class ranges. - /// - private interface ICharClassRangeSearcher - { - /// - /// Determines whether the specified character is contained within the character class range. - /// - /// The character to search for. - /// - /// if the character is contained within the range; - /// otherwise, . - /// - bool Contains(char ch); - - /// - /// Searches for the first occurrence of any character in the specified span - /// that is not contained within the character class range. - /// - /// The span of characters to search. - /// - /// The zero-based index of the first character in - /// that is not contained within the range; -1 if all characters - /// in are contained within the range. - /// - int IndexOfAnyExcept(ReadOnlySpan s); - - /// - /// Returns a object representing the current instance. - /// - /// The Unicode categories to add to the character classes. - /// - /// A object representing the current instance. - /// - CharClass GetCharClass(CharClassUnicodeCategory categories); - } - - #endregion -} +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that matches a single character within the specified range. + /// + /// The start of the range. + /// The end of the range. + /// + /// A parser that matches any single character within the specified range. + /// + public static Parser Set(char lo, char hi) + { + Argument.ThrowIfGreaterThan(lo, hi); + + if (lo == hi) + return L(lo); + + if (lo == char.MinValue && hi == char.MaxValue) + return Any; + + var name = CharClassRange.Create(lo, hi).ToPrintable(); + return new RangeParser( + new RangeSearcher(lo, hi), + default) { Name = name }; + } + + /// + /// Creates a parser that matches any single character within the specified range set. + /// For example, use Set("a-z0-9") to match a lowercase ASCII letter or an ASCII digit. + /// + /// + /// The Set method supports a wide range of character classes and escape sequences, including: + /// + /// + /// + /// Unicode general categories:
+ /// - \p{name}: any character in Unicode general category + /// (e.g., \p{L} for letters, \p{Nd} for decimal digits).
+ /// Where name is a Unicode general category: https://www.unicode.org/reports/tr44/#General_Category_Values + ///
+ ///
+ /// + /// + /// Negation of Unicode general categories:
+ /// - \P{name}: any character not in the specified Unicode general category. + ///
+ ///
+ /// + /// + /// Shorthand character classes:
+ /// - \w: word character
+ /// - \W: non-word character
+ /// - \s: whitespace character
+ /// - \S: non-whitespace character
+ /// - \d: decimal digit
+ /// - \D: non-decimal digit. + ///
+ ///
+ /// + /// + /// Character escapes:
+ /// - \a: bell character (\u0007)
+ /// - \b: backspace (\u0008)
+ /// - \t: tab (\u0009)
+ /// - \r: carriage return (\u000D)
+ /// - \v: vertical tab (\u000B)
+ /// - \f: form feed (\u000C)
+ /// - \n: new line (\u000A)
+ /// - \e: escape (\u001B)
+ /// - \unnnn: Unicode character (exactly four hexadecimal digits represented by nnnn).
+ ///
+ ///
+ /// + /// + /// Character ranges: 0-9, a-z, etc. + /// + /// + /// + /// + /// Literal characters: abcdefABCDEF, etc. + /// + /// + ///
+ /// + /// The following features are **not supported**: + /// + /// Named Unicode blocks (e.g., \p{Cyrillic}). + /// Negation within ranges (e.g., ^0-9). + /// Exclude-style ranges (e.g., a-z-[a-f]). + /// Anchors (e.g., ^, $, \A, \Z, \z, \G, \b, \B). + /// + ///
+ /// A character class without surrounding square brackets, e.g., @"A-Za-z\p{Nd}". + /// + /// A parser that matches any single character within the specified range sets. + /// + public static Parser Set(string set) + { + var @class = CharClassParser.Parse(set); + return Set(@class); + } + + internal static Parser Set(CharClass @class) + { + var name = @class.ToPrintable(); + var categories = @class.UnicodeCategories; + var ranges = @class.Ranges; + + if (ranges.Length == 0) + return new UnicodeCategoryParser(categories) { Name = name }; + + if (ranges.Length == 1) + { + if (ranges[0].IsFullRange) + return Any; + + if (categories.IsEmpty && ranges[0].IsSingleCharacter) + return L(ranges[0].Lo); + + return new RangeParser( + new RangeSearcher(ranges[0].Lo, ranges[0].Hi), + categories) { Name = name }; + } + + if (@class.OverallWidth <= 128) + return new RangeParser>( + new BitVectorSearcher(ranges), + categories) { Name = name }; + + if (@class.OverallWidth <= 256) + return new RangeParser>( + new BitVectorSearcher(ranges), + categories) { Name = name }; + + if (@class.OverallWidth <= 512) + return new RangeParser>( + new BitVectorSearcher(ranges), + categories) { Name = name }; + + var list = new List<(int comparisons, int consumption, Parser parser)>(); + + if (true) + { + var count = @class.SymbolCount; + var width = Avx2.IsSupported + ? Vector256.Count + : Sse41.IsSupported || AdvSimd.IsSupported + ? Vector128.Count + : 0; + + list.Add( + ( + comparisons: width != 0 ? (count + width - 1 & -width) >>> BitOperations.Log2((uint)width) : count, + consumption: width != 0 ? (count + width - 1 & -width) : count, + parser: new RangeParser( + new ContainsSearcher(@class.GetSymbols(simdAligned: true)), + categories) { Name = name } + ) + ); + } + + if (Avx2.IsSupported) + { + var count = ranges.Length + 15 & -16; + list.Add( + ( + comparisons: count >>> 4, + consumption: count * 2, + parser: new RangeParser>( + new SimdRangeSearcher(ranges), + categories) { Name = name } + ) + ); + } + + if (Sse41.IsSupported || AdvSimd.IsSupported) + { + var count = ranges.Length + 7 & -8; + list.Add( + ( + comparisons: count >>> 3, + consumption: count * 2, + parser: new RangeParser>( + new SimdRangeSearcher(ranges), + categories) { Name = name } + ) + ); + } + + if (true) + { + var count = (int)BitOperations.RoundUpToPowerOf2( + (uint)BitOperations.Log2((uint)ranges.Length) + ); + + var overhead = Avx2.IsSupported + ? 3 + : Sse41.IsSupported || AdvSimd.IsSupported + ? 2 + : 0; + + list.Add( + ( + comparisons: count + overhead, + consumption: ranges.Length * 2, + parser: new RangeParser( + new BinaryRangeSearcher(ranges), + categories) { Name = name } + ) + ); + } + + return list + .OrderBy(v => v.comparisons) + .ThenBy(v => v.consumption) + .First() + .parser; + } + + #region Inner type: UnicodeCategoryParser + + /// + /// Represents a parser that attempts to parse a single character by checking + /// whether it belongs to a specified Unicode category. + /// + /// The type of the value produced by the parser. + /// The Unicode categories against which a character is matched. + private sealed class UnicodeCategoryParser(CharClassUnicodeCategory categories) : Parser, ICharClassSupport where T : struct + { + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Source; + var p = context.Position; + + if ((uint)p < (uint)s.Length) + { + var c = s[p]; + if (categories.IsInclude(c)) + { + if (typeof(T) == typeof(char)) + value = (T)(object)c; + else + value = default; + + context.Advance(1); + return true; + } + } + + value = default; + context.ReportExpected(Name); + return false; + } + + /// + public CharClass GetCharClass() => + new CharClass(categories); + + /// + protected internal override Parser ToNamedParser(string? name) => + new UnicodeCategoryParser(categories) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new UnicodeCategoryParser(categories) { Name = Name }; + } + + #endregion + + #region Inner type: RangeParser + + /// + /// Represents a parser that attempts to parse a single character using a character class range searcher. + /// + /// The type of the value produced by the parser. + /// The type of the character class range searcher implementing . + private sealed class RangeParser : Parser, ICharClassSupport where T : struct where TSearcher : struct, ICharClassRangeSearcher + { + private TSearcher _searcher; + private readonly CharClassUnicodeCategory _categories; + + /// + /// Initializes a new instance of the class + /// with the specified searcher and character class categories. + /// + /// The character class range searcher used for parsing. + /// The Unicode character categories against which the character is matched + /// if it is not found in the searcher. + public RangeParser(TSearcher searcher, CharClassUnicodeCategory categories) + { + Debug.Assert(typeof(T) == typeof(char) || typeof(T) == typeof(Unit)); + + _searcher = searcher; + _categories = categories; + } + + /// + public override bool TryParse(ref ParseContext context, out T value) + { + var s = context.Source; + var p = context.Position; + + if ((uint)p < (uint)s.Length) + { + var c = s[p]; + if (_searcher.Contains(c) || _categories.IsInclude(c)) + { + if (typeof(T) == typeof(char)) + value = (T)(object)c; + else + value = default; + + context.Advance(1); + return true; + } + } + + value = default; + context.ReportExpected(Name); + return false; + } + + /// + public CharClass GetCharClass() => + _searcher.GetCharClass(_categories); + + /// + protected internal override Parser ToNamedParser(string? name) => + new RangeParser(_searcher, _categories) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new RangeParser(_searcher, _categories) { Name = Name }; + } + + #endregion + + #region Inner type: RangeSearcher + + /// + /// Represents a searcher that checks whether a character falls within a single range. + /// + private readonly struct RangeSearcher : ICharClassRangeSearcher + { + private readonly uint _start; + private readonly uint _count; + + /// + /// Initializes a new instance of the structure + /// with the specified lower and upper character bounds. + /// + /// The lower bound of the character range. + /// The upper bound of the character range. + public RangeSearcher(char lo, char hi) + { + Debug.Assert(lo <= hi); + + _start = lo; + _count = (uint)(hi - lo + 1); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + ch - _start < _count; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) + { + static int IndexOfAnyExceptCore(ref char s, int length, uint start, uint count) + { + var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); + for (var i = 0; i < span.Length; i++) + if (span[i] - start >= count) + return i; + + return -1; + } + + return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _start, _count); + } + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) + { + var lo = _start; + var hi = lo + _count - 1; + + return new CharClass( + [CharClassRange.Create((char)lo, (char)hi)], + categories); + } + } + + #endregion + + #region Inner type: ContainsSearcher + + /// + /// Represents a searcher that determines whether a character is contained + /// within a predefined set using direct lookup. + /// + /// The string containing the set of characters to search within. + private readonly struct ContainsSearcher(string chars) : ICharClassRangeSearcher + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + chars.Contains(ch); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) + { + static int IndexOfAnyExceptCore(ref char s, int length, string chars) + { + _ = chars.Length; + var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); + + #if NET7_0_OR_GREATER + return span.IndexOfAnyExcept(chars); + #else + for (var i = 0; i < span.Length; i++) + if (!chars.Contains(span[i])) + return i; + + return -1; + #endif + } + + return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, chars); + } + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) => + new CharClass(CharClassRange.Create(chars), categories); + } + + #endregion + + #region Inner type: BitVectorSearcher + + /// + /// Represents a searcher that uses bit vector to efficiently determine + /// whether a character belongs to a specified set of ranges. + /// + /// The underlying storage type for the bit vector, which must be an unmanaged type. + private readonly struct BitVectorSearcher : ICharClassRangeSearcher where TStorage : unmanaged + { + private readonly BitVector _vector; + private readonly int _offset; + + /// + /// Initializes a new instance of the structure + /// with the specified character class ranges. + /// + /// The character class ranges used for searching. + public BitVectorSearcher(CharClassRange[] ranges) + { + var vector = default(BitVector); + var offset = ranges.Length != 0 ? ranges[0].Lo : 0; + + foreach (var range in ranges) + for (var i = range.Lo; i <= range.Hi; i++) + vector.Set(i - offset); + + _vector = vector; + _offset = offset; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + _vector.IsBitSet(ch - _offset); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) + { + static int IndexOfAnyExceptCore(ref char s, int length, in BitVector vector, int offset) + { + var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); + + for (var i = 0; i < span.Length; i++) + if (!vector.IsBitSet(span[i] - offset)) + return i; + + return -1; + + } + + return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, in _vector, _offset); + } + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) => + new CharClass(_vector.ToCharClassRanges(_offset), categories); + } + + #endregion + + #region Inner type: SimdRangeSearcher + + /// + /// Represents a searcher that uses SIMD operations to efficiently determine + /// whether a character falls within a specified set of ranges. + /// + /// The SIMD vector width, which must be an unmanaged type. + private readonly struct SimdRangeSearcher : ICharClassRangeSearcher where TWidth : unmanaged + { + private readonly ushort[] _ranges; + + /// + /// Initializes a new instance of the structure + /// with the specified character class ranges. + /// + /// The character class ranges used for searching. + public SimdRangeSearcher(CharClassRange[] ranges) + { + var result = new ArrayBuilder(); + var stride = Unsafe.SizeOf() / 2; + + for (var i = 0; i < ranges.Length; i += stride) + { + for (var j = i; j < i + stride; j++) result.Add(ranges[(uint)j < (uint)ranges.Length ? j : 0].Lo); + for (var j = i; j < i + stride; j++) result.Add(ranges[(uint)j < (uint)ranges.Length ? j : 0].Hi); + } + + _ranges = result.ToArray(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + ContainsCore(ch, _ranges); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) + { + static int IndexOfAnyExceptCore(ref char s, int length, ushort[] ranges) + { + var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); + for (var i = 0; i < span.Length; i++) + if (!ContainsCore(span[i], ranges)) + return i; + + return -1; + } + + return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _ranges); + } + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) + { + var ranges = _ranges; + var length = ranges.Length; + var @class = new ArrayBuilder(); + var stride = Unsafe.SizeOf() / 2; + + for (var i = 0; i < length; i += stride * 2) + for (var j = i; j < i + stride; j++) + @class.Add( + CharClassRange.Create( + (char)ranges[j], + (char)ranges[j + stride])); + + return new CharClass(@class.ToArray(), categories); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ContainsCore(char ch, ushort[] ranges) + { + var length = ranges.Length; + + if (Unsafe.SizeOf() == 256/8) + { + // ReSharper disable once RedundantCast + var c = Vector256.Create((ushort)ch); + ref var r = ref MemoryMarshal.GetArrayDataReference(ranges); + + for (; length > 0; r = ref Unsafe.Add(ref r, 32), length -= 32) + { + var x = Unsafe.ReadUnaligned>(ref Unsafe.As(ref r)); + var y = Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref r, 16))); + + var v = Avx2.CompareEqual( + Avx2.Min(Avx2.Max(c, x), y), + c); + + if (!Avx.TestZ(v, v)) + return true; + } + } + else if (Unsafe.SizeOf() == 128/8) + { + // ReSharper disable once RedundantCast + var c = Vector128.Create((ushort)ch); + ref var r = ref MemoryMarshal.GetArrayDataReference(ranges); + + for (; length > 0; r = ref Unsafe.Add(ref r, 16), length -= 16) + { + var x = Unsafe.ReadUnaligned>(ref Unsafe.As(ref r)); + var y = Unsafe.ReadUnaligned>(ref Unsafe.As(ref Unsafe.Add(ref r, 8))); + + if (Sse41.IsSupported) + { + var v = Sse2.CompareEqual( + Sse41.Min(Sse41.Max(c, x), y), + c); + + if (!Sse41.TestZ(v, v)) + return true; + } + else + { + // var v = AdvSimd.CompareEqual( + // AdvSimd.Min(AdvSimd.Max(c, x), y), + // c); + + var v = AdvSimd.And( + AdvSimd.CompareGreaterThanOrEqual(c, x), + AdvSimd.CompareLessThanOrEqual(c, y)); + + if (!v.Equals(Vector128.Zero)) + return true; + } + } + } + else + { + throw new NotSupportedException(); + } + + return false; + } + } + + #endregion + + #region Inner type: BinaryRangeSearcher + + /// + /// Represents a searcher that uses a binary search algorithm to determine + /// whether a character falls within a specified set of ranges. + /// + private readonly struct BinaryRangeSearcher : ICharClassRangeSearcher + { + private readonly (uint lo, uint hi)[] _ranges; + + /// + /// Initializes a new instance of the structure + /// with the specified character class ranges. + /// + /// The character class ranges used for searching. + public BinaryRangeSearcher(CharClassRange[] ranges) + { + var list = new (uint lo, uint hi)[ranges.Length]; + + // Note: The following condition is redundant, but it is included specifically + // to eliminate bounds checks generated by the JIT compiler. + if (list.Length == ranges.Length) + for (var i = 0; i < ranges.Length; i++) + list[i] = ranges[i]; + + _ranges = list; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + ContainsCore(ch, _ranges); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) + { + static int IndexOfAnyExceptCore(ref char s, int length, (uint lo, uint hi)[] ranges) + { + var span = MemoryMarshal.CreateReadOnlySpan(ref s, length); + for (var i = 0; i < span.Length; i++) + if (!ContainsCore(span[i], ranges)) + return i; + + return -1; + } + + return IndexOfAnyExceptCore(ref MemoryMarshal.GetReference(s), s.Length, _ranges); + } + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) + { + var ranges = _ranges; + var @class = new CharClassRange[ranges.Length]; + + // Note: The following condition is redundant, but it is included specifically + // to eliminate bounds checks generated by the JIT compiler. + if (@class.Length == ranges.Length) + for (var i = 0; i < ranges.Length; i++) + @class[i] = CharClassRange.Create((char)ranges[i].lo, (char)ranges[i].hi); + + return new CharClass(@class, categories); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ContainsCore(char ch, (uint lo, uint hi)[] ranges) + { + var lo = 0; + var hi = ranges.Length - 1; + var nc = (uint)ch; + + while (lo <= hi) + { + var mi = (lo + hi) >>> 1; + if ((uint)mi >= (uint)ranges.Length) + break; + + var range = ranges[mi]; + + if (nc < range.lo) + { + hi = mi - 1; + } + else if (nc > range.hi) + { + lo = mi + 1; + } + else + { + return true; + } + } + + return false; + } + } + + #endregion + + #region Inner type: SearchValuesSearcher + + #if NET8_0_OR_GREATER + + /// + /// Represents a searcher that utilizes to efficiently + /// determine whether a character belongs to a predefined set. + /// + /// The set of characters against which searches are performed. + private readonly struct SearchValuesSearcher(System.Buffers.SearchValues values) : ICharClassRangeSearcher + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char ch) => + values.Contains(ch); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOfAnyExcept(ReadOnlySpan s) => + s.IndexOfAnyExcept(values); + + /// + public CharClass GetCharClass(CharClassUnicodeCategory categories) + { + var list = new ArrayBuilder(); + for (var c = 0; c <= 127; c++) + if (values.Contains((char)c)) + list.Add(CharClassRange.Create((char)c)); + + return new CharClass(list.ToArray(), categories); + } + } + + #endif + + #endregion + + #region Inner type: ICharClassRangeSearcher + + /// + /// Provides support for searching character class ranges. + /// + private interface ICharClassRangeSearcher + { + /// + /// Determines whether the specified character is contained within the character class range. + /// + /// The character to search for. + /// + /// if the character is contained within the range; + /// otherwise, . + /// + bool Contains(char ch); + + /// + /// Searches for the first occurrence of any character in the specified span + /// that is not contained within the character class range. + /// + /// The span of characters to search. + /// + /// The zero-based index of the first character in + /// that is not contained within the range; -1 if all characters + /// in are contained within the range. + /// + int IndexOfAnyExcept(ReadOnlySpan s); + + /// + /// Returns a object representing the current instance. + /// + /// The Unicode categories to add to the character classes. + /// + /// A object representing the current instance. + /// + CharClass GetCharClass(CharClassUnicodeCategory categories); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.String.cs b/src/Ramstack.Parsing/Parser.String.cs index 518b6ff..9aa105a 100644 --- a/src/Ramstack.Parsing/Parser.String.cs +++ b/src/Ramstack.Parsing/Parser.String.cs @@ -1,389 +1,389 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that attempts to match the specified literal. - /// - /// The text to match. - /// The string comparison type to use for matching the literal. - /// Defaults to . - /// - /// A parser that attempts to match the specified literal using the provided comparison type. - /// - public static Parser L(string literal, StringComparison comparison = StringComparison.Ordinal) - { - Argument.ThrowIfNullOrEmpty(literal); - - var expected = literal.ToPrintable(); - return comparison == StringComparison.Ordinal - ? new OrdinalStringParser(literal) { Name = expected } - : new StringParser(literal, comparison) { Name = expected }; - } - - /// - /// Creates a parser that matches one of the specified literals. - /// - /// The first text to match. - /// The second text to match. - /// The string comparison type to use for matching the literal. - /// Defaults to . - /// - /// A parser that parses one of the specified literals using the provided comparison type. - /// - public static Parser OneOf(string literal1, string literal2, StringComparison comparison = StringComparison.Ordinal) => - OneOf([literal1, literal2], comparison); - - /// - /// Creates a parser that matches one of the specified literals. - /// - /// The first text to match. - /// The second text to match. - /// The third text to match. - /// The string comparison type to use for matching the literal. - /// Defaults to . - /// - /// A parser that parses one of the specified literals using the provided comparison type. - /// - public static Parser OneOf(string literal1, string literal2, string literal3, StringComparison comparison = StringComparison.Ordinal) => - OneOf([literal1, literal2, literal3], comparison); - - /// - /// Creates a parser that matches one of the specified literals. - /// - /// The first text to match. - /// The second text to match. - /// The third text to match. - /// The fourth text to match. - /// The string comparison type to use for matching the literal. - /// Defaults to . - /// - /// A parser that parses one of the specified literals using the provided comparison type. - /// - public static Parser OneOf(string literal1, string literal2, string literal3, string literal4, StringComparison comparison = StringComparison.Ordinal) => - OneOf([literal1, literal2, literal3, literal4], comparison); - - /// - /// Creates a parser that matches one of the specified literals. - /// - /// The array of literals to match. - /// The string comparison type to use for matching the literal. - /// Defaults to . - /// - /// A parser that parses one of the specified literals using the provided comparison type. - /// - public static Parser OneOf(string[] literals, StringComparison comparison = StringComparison.Ordinal) - { - Argument.ThrowIfNullOrEmpty(literals); - - if (literals.Length == 1) - return L(literals[0], comparison); - - var expected = literals.Select(l => l.ToPrintable()).ToArray(); - var comparer = StringComparer.FromComparison(comparison); - - var dictionary = new CharMap( - Permute(literals, comparison) - .GroupBy(l => l.Key) - .ToDictionary( - g => g.Key, - g => g - .Select(p => p.Value) - .OrderDescending(comparer) - .Distinct(comparer) - .ToArray())); - - return comparison switch - { - StringComparison.CurrentCulture => new StringDictionaryParser(in dictionary, CultureInfo.CurrentCulture, CompareOptions.None, expected), - StringComparison.CurrentCultureIgnoreCase => new StringDictionaryParser(in dictionary, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase, expected), - StringComparison.InvariantCulture => new StringDictionaryParser(in dictionary, CultureInfo.InvariantCulture, CompareOptions.None, expected), - StringComparison.InvariantCultureIgnoreCase => new StringDictionaryParser(in dictionary, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase, expected), - StringComparison.Ordinal => new OrdinalStringDictionaryParser(in dictionary, expected), - _ => new OrdinalStringDictionaryParser(in dictionary, expected) - }; - } - - #region Inner type: OrdinalStringParser - - /// - /// Represents a parser that attempts to match the specified literal using ordinal string comparison. - /// This parser is specialized for performance with ordinal comparison. - /// - /// The type of the value produced by the parser. - private sealed class OrdinalStringParser : Parser - { - private readonly string _literal; - - /// - /// Initializes a new instance of the class. - /// - /// The string to match. - public OrdinalStringParser(string literal) => - _literal = literal; - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - if (context.Remaining.StartsWith(_literal)) - { - if (typeof(T) != typeof(Unit)) - value = ((T)(object)_literal)!; - else - value = default!; - - context.Advance(_literal.Length); - return true; - } - - value = default; - context.ReportExpected(Name); - - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new OrdinalStringParser(_literal) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new OrdinalStringParser(_literal) { Name = Name }; - } - - #endregion - - #region Inner type: StringParser - - /// - /// Represents a parser that attempts to match a specified literal using a specified string comparison type. - /// - /// The type of the value produced by the parser. - private sealed class StringParser : Parser - { - private readonly string _literal; - private readonly StringComparison _comparison; - - /// - /// Initializes a new instance of the class. - /// - /// The string to match. - /// The string comparison type to use for matching the literal. - public StringParser(string literal, StringComparison comparison) => - (_literal, _comparison) = (literal, comparison); - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - if (context.Remaining.StartsWith(_literal, _comparison)) - { - if (typeof(T) != typeof(Unit)) - value = ((T)(object)_literal)!; - else - value = default!; - - context.Advance(_literal.Length); - return true; - } - - value = default; - context.ReportExpected(Name); - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new StringParser(_literal, _comparison) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new StringParser(_literal, _comparison) { Name = Name }; - } - - #endregion - - #region Inner type: OrdinalStringDictionaryParser - - /// - /// Represents a parser that attempts to match one of the specified literals. - /// - /// The type of the value produced by the parser. - /// The compare options used for matching strings. - private sealed class OrdinalStringDictionaryParser : Parser - { - private readonly CharMap _literals; - private readonly string[] _expected; - - /// - /// Initializes a new instance of the class. - /// - /// A dictionary mapping initial characters to arrays of string literals expected at the beginning of the source. - /// An array of error messages describing expected literals, used when parsing fails. - public OrdinalStringDictionaryParser(in CharMap literals, string[] expected) - { - _literals = literals; - _expected = expected; - } - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var s = context.Remaining; - - if (s.Length != 0 && _literals[s[0]] is {} literals) - { - foreach (var literal in literals) - { - _ = literal.Length; - - bool result; - if (typeof(TCompareOptions) == typeof(CompareOptionsIgnoreCase)) - result = s.StartsWith(literal, StringComparison.OrdinalIgnoreCase); - else - result = s.StartsWith(literal); - - if (result) - { - if (typeof(T) != typeof(Unit)) - value = (T)(object)literal; - else - value = default!; - - context.Advance(literal.Length); - return true; - } - } - } - - switch (Name) - { - case not null: - context.ReportExpected(Name); - break; - default: - context.ReportExpected(_expected); - break; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new OrdinalStringDictionaryParser(in _literals, _expected) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new OrdinalStringDictionaryParser(in _literals, _expected) { Name = Name }; - } - - #endregion - - #region Inner type: StringDictionaryParser - - /// - /// Represents a parser that attempts to match one of the specified literals based on specific cultural and comparison options. - /// - /// The type of the value produced by the parser. - private sealed class StringDictionaryParser : Parser - { - private readonly CharMap _literals; - private readonly CultureInfo _culture; - private readonly CompareInfo _comparer; - private readonly string[] _expected; - private readonly CompareOptions _options; - - /// - /// Initializes a new instance of the class. - /// - /// A dictionary mapping initial characters to arrays of string literals expected at the beginning of the source. - /// The culture to use for comparisons, which affects how strings are compared and ordered. - /// An optional combination of enumeration values to use during the match. - /// An array of error messages describing expected literals, used when parsing fails. - public StringDictionaryParser(in CharMap literals, CultureInfo culture, CompareOptions options, string[] expected) - { - _literals = literals; - _culture = culture; - _comparer = culture.CompareInfo; - _expected = expected; - _options = options; - } - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var s = context.Remaining; - - if (s.Length != 0 && _literals[s[0]] is {} literals) - { - foreach (var literal in literals) - { - _ = literal.Length; - - if (_comparer.IsPrefix(s, literal, _options)) - { - if (typeof(T) != typeof(Unit)) - value = (T)(object)literal; - else - value = default!; - - context.Advance(literal.Length); - return true; - } - } - } - - switch (Name) - { - case not null: - context.ReportExpected(Name); - break; - default: - context.ReportExpected(_expected); - break; - } - - value = default; - return false; - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new StringDictionaryParser(in _literals, _culture, _options, _expected) { Name = name }; - - /// - protected internal override Parser ToVoidParser() => - new StringDictionaryParser(in _literals, _culture, _options, _expected) { Name = Name }; - } - - #endregion - - private static IEnumerable<(char Key, string Value)> Permute(string[] literals, StringComparison comparison) - { - var list = literals.Select(s => (Key: s[0], Value: s)).ToArray(); - - if (comparison is StringComparison.Ordinal - or StringComparison.InvariantCulture - or StringComparison.CurrentCulture) - return list.Distinct(); - - var ci = comparison == StringComparison.CurrentCultureIgnoreCase - ? CultureInfo.CurrentCulture - : CultureInfo.InvariantCulture; - - return list - .Concat(list.Select(s => s with { Key = ci.TextInfo.ToUpper(s.Key) })) - .Concat(list.Select(s => s with { Key = ci.TextInfo.ToLower(s.Key) })); - } - - /// - /// A marker struct used to indicate that string comparisons should be case-sensitive. - /// - private struct CompareOptionsNone; - - /// - /// A marker struct used to indicate that string comparisons should be case-insensitive. - /// - private struct CompareOptionsIgnoreCase; -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that attempts to match the specified literal. + /// + /// The text to match. + /// The string comparison type to use for matching the literal. + /// Defaults to . + /// + /// A parser that attempts to match the specified literal using the provided comparison type. + /// + public static Parser L(string literal, StringComparison comparison = StringComparison.Ordinal) + { + Argument.ThrowIfNullOrEmpty(literal); + + var expected = literal.ToPrintable(); + return comparison == StringComparison.Ordinal + ? new OrdinalStringParser(literal) { Name = expected } + : new StringParser(literal, comparison) { Name = expected }; + } + + /// + /// Creates a parser that matches one of the specified literals. + /// + /// The first text to match. + /// The second text to match. + /// The string comparison type to use for matching the literal. + /// Defaults to . + /// + /// A parser that parses one of the specified literals using the provided comparison type. + /// + public static Parser OneOf(string literal1, string literal2, StringComparison comparison = StringComparison.Ordinal) => + OneOf([literal1, literal2], comparison); + + /// + /// Creates a parser that matches one of the specified literals. + /// + /// The first text to match. + /// The second text to match. + /// The third text to match. + /// The string comparison type to use for matching the literal. + /// Defaults to . + /// + /// A parser that parses one of the specified literals using the provided comparison type. + /// + public static Parser OneOf(string literal1, string literal2, string literal3, StringComparison comparison = StringComparison.Ordinal) => + OneOf([literal1, literal2, literal3], comparison); + + /// + /// Creates a parser that matches one of the specified literals. + /// + /// The first text to match. + /// The second text to match. + /// The third text to match. + /// The fourth text to match. + /// The string comparison type to use for matching the literal. + /// Defaults to . + /// + /// A parser that parses one of the specified literals using the provided comparison type. + /// + public static Parser OneOf(string literal1, string literal2, string literal3, string literal4, StringComparison comparison = StringComparison.Ordinal) => + OneOf([literal1, literal2, literal3, literal4], comparison); + + /// + /// Creates a parser that matches one of the specified literals. + /// + /// The array of literals to match. + /// The string comparison type to use for matching the literal. + /// Defaults to . + /// + /// A parser that parses one of the specified literals using the provided comparison type. + /// + public static Parser OneOf(string[] literals, StringComparison comparison = StringComparison.Ordinal) + { + Argument.ThrowIfNullOrEmpty(literals); + + if (literals.Length == 1) + return L(literals[0], comparison); + + var expected = literals.Select(l => l.ToPrintable()).ToArray(); + var comparer = StringComparer.FromComparison(comparison); + + var dictionary = new CharMap( + Permute(literals, comparison) + .GroupBy(l => l.Key) + .ToDictionary( + g => g.Key, + g => g + .Select(p => p.Value) + .OrderDescending(comparer) + .Distinct(comparer) + .ToArray())); + + return comparison switch + { + StringComparison.CurrentCulture => new StringDictionaryParser(in dictionary, CultureInfo.CurrentCulture, CompareOptions.None, expected), + StringComparison.CurrentCultureIgnoreCase => new StringDictionaryParser(in dictionary, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase, expected), + StringComparison.InvariantCulture => new StringDictionaryParser(in dictionary, CultureInfo.InvariantCulture, CompareOptions.None, expected), + StringComparison.InvariantCultureIgnoreCase => new StringDictionaryParser(in dictionary, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase, expected), + StringComparison.Ordinal => new OrdinalStringDictionaryParser(in dictionary, expected), + _ => new OrdinalStringDictionaryParser(in dictionary, expected) + }; + } + + #region Inner type: OrdinalStringParser + + /// + /// Represents a parser that attempts to match the specified literal using ordinal string comparison. + /// This parser is specialized for performance with ordinal comparison. + /// + /// The type of the value produced by the parser. + private sealed class OrdinalStringParser : Parser + { + private readonly string _literal; + + /// + /// Initializes a new instance of the class. + /// + /// The string to match. + public OrdinalStringParser(string literal) => + _literal = literal; + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + if (context.Remaining.StartsWith(_literal)) + { + if (typeof(T) != typeof(Unit)) + value = ((T)(object)_literal)!; + else + value = default!; + + context.Advance(_literal.Length); + return true; + } + + value = default; + context.ReportExpected(Name); + + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new OrdinalStringParser(_literal) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new OrdinalStringParser(_literal) { Name = Name }; + } + + #endregion + + #region Inner type: StringParser + + /// + /// Represents a parser that attempts to match a specified literal using a specified string comparison type. + /// + /// The type of the value produced by the parser. + private sealed class StringParser : Parser + { + private readonly string _literal; + private readonly StringComparison _comparison; + + /// + /// Initializes a new instance of the class. + /// + /// The string to match. + /// The string comparison type to use for matching the literal. + public StringParser(string literal, StringComparison comparison) => + (_literal, _comparison) = (literal, comparison); + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + if (context.Remaining.StartsWith(_literal, _comparison)) + { + if (typeof(T) != typeof(Unit)) + value = ((T)(object)_literal)!; + else + value = default!; + + context.Advance(_literal.Length); + return true; + } + + value = default; + context.ReportExpected(Name); + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new StringParser(_literal, _comparison) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new StringParser(_literal, _comparison) { Name = Name }; + } + + #endregion + + #region Inner type: OrdinalStringDictionaryParser + + /// + /// Represents a parser that attempts to match one of the specified literals. + /// + /// The type of the value produced by the parser. + /// The compare options used for matching strings. + private sealed class OrdinalStringDictionaryParser : Parser + { + private readonly CharMap _literals; + private readonly string[] _expected; + + /// + /// Initializes a new instance of the class. + /// + /// A dictionary mapping initial characters to arrays of string literals expected at the beginning of the source. + /// An array of error messages describing expected literals, used when parsing fails. + public OrdinalStringDictionaryParser(in CharMap literals, string[] expected) + { + _literals = literals; + _expected = expected; + } + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var s = context.Remaining; + + if (s.Length != 0 && _literals[s[0]] is {} literals) + { + foreach (var literal in literals) + { + _ = literal.Length; + + bool result; + if (typeof(TCompareOptions) == typeof(CompareOptionsIgnoreCase)) + result = s.StartsWith(literal, StringComparison.OrdinalIgnoreCase); + else + result = s.StartsWith(literal); + + if (result) + { + if (typeof(T) != typeof(Unit)) + value = (T)(object)literal; + else + value = default!; + + context.Advance(literal.Length); + return true; + } + } + } + + switch (Name) + { + case not null: + context.ReportExpected(Name); + break; + default: + context.ReportExpected(_expected); + break; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new OrdinalStringDictionaryParser(in _literals, _expected) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new OrdinalStringDictionaryParser(in _literals, _expected) { Name = Name }; + } + + #endregion + + #region Inner type: StringDictionaryParser + + /// + /// Represents a parser that attempts to match one of the specified literals based on specific cultural and comparison options. + /// + /// The type of the value produced by the parser. + private sealed class StringDictionaryParser : Parser + { + private readonly CharMap _literals; + private readonly CultureInfo _culture; + private readonly CompareInfo _comparer; + private readonly string[] _expected; + private readonly CompareOptions _options; + + /// + /// Initializes a new instance of the class. + /// + /// A dictionary mapping initial characters to arrays of string literals expected at the beginning of the source. + /// The culture to use for comparisons, which affects how strings are compared and ordered. + /// An optional combination of enumeration values to use during the match. + /// An array of error messages describing expected literals, used when parsing fails. + public StringDictionaryParser(in CharMap literals, CultureInfo culture, CompareOptions options, string[] expected) + { + _literals = literals; + _culture = culture; + _comparer = culture.CompareInfo; + _expected = expected; + _options = options; + } + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var s = context.Remaining; + + if (s.Length != 0 && _literals[s[0]] is {} literals) + { + foreach (var literal in literals) + { + _ = literal.Length; + + if (_comparer.IsPrefix(s, literal, _options)) + { + if (typeof(T) != typeof(Unit)) + value = (T)(object)literal; + else + value = default!; + + context.Advance(literal.Length); + return true; + } + } + } + + switch (Name) + { + case not null: + context.ReportExpected(Name); + break; + default: + context.ReportExpected(_expected); + break; + } + + value = default; + return false; + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new StringDictionaryParser(in _literals, _culture, _options, _expected) { Name = name }; + + /// + protected internal override Parser ToVoidParser() => + new StringDictionaryParser(in _literals, _culture, _options, _expected) { Name = Name }; + } + + #endregion + + private static IEnumerable<(char Key, string Value)> Permute(string[] literals, StringComparison comparison) + { + var list = literals.Select(s => (Key: s[0], Value: s)).ToArray(); + + if (comparison is StringComparison.Ordinal + or StringComparison.InvariantCulture + or StringComparison.CurrentCulture) + return list.Distinct(); + + var ci = comparison == StringComparison.CurrentCultureIgnoreCase + ? CultureInfo.CurrentCulture + : CultureInfo.InvariantCulture; + + return list + .Concat(list.Select(s => s with { Key = ci.TextInfo.ToUpper(s.Key) })) + .Concat(list.Select(s => s with { Key = ci.TextInfo.ToLower(s.Key) })); + } + + /// + /// A marker struct used to indicate that string comparisons should be case-sensitive. + /// + private struct CompareOptionsNone; + + /// + /// A marker struct used to indicate that string comparisons should be case-insensitive. + /// + private struct CompareOptionsIgnoreCase; +} diff --git a/src/Ramstack.Parsing/Parser.Text.cs b/src/Ramstack.Parsing/Parser.Text.cs index 2517a30..fd7071e 100644 --- a/src/Ramstack.Parsing/Parser.Text.cs +++ b/src/Ramstack.Parsing/Parser.Text.cs @@ -1,15 +1,15 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that returns the string representation of the matched segment (ignoring the parsed value). - /// - /// The type of the value produced by the initial parser. - /// The parser instance used to match the input. - /// - /// A parser that returns the string representation of the matched segment. - /// - public static Parser Text(this Parser parser) => - parser.Map(m => m.ToString()); -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that returns the string representation of the matched segment (ignoring the parsed value). + /// + /// The type of the value produced by the initial parser. + /// The parser instance used to match the input. + /// + /// A parser that returns the string representation of the matched segment. + /// + public static Parser Text(this Parser parser) => + parser.Map(m => m.ToString()); +} diff --git a/src/Ramstack.Parsing/Parser.Then.cs b/src/Ramstack.Parsing/Parser.Then.cs index 59f9ae4..b3e8fc0 100644 --- a/src/Ramstack.Parsing/Parser.Then.cs +++ b/src/Ramstack.Parsing/Parser.Then.cs @@ -1,51 +1,51 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that sequentially applies the current parser and a specified second parser, - /// returning the result of the second parser while discarding the result of the first. - /// - /// The type of the value produced by the first parser, which is ignored. - /// The type of the value produced by the second parser. - /// The initial parser. - /// The subsequent parser whose result is returned. - /// - /// A parser that sequentially applies the current parser and a specified second parser, - /// returning the result of the second parser while discarding the result of the first. - /// - public static Parser Then(this Parser parser, Parser then) => - new ThenParser(parser.Void(), then); - - #region Inner type: ThenParser - - /// - /// Represents a parser that sequentially applies an initial parser and a specified second parser, - /// returning the result of the second parser while discarding the result of the first. - /// - /// The type of the value produced by the second parser. - /// The initial parser. - /// The subsequent parser whose result is returned. - private sealed class ThenParser(Parser parser, Parser then) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var position = context.BookmarkPosition(); - - if (parser.TryParse(ref context, out _) && then.TryParse(ref context, out value)) - return true; - - value = default; - context.RestorePosition(position); - - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new ThenParser(parser, then.Void()); - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that sequentially applies the current parser and a specified second parser, + /// returning the result of the second parser while discarding the result of the first. + /// + /// The type of the value produced by the first parser, which is ignored. + /// The type of the value produced by the second parser. + /// The initial parser. + /// The subsequent parser whose result is returned. + /// + /// A parser that sequentially applies the current parser and a specified second parser, + /// returning the result of the second parser while discarding the result of the first. + /// + public static Parser Then(this Parser parser, Parser then) => + new ThenParser(parser.Void(), then); + + #region Inner type: ThenParser + + /// + /// Represents a parser that sequentially applies an initial parser and a specified second parser, + /// returning the result of the second parser while discarding the result of the first. + /// + /// The type of the value produced by the second parser. + /// The initial parser. + /// The subsequent parser whose result is returned. + private sealed class ThenParser(Parser parser, Parser then) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var position = context.BookmarkPosition(); + + if (parser.TryParse(ref context, out _) && then.TryParse(ref context, out value)) + return true; + + value = default; + context.RestorePosition(position); + + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new ThenParser(parser, then.Void()); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.ThenIgnore.cs b/src/Ramstack.Parsing/Parser.ThenIgnore.cs index b68c011..06aadb0 100644 --- a/src/Ramstack.Parsing/Parser.ThenIgnore.cs +++ b/src/Ramstack.Parsing/Parser.ThenIgnore.cs @@ -1,57 +1,57 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that sequentially applies the current parser and a specified second parser, - /// returning the result of the first parser while discarding the result of the second. - /// - /// The type of the value produced by the first parser. - /// The type of the value produced by the second parser, which is ignored. - /// The initial parser whose result is returned. - /// The subsequent parser, applied after the initial parser. - /// - /// A parser that sequentially applies the current parser and a specified second parser, - /// returning the result of the first parser while discarding the result of the second. - /// - public static Parser ThenIgnore(this Parser parser, Parser ignore) => - new ThenIgnoreParser(parser, ignore.Void()); - - #region Inner type: ThenIgnoreParser - - /// - /// Represents a parser that sequentially applies an initial parser and a specified second parser, - /// returning the result of the first parser while discarding the result of the second. - /// - /// The type of the value produced by the first parser. - /// The initial parser whose result is returned. - /// The subsequent parser, applied after the initial parser. - private sealed class ThenIgnoreParser(Parser parser, Parser ignore) : Parser - { - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - var bookmark = context.BookmarkPosition(); - - if (parser.TryParse(ref context, out value)) - { - var (index, length) = context.MatchedSegment; - - if (ignore.TryParse(ref context, out _)) - { - context.SetMatched(index, length); - return true; - } - } - - context.RestorePosition(bookmark); - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new ThenIgnoreParser(parser.Void(), ignore); - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that sequentially applies the current parser and a specified second parser, + /// returning the result of the first parser while discarding the result of the second. + /// + /// The type of the value produced by the first parser. + /// The type of the value produced by the second parser, which is ignored. + /// The initial parser whose result is returned. + /// The subsequent parser, applied after the initial parser. + /// + /// A parser that sequentially applies the current parser and a specified second parser, + /// returning the result of the first parser while discarding the result of the second. + /// + public static Parser ThenIgnore(this Parser parser, Parser ignore) => + new ThenIgnoreParser(parser, ignore.Void()); + + #region Inner type: ThenIgnoreParser + + /// + /// Represents a parser that sequentially applies an initial parser and a specified second parser, + /// returning the result of the first parser while discarding the result of the second. + /// + /// The type of the value produced by the first parser. + /// The initial parser whose result is returned. + /// The subsequent parser, applied after the initial parser. + private sealed class ThenIgnoreParser(Parser parser, Parser ignore) : Parser + { + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + var bookmark = context.BookmarkPosition(); + + if (parser.TryParse(ref context, out value)) + { + var (index, length) = context.MatchedSegment; + + if (ignore.TryParse(ref context, out _)) + { + context.SetMatched(index, length); + return true; + } + } + + context.RestorePosition(bookmark); + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new ThenIgnoreParser(parser.Void(), ignore); + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.UnicodeCategory.cs b/src/Ramstack.Parsing/Parser.UnicodeCategory.cs index 2df93a1..12b79a1 100644 --- a/src/Ramstack.Parsing/Parser.UnicodeCategory.cs +++ b/src/Ramstack.Parsing/Parser.UnicodeCategory.cs @@ -1,36 +1,36 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that matches a character in the specified Unicode category. - /// - /// The of the character to match. - /// - /// A parser that matches a character in the specified Unicode category. - /// - public static Parser L(UnicodeCategory category) => - L((GeneralUnicodeCategory)(1 << (int)category)); - - /// - /// Creates a parser that matches a character in any of the specified Unicode categories. - /// - /// The Unicode categories to match. - /// - /// A parser that matches a character in any of the specified Unicode categories. - /// - public static Parser L(GeneralUnicodeCategory categories) - { - var value = categories & CharClassExtensions.NormalizedCategoryMask; - if (value == GeneralUnicodeCategory.None) - Error_InvalidArgument(); - - return Set(new CharClass(value)); - - [DoesNotReturn] - static void Error_InvalidArgument() => - throw new ArgumentException( - $"'{nameof(GeneralUnicodeCategory.None)}' is not a valid Unicode category value.", - nameof(categories)); - } -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that matches a character in the specified Unicode category. + /// + /// The of the character to match. + /// + /// A parser that matches a character in the specified Unicode category. + /// + public static Parser L(UnicodeCategory category) => + L((GeneralUnicodeCategory)(1 << (int)category)); + + /// + /// Creates a parser that matches a character in any of the specified Unicode categories. + /// + /// The Unicode categories to match. + /// + /// A parser that matches a character in any of the specified Unicode categories. + /// + public static Parser L(GeneralUnicodeCategory categories) + { + var value = categories & CharClassExtensions.NormalizedCategoryMask; + if (value == GeneralUnicodeCategory.None) + Error_InvalidArgument(); + + return Set(new CharClass(value)); + + [DoesNotReturn] + static void Error_InvalidArgument() => + throw new ArgumentException( + $"'{nameof(GeneralUnicodeCategory.None)}' is not a valid Unicode category value.", + nameof(categories)); + } +} diff --git a/src/Ramstack.Parsing/Parser.Until.cs b/src/Ramstack.Parsing/Parser.Until.cs index 0719569..7d99922 100644 --- a/src/Ramstack.Parsing/Parser.Until.cs +++ b/src/Ramstack.Parsing/Parser.Until.cs @@ -1,140 +1,140 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Creates a parser that repeatedly applies the specified parser zero or more times - /// until the specified parser succeeds. - /// - /// The type of the value produced by the parser. - /// The type of the value produced by the terminator parser (ignored). - /// The main parser to apply repeatedly. - /// The parser that determines when to stop. - /// - /// A parser that applies zero or more times, stopping when - /// succeeds, and returns a list of the results produced by . - /// The terminator is not consumed. - /// - public static Parser> Until(this Parser parser, Parser terminator) => - new UntilParser(parser, terminator.Void()); - - /// - /// Creates a parser that repeatedly applies the specified parser zero or more times - /// until the specified parser succeeds. - /// - /// The type of the value produced by the parser. - /// The main parser to apply repeatedly. - /// The parser that determines when to stop. - /// - /// A parser that applies zero or more times, stopping when - /// succeeds, and returns a list of the results produced by . - /// The terminator is not consumed. - /// - public static Parser> Until(this Parser parser, Parser terminator) => - new UntilParser(parser, terminator); - - #region Inner type: UntilParser - - /// - /// Represents a parser that repeatedly applies the specified parser zero or more times - /// until the specified parser succeeds. - /// - /// The main parser to apply repeatedly. - /// The parser that determines when to stop. - private sealed class UntilParser(Parser parser, Parser terminator) : Parser> - { - private readonly Parser _isTerminator = And(terminator); - - /// - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) - { - var list = new List(); - var bookmark = context.BookmarkPosition(); - - while (true) - { - if (_isTerminator.TryParse(ref context, out _)) - { - context.SetMatched(bookmark); - value = list; - return true; - } - - var position = context.Position; - - if (!parser.TryParse(ref context, out var result)) - break; - - list.Add(result); - - // - // Prevent infinite loop - // - if (context.Position == position) - { - // The parser succeeded without consuming input. - // Stop to avoid an infinite loop. - break; - } - } - - value = null; - context.RestorePosition(bookmark); - return false; - } - - /// - protected internal override Parser ToVoidParser() => - new VoidUntilParser(parser.Void(), terminator); - } - - #endregion - - #region Inner type: VoidUntilParser - - /// - /// Represents a parser that repeatedly applies the specified parser zero or more times - /// until the specified parser succeeds. - /// - /// The main parser to apply repeatedly. - /// The parser that determines when to stop. - private sealed class VoidUntilParser(Parser parser, Parser terminator) : Parser - { - private readonly Parser _isTerminator = And(terminator); - - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - var bookmark = context.BookmarkPosition(); - - while (true) - { - if (_isTerminator.TryParse(ref context, out value)) - { - context.SetMatched(bookmark); - return true; - } - - var position = context.Position; - - if (!parser.TryParse(ref context, out value)) - break; - - // - // Prevent infinite loop - // - if (context.Position == position) - { - // The parser succeeded without consuming input. - // Stop to avoid an infinite loop. - break; - } - } - - context.RestorePosition(bookmark); - return false; - } - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Creates a parser that repeatedly applies the specified parser zero or more times + /// until the specified parser succeeds. + /// + /// The type of the value produced by the parser. + /// The type of the value produced by the terminator parser (ignored). + /// The main parser to apply repeatedly. + /// The parser that determines when to stop. + /// + /// A parser that applies zero or more times, stopping when + /// succeeds, and returns a list of the results produced by . + /// The terminator is not consumed. + /// + public static Parser> Until(this Parser parser, Parser terminator) => + new UntilParser(parser, terminator.Void()); + + /// + /// Creates a parser that repeatedly applies the specified parser zero or more times + /// until the specified parser succeeds. + /// + /// The type of the value produced by the parser. + /// The main parser to apply repeatedly. + /// The parser that determines when to stop. + /// + /// A parser that applies zero or more times, stopping when + /// succeeds, and returns a list of the results produced by . + /// The terminator is not consumed. + /// + public static Parser> Until(this Parser parser, Parser terminator) => + new UntilParser(parser, terminator); + + #region Inner type: UntilParser + + /// + /// Represents a parser that repeatedly applies the specified parser zero or more times + /// until the specified parser succeeds. + /// + /// The main parser to apply repeatedly. + /// The parser that determines when to stop. + private sealed class UntilParser(Parser parser, Parser terminator) : Parser> + { + private readonly Parser _isTerminator = And(terminator); + + /// + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out List? value) + { + var list = new List(); + var bookmark = context.BookmarkPosition(); + + while (true) + { + if (_isTerminator.TryParse(ref context, out _)) + { + context.SetMatched(bookmark); + value = list; + return true; + } + + var position = context.Position; + + if (!parser.TryParse(ref context, out var result)) + break; + + list.Add(result); + + // + // Prevent infinite loop + // + if (context.Position == position) + { + // The parser succeeded without consuming input. + // Stop to avoid an infinite loop. + break; + } + } + + value = null; + context.RestorePosition(bookmark); + return false; + } + + /// + protected internal override Parser ToVoidParser() => + new VoidUntilParser(parser.Void(), terminator); + } + + #endregion + + #region Inner type: VoidUntilParser + + /// + /// Represents a parser that repeatedly applies the specified parser zero or more times + /// until the specified parser succeeds. + /// + /// The main parser to apply repeatedly. + /// The parser that determines when to stop. + private sealed class VoidUntilParser(Parser parser, Parser terminator) : Parser + { + private readonly Parser _isTerminator = And(terminator); + + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + var bookmark = context.BookmarkPosition(); + + while (true) + { + if (_isTerminator.TryParse(ref context, out value)) + { + context.SetMatched(bookmark); + return true; + } + + var position = context.Position; + + if (!parser.TryParse(ref context, out value)) + break; + + // + // Prevent infinite loop + // + if (context.Position == position) + { + // The parser succeeded without consuming input. + // Stop to avoid an infinite loop. + break; + } + } + + context.RestorePosition(bookmark); + return false; + } + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.Void.cs b/src/Ramstack.Parsing/Parser.Void.cs index 0186e25..53a54f6 100644 --- a/src/Ramstack.Parsing/Parser.Void.cs +++ b/src/Ramstack.Parsing/Parser.Void.cs @@ -1,38 +1,38 @@ -namespace Ramstack.Parsing; - -partial class Parser -{ - /// - /// Converts a specified parser into a void parser, which matches the input without storing the result. - /// - /// The type of the value produced by the parser. - /// The parser to convert. - /// - /// A parser instance that performs the same parsing as the original parser, but does not capture the result. - /// - public static Parser Void(this Parser parser) => - parser as Parser ?? parser.ToVoidParser(); - - #region Inner type: VoidParser - - /// - /// Represents a parser that wraps a specified parser that cannot provide a void parser for itself. - /// - /// The type of the value produced by the underlying parser. - /// The parser to wrap. - internal sealed class VoidParser(Parser parser) : Parser - { - /// - public override bool TryParse(ref ParseContext context, out Unit value) - { - Unsafe.SkipInit(out value); - return parser.TryParse(ref context, out _); - } - - /// - protected internal override Parser ToNamedParser(string? name) => - new VoidParser(parser.ToNamedParser(name)) { Name = name }; - } - - #endregion -} +namespace Ramstack.Parsing; + +partial class Parser +{ + /// + /// Converts a specified parser into a void parser, which matches the input without storing the result. + /// + /// The type of the value produced by the parser. + /// The parser to convert. + /// + /// A parser instance that performs the same parsing as the original parser, but does not capture the result. + /// + public static Parser Void(this Parser parser) => + parser as Parser ?? parser.ToVoidParser(); + + #region Inner type: VoidParser + + /// + /// Represents a parser that wraps a specified parser that cannot provide a void parser for itself. + /// + /// The type of the value produced by the underlying parser. + /// The parser to wrap. + internal sealed class VoidParser(Parser parser) : Parser + { + /// + public override bool TryParse(ref ParseContext context, out Unit value) + { + Unsafe.SkipInit(out value); + return parser.TryParse(ref context, out _); + } + + /// + protected internal override Parser ToNamedParser(string? name) => + new VoidParser(parser.ToNamedParser(name)) { Name = name }; + } + + #endregion +} diff --git a/src/Ramstack.Parsing/Parser.cs b/src/Ramstack.Parsing/Parser.cs index 19b0676..a3adba2 100644 --- a/src/Ramstack.Parsing/Parser.cs +++ b/src/Ramstack.Parsing/Parser.cs @@ -1,6 +1,6 @@ -namespace Ramstack.Parsing; - -/// -/// Provides factory methods and extension methods for . -/// -public static partial class Parser; +namespace Ramstack.Parsing; + +/// +/// Provides factory methods and extension methods for . +/// +public static partial class Parser; diff --git a/src/Ramstack.Parsing/Parser`1.cs b/src/Ramstack.Parsing/Parser`1.cs index b041973..ceb75e0 100644 --- a/src/Ramstack.Parsing/Parser`1.cs +++ b/src/Ramstack.Parsing/Parser`1.cs @@ -1,135 +1,135 @@ -namespace Ramstack.Parsing; - -/// -/// Represents the base class of a parser, providing parsing logic and extension points for derived parsers. -/// -/// The type of the value produced by the parser. -public abstract class Parser -{ - /// - /// Gets the name of the parser. - /// - public string? Name { get; init; } - - /// - /// Initializes a new instance of the class. - /// - protected Parser() - { - } - - /// - /// Initializes a new instance of the class with the specified name. - /// - /// The name to assign to the parser. - protected Parser(string? name) => - Name = name; - - /// - /// Attempts to parse the specified source text. Diagnostic messages are suppressed during this operation. - /// - /// The source text to parse. - /// When this method returns, contains the parsed value if parsing succeeded; - /// otherwise, the default value of . - /// - /// if the parser succeeded; otherwise, . - /// - public bool TryParse(ReadOnlySpan source, [NotNullWhen(true)] out T? value) - { - try - { - var context = new ParseContext(source) - { - DiagnosticState = DiagnosticState.Suppressed - }; - - if (TryParse(ref context, out value)) - return true; - } - catch - { - // Ignore exceptions - } - - value = default; - return false; - } - - /// - /// Parses the specified source text. - /// - /// The source text to parse. - /// - /// A containing the parsed result or error information. - /// - public ParseResult Parse(ReadOnlySpan source) - { - var context = new ParseContext(source); - try - { - if (TryParse(ref context, out var value)) - { - return new ParseResult - { - Value = value, - Length = context.Position - }; - } - - return new ParseResult - { - Length = 0, - ErrorMessage = context.ToString() - }; - } - catch (FatalErrorException exception) - { - var message = ParseContext.GenerateErrorMessage(context.Source, context.Position, exception.Message); - return new ParseResult - { - Length = 0, - ErrorMessage = message - }; - } - catch (Exception exception) - { - var message = ParseContext.GenerateErrorMessage(context.Source, context.Position, exception.ToString()); - return new ParseResult - { - Length = 0, - ErrorMessage = message, - Exception = exception - }; - } - } - - /// - /// Attempts to parse the source text using the provided . - /// - /// The parse context containing the source text and position. - /// When this method returns, contains the parsed value if parsing succeeded; - /// otherwise, the default value of . - /// - /// if the parser succeeded; otherwise, . - /// - public abstract bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value); - - /// - /// Creates a new parser with the specified name. - /// - /// The name to assign to the parser. - /// - /// A newly created instance with the specified name. - /// - protected internal virtual Parser ToNamedParser(string? name) => - new Parser.NamedParser(this) { Name = name }; - - /// - /// Creates a parser that matches the input without capturing or storing the parsed result. - /// - /// - /// A parser that discards the parsed value. - /// - protected internal virtual Parser ToVoidParser() => - this as Parser ?? new Parser.VoidParser(this) { Name = Name }; -} +namespace Ramstack.Parsing; + +/// +/// Represents the base class of a parser, providing parsing logic and extension points for derived parsers. +/// +/// The type of the value produced by the parser. +public abstract class Parser +{ + /// + /// Gets the name of the parser. + /// + public string? Name { get; init; } + + /// + /// Initializes a new instance of the class. + /// + protected Parser() + { + } + + /// + /// Initializes a new instance of the class with the specified name. + /// + /// The name to assign to the parser. + protected Parser(string? name) => + Name = name; + + /// + /// Attempts to parse the specified source text. Diagnostic messages are suppressed during this operation. + /// + /// The source text to parse. + /// When this method returns, contains the parsed value if parsing succeeded; + /// otherwise, the default value of . + /// + /// if the parser succeeded; otherwise, . + /// + public bool TryParse(ReadOnlySpan source, [NotNullWhen(true)] out T? value) + { + try + { + var context = new ParseContext(source) + { + DiagnosticState = DiagnosticState.Suppressed + }; + + if (TryParse(ref context, out value)) + return true; + } + catch + { + // Ignore exceptions + } + + value = default; + return false; + } + + /// + /// Parses the specified source text. + /// + /// The source text to parse. + /// + /// A containing the parsed result or error information. + /// + public ParseResult Parse(ReadOnlySpan source) + { + var context = new ParseContext(source); + try + { + if (TryParse(ref context, out var value)) + { + return new ParseResult + { + Value = value, + Length = context.Position + }; + } + + return new ParseResult + { + Length = 0, + ErrorMessage = context.ToString() + }; + } + catch (FatalErrorException exception) + { + var message = ParseContext.GenerateErrorMessage(context.Source, context.Position, exception.Message); + return new ParseResult + { + Length = 0, + ErrorMessage = message + }; + } + catch (Exception exception) + { + var message = ParseContext.GenerateErrorMessage(context.Source, context.Position, exception.ToString()); + return new ParseResult + { + Length = 0, + ErrorMessage = message, + Exception = exception + }; + } + } + + /// + /// Attempts to parse the source text using the provided . + /// + /// The parse context containing the source text and position. + /// When this method returns, contains the parsed value if parsing succeeded; + /// otherwise, the default value of . + /// + /// if the parser succeeded; otherwise, . + /// + public abstract bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value); + + /// + /// Creates a new parser with the specified name. + /// + /// The name to assign to the parser. + /// + /// A newly created instance with the specified name. + /// + protected internal virtual Parser ToNamedParser(string? name) => + new Parser.NamedParser(this) { Name = name }; + + /// + /// Creates a parser that matches the input without capturing or storing the parsed result. + /// + /// + /// A parser that discards the parsed value. + /// + protected internal virtual Parser ToVoidParser() => + this as Parser ?? new Parser.VoidParser(this) { Name = Name }; +} diff --git a/src/Ramstack.Parsing/PositionBookmark.cs b/src/Ramstack.Parsing/PositionBookmark.cs index 5e90e39..7b3d55f 100644 --- a/src/Ramstack.Parsing/PositionBookmark.cs +++ b/src/Ramstack.Parsing/PositionBookmark.cs @@ -1,20 +1,20 @@ -namespace Ramstack.Parsing; - -/// -/// Represents a bookmark for a particular parsing position in the input text. -/// -[DebuggerDisplay("Position = {Position}")] -public readonly struct PositionBookmark -{ - /// - /// Gets the zero-based parsing position. - /// - public readonly int Position; - - /// - /// Initializes a new instance of the structure. - /// - /// The zero-based parsing position. - internal PositionBookmark(int position) => - Position = position; -} +namespace Ramstack.Parsing; + +/// +/// Represents a bookmark for a particular parsing position in the input text. +/// +[DebuggerDisplay("Position = {Position}")] +public readonly struct PositionBookmark +{ + /// + /// Gets the zero-based parsing position. + /// + public readonly int Position; + + /// + /// Initializes a new instance of the structure. + /// + /// The zero-based parsing position. + internal PositionBookmark(int position) => + Position = position; +} diff --git a/src/Ramstack.Parsing/Properties/AssemblyInfo.cs b/src/Ramstack.Parsing/Properties/AssemblyInfo.cs index 149faff..9d36346 100644 --- a/src/Ramstack.Parsing/Properties/AssemblyInfo.cs +++ b/src/Ramstack.Parsing/Properties/AssemblyInfo.cs @@ -1 +1 @@ -[module: SkipLocalsInit] +[module: SkipLocalsInit] diff --git a/src/Ramstack.Parsing/Properties/Autogenerated.ttinclude b/src/Ramstack.Parsing/Properties/Autogenerated.ttinclude index a76a298..fa7a0a0 100644 --- a/src/Ramstack.Parsing/Properties/Autogenerated.ttinclude +++ b/src/Ramstack.Parsing/Properties/Autogenerated.ttinclude @@ -1,57 +1,57 @@ -<#@ output extension=".Generated.cs" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -//----------------------------------------------------------------------------// -// // -// This code was generated by a tool. // -// // -// Changes to this file may cause incorrect behavior // -// and will be lost if the code is regenerated. // -// // -//----------------------------------------------------------------------------// -<#+ - public void Clean() - { - var sb = GenerationEnvironment; - while (sb.Length > 0 && char.IsWhiteSpace(sb[sb.Length - 1])) - sb.Length--; - - WriteLine(""); - } - - public string GenerateList(int count, Func generator, string separator = ", ", int from = 0) => - string.Join(separator, Enumerable.Range(from, count).Select(generator)); - - public void WriteLine() => - WriteLine(""); - - public void WriteSeparatorLine(bool separate) - { - if (separate) - WriteLine(); - } - - public IDisposable WriteRegion(string region) => - new RegionScope(GenerationEnvironment, region); - - #region Inner type: RegionScope - - private sealed class RegionScope : IDisposable - { - private readonly StringBuilder _sb; - - public RegionScope(StringBuilder builder, string region) - { - _sb = builder; - _sb - .AppendLine($" #region {region}") - .AppendLine(); - } - - public void Dispose() => - _sb.AppendLine().AppendLine(" #endregion"); - } - - #endregion -#> +<#@ output extension=".Generated.cs" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +//----------------------------------------------------------------------------// +// // +// This code was generated by a tool. // +// // +// Changes to this file may cause incorrect behavior // +// and will be lost if the code is regenerated. // +// // +//----------------------------------------------------------------------------// +<#+ + public void Clean() + { + var sb = GenerationEnvironment; + while (sb.Length > 0 && char.IsWhiteSpace(sb[sb.Length - 1])) + sb.Length--; + + WriteLine(""); + } + + public string GenerateList(int count, Func generator, string separator = ", ", int from = 0) => + string.Join(separator, Enumerable.Range(from, count).Select(generator)); + + public void WriteLine() => + WriteLine(""); + + public void WriteSeparatorLine(bool separate) + { + if (separate) + WriteLine(); + } + + public IDisposable WriteRegion(string region) => + new RegionScope(GenerationEnvironment, region); + + #region Inner type: RegionScope + + private sealed class RegionScope : IDisposable + { + private readonly StringBuilder _sb; + + public RegionScope(StringBuilder builder, string region) + { + _sb = builder; + _sb + .AppendLine($" #region {region}") + .AppendLine(); + } + + public void Dispose() => + _sb.AppendLine().AppendLine(" #endregion"); + } + + #endregion +#> diff --git a/src/Ramstack.Parsing/Properties/Compatibility.cs b/src/Ramstack.Parsing/Properties/Compatibility.cs index 804e015..7cb9e5d 100644 --- a/src/Ramstack.Parsing/Properties/Compatibility.cs +++ b/src/Ramstack.Parsing/Properties/Compatibility.cs @@ -1,88 +1,88 @@ -#if !NET7_0_OR_GREATER - -namespace System.Runtime.CompilerServices -{ - /// - /// Specifies that a type has required members or that a member is required. - /// - [DebuggerNonUserCode] - [AttributeUsage(AttributeTargets.Class - | AttributeTargets.Struct - | AttributeTargets.Field - | AttributeTargets.Property, - Inherited = false)] - internal sealed class RequiredMemberAttribute : Attribute; - - /// - /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. - /// - /// The name of the required compiler feature. - [DebuggerNonUserCode] - [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - internal sealed class CompilerFeatureRequiredAttribute(string featureName) : Attribute - { - /// - /// Gets the name of the compiler feature. - /// - public string FeatureName => featureName; - - /// - /// Gets a value that indicates whether the compiler can choose to allow access - /// to the location where this attribute is applied if it does not understand . - /// - public bool IsOptional { get; init; } - } -} - -namespace System.Diagnostics.CodeAnalysis -{ - /// - /// Specifies that this constructor sets all required members for the current type, - /// and callers do not need to set any required members themselves. - /// - [AttributeUsage(AttributeTargets.Constructor)] - internal sealed class SetsRequiredMembersAttribute : Attribute; -} - -namespace System.Linq -{ - /// - /// Provides extension methods for the . - /// - internal static class EnumerableExtensions - { - /// - /// Sorts the elements of a sequence in ascending order. - /// - /// The type of the elements. - /// A sequence of values to order. - /// An to compare keys. - /// - /// An whose elements are sorted. - /// - public static IOrderedEnumerable Order(this IEnumerable source, IComparer? comparer = null) => - source.OrderBy(OrderHelper.IdentityFunc, comparer); - - /// - /// Sorts the elements of a sequence in descending order. - /// - /// The type of the elements. - /// A sequence of values to order. - /// An to compare keys. - /// - /// An whose elements are sorted. - /// - public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer? comparer = null) => - source.OrderByDescending(OrderHelper.IdentityFunc, comparer); - - private static class OrderHelper - { - /// - /// The function that returns the same object which was passed as parameter. - /// - public static readonly Func IdentityFunc = v => v; - } - } -} - -#endif +#if !NET7_0_OR_GREATER + +namespace System.Runtime.CompilerServices +{ + /// + /// Specifies that a type has required members or that a member is required. + /// + [DebuggerNonUserCode] + [AttributeUsage(AttributeTargets.Class + | AttributeTargets.Struct + | AttributeTargets.Field + | AttributeTargets.Property, + Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute; + + /// + /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. + /// + /// The name of the required compiler feature. + [DebuggerNonUserCode] + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute(string featureName) : Attribute + { + /// + /// Gets the name of the compiler feature. + /// + public string FeatureName => featureName; + + /// + /// Gets a value that indicates whether the compiler can choose to allow access + /// to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; init; } + } +} + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies that this constructor sets all required members for the current type, + /// and callers do not need to set any required members themselves. + /// + [AttributeUsage(AttributeTargets.Constructor)] + internal sealed class SetsRequiredMembersAttribute : Attribute; +} + +namespace System.Linq +{ + /// + /// Provides extension methods for the . + /// + internal static class EnumerableExtensions + { + /// + /// Sorts the elements of a sequence in ascending order. + /// + /// The type of the elements. + /// A sequence of values to order. + /// An to compare keys. + /// + /// An whose elements are sorted. + /// + public static IOrderedEnumerable Order(this IEnumerable source, IComparer? comparer = null) => + source.OrderBy(OrderHelper.IdentityFunc, comparer); + + /// + /// Sorts the elements of a sequence in descending order. + /// + /// The type of the elements. + /// A sequence of values to order. + /// An to compare keys. + /// + /// An whose elements are sorted. + /// + public static IOrderedEnumerable OrderDescending(this IEnumerable source, IComparer? comparer = null) => + source.OrderByDescending(OrderHelper.IdentityFunc, comparer); + + private static class OrderHelper + { + /// + /// The function that returns the same object which was passed as parameter. + /// + public static readonly Func IdentityFunc = v => v; + } + } +} + +#endif diff --git a/src/Ramstack.Parsing/Ramstack.Parsing.csproj b/src/Ramstack.Parsing/Ramstack.Parsing.csproj index e7368b3..3be5e70 100644 --- a/src/Ramstack.Parsing/Ramstack.Parsing.csproj +++ b/src/Ramstack.Parsing/Ramstack.Parsing.csproj @@ -1,98 +1,98 @@ - - - - net6.0;net7.0;net8.0;net9.0 - A fast, lightweight parser combinator library for .NET - enable - enable - latest - true - true - - - - 649;1591 - CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714 - - - - https://github.com/rameel/ramstack.parsing - Rameel - parser;peg - https://github.com/rameel/ramstack.parsing#readme - README.md - MIT - true - snupkg - true - true - true - true - - - - true - - - - - - - - - - - - - - - - - - - - TextTemplatingFileGenerator - Parser.Seq.Generated.cs - - - TextTemplatingFileGenerator - Parser.Do.Generated.cs - - - - - - True - True - Parser.Seq.tt - - - True - True - Parser.Do.tt - - - - - - - - - - true - \ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + net6.0;net7.0;net8.0;net9.0 + A fast, lightweight parser combinator library for .NET + enable + enable + latest + true + true + + + + 649;1591 + CS8600;CS8601;CS8602;CS8603;CS8604;CS8610;CS8613;CS8614;CS8618;CS8619;CS8620;CS8625;CS8643;CS8653;CS8714 + + + + https://github.com/rameel/ramstack.parsing + Rameel + parser;peg + https://github.com/rameel/ramstack.parsing#readme + README.md + MIT + true + snupkg + true + true + true + true + + + + true + + + + + + + + + + + + + + + + + + + + TextTemplatingFileGenerator + Parser.Seq.Generated.cs + + + TextTemplatingFileGenerator + Parser.Do.Generated.cs + + + + + + True + True + Parser.Seq.tt + + + True + True + Parser.Do.tt + + + + + + + + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Ramstack.Parsing/ThrowHelper.cs b/src/Ramstack.Parsing/ThrowHelper.cs index 4e902a9..ce424ab 100644 --- a/src/Ramstack.Parsing/ThrowHelper.cs +++ b/src/Ramstack.Parsing/ThrowHelper.cs @@ -1,12 +1,12 @@ -namespace Ramstack.Parsing; - -internal static class ThrowHelper -{ - [DoesNotReturn] - public static void ThrowArgumentOutOfRangeException() => - throw new ArgumentOutOfRangeException(); - - [DoesNotReturn] - public static void ThrowNotSupportedException() => - throw new NotSupportedException(); -} +namespace Ramstack.Parsing; + +internal static class ThrowHelper +{ + [DoesNotReturn] + public static void ThrowArgumentOutOfRangeException() => + throw new ArgumentOutOfRangeException(); + + [DoesNotReturn] + public static void ThrowNotSupportedException() => + throw new NotSupportedException(); +} diff --git a/src/Ramstack.Parsing/Unit.cs b/src/Ramstack.Parsing/Unit.cs index 6fcf8df..61d51c2 100644 --- a/src/Ramstack.Parsing/Unit.cs +++ b/src/Ramstack.Parsing/Unit.cs @@ -1,19 +1,19 @@ -namespace Ramstack.Parsing; - -/// -/// Specifies a type that does not have a value, serving as a void-like type for generic contexts. -/// Since cannot be used as a generic type parameter, provides -/// an alternative for scenarios requiring a "void" type in generic code. -/// -public readonly struct Unit -{ - #pragma warning disable 169 - // Before .NET 9, the JIT generates inefficient code for structures without fields. - private readonly int _; - #pragma warning restore 169 - - /// - /// Gets the value of the type. - /// - public static Unit Value => default; -} +namespace Ramstack.Parsing; + +/// +/// Specifies a type that does not have a value, serving as a void-like type for generic contexts. +/// Since cannot be used as a generic type parameter, provides +/// an alternative for scenarios requiring a "void" type in generic code. +/// +public readonly struct Unit +{ + #pragma warning disable 169 + // Before .NET 9, the JIT generates inefficient code for structures without fields. + private readonly int _; + #pragma warning restore 169 + + /// + /// Gets the value of the type. + /// + public static Unit Value => default; +} diff --git a/src/Ramstack.Parsing/Utilities/BitVector`1.cs b/src/Ramstack.Parsing/Utilities/BitVector`1.cs index a1a1568..d562471 100644 --- a/src/Ramstack.Parsing/Utilities/BitVector`1.cs +++ b/src/Ramstack.Parsing/Utilities/BitVector`1.cs @@ -1,89 +1,89 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a bit vector structure. -/// -internal readonly unsafe struct BitVector where T : unmanaged -{ - // ReSharper disable once UnassignedReadonlyField - private readonly T _storage; - - /// - /// Gets the total number of bits that this instance can represent. - /// - public static int Length => sizeof(T) * 8; - - /// - /// Sets the bit at the specified position (0..N-1) to 1. - /// - /// The bit position to set. Must be within the range [0..N-1]. - public void Set(int position) - { - Argument.ThrowIfGreaterThanOrEqual((uint)position, (uint)Length); - - var index = (uint)position >> 5; - var value = 1u << position; - - Unsafe.Add( - ref Unsafe.As(ref Unsafe.AsRef(in _storage)), - index) |= value; - } - - /// - /// Determines whether the bit at the specified position is set to 1. - /// - /// The bit position to check. - /// - /// if the specified bit is set; otherwise, . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsBitSet(int position) - { - if ((uint)position >= (uint)sizeof(T) * 8) - return false; - - var index = sizeof(T) != 32/8 ? (uint)position >> 5 : 0; - var value = Unsafe.Add( - ref Unsafe.As(ref Unsafe.AsRef(in _storage)), - index) & (1u << position); - - return value != 0; - } - - /// - /// Returns an array of objects representing the internal bitset, - /// applying the specified offset to each character value. - /// - /// The offset to apply to the character values. Must be non-negative. - /// - /// An array of objects representing the characters indicated by the set bits - /// in the internal representation. - /// - public CharClassRange[] ToCharClassRanges(int offset) - { - Argument.ThrowIfNegative(offset); - - var count = Length; - var array = new ArrayBuilder(count); - - for (var i = 0; i < count; i++) - { - if (IsBitSet(i)) - { - var range = CharClassRange.Create((char)(i + offset)); - array.TryAdd(range); - } - } - - return array.ToArray(); - } -} - -[StructLayout(LayoutKind.Explicit, Size = 128/8)] -internal readonly struct Block128Bit; - -[StructLayout(LayoutKind.Explicit, Size = 256/8)] -internal readonly struct Block256Bit; - -[StructLayout(LayoutKind.Explicit, Size = 512/8)] -internal readonly struct Block512Bit; +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a bit vector structure. +/// +internal readonly unsafe struct BitVector where T : unmanaged +{ + // ReSharper disable once UnassignedReadonlyField + private readonly T _storage; + + /// + /// Gets the total number of bits that this instance can represent. + /// + public static int Length => sizeof(T) * 8; + + /// + /// Sets the bit at the specified position (0..N-1) to 1. + /// + /// The bit position to set. Must be within the range [0..N-1]. + public void Set(int position) + { + Argument.ThrowIfGreaterThanOrEqual((uint)position, (uint)Length); + + var index = (uint)position >> 5; + var value = 1u << position; + + Unsafe.Add( + ref Unsafe.As(ref Unsafe.AsRef(in _storage)), + index) |= value; + } + + /// + /// Determines whether the bit at the specified position is set to 1. + /// + /// The bit position to check. + /// + /// if the specified bit is set; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsBitSet(int position) + { + if ((uint)position >= (uint)sizeof(T) * 8) + return false; + + var index = sizeof(T) != 32/8 ? (uint)position >> 5 : 0; + var value = Unsafe.Add( + ref Unsafe.As(ref Unsafe.AsRef(in _storage)), + index) & (1u << position); + + return value != 0; + } + + /// + /// Returns an array of objects representing the internal bitset, + /// applying the specified offset to each character value. + /// + /// The offset to apply to the character values. Must be non-negative. + /// + /// An array of objects representing the characters indicated by the set bits + /// in the internal representation. + /// + public CharClassRange[] ToCharClassRanges(int offset) + { + Argument.ThrowIfNegative(offset); + + var count = Length; + var array = new ArrayBuilder(count); + + for (var i = 0; i < count; i++) + { + if (IsBitSet(i)) + { + var range = CharClassRange.Create((char)(i + offset)); + array.TryAdd(range); + } + } + + return array.ToArray(); + } +} + +[StructLayout(LayoutKind.Explicit, Size = 128/8)] +internal readonly struct Block128Bit; + +[StructLayout(LayoutKind.Explicit, Size = 256/8)] +internal readonly struct Block256Bit; + +[StructLayout(LayoutKind.Explicit, Size = 512/8)] +internal readonly struct Block512Bit; diff --git a/src/Ramstack.Parsing/Utilities/CharClass.cs b/src/Ramstack.Parsing/Utilities/CharClass.cs index bdf601d..5f8bda5 100644 --- a/src/Ramstack.Parsing/Utilities/CharClass.cs +++ b/src/Ramstack.Parsing/Utilities/CharClass.cs @@ -1,324 +1,324 @@ -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a character class that defines a set of characters based on Unicode categories or specific ranges. -/// -[DebuggerDisplay("{ToString(),nq}")] -internal sealed class CharClass -{ - /// - /// A dictionary mapping Unicode category designations to their corresponding values. - /// - public static readonly Dictionary Categories = new Dictionary - { - // Letters - ["Lu"] = GeneralUnicodeCategory.UppercaseLetter, - ["Ll"] = GeneralUnicodeCategory.LowercaseLetter, - ["Lt"] = GeneralUnicodeCategory.TitlecaseLetter, - ["Lm"] = GeneralUnicodeCategory.ModifierLetter, - ["Lo"] = GeneralUnicodeCategory.OtherLetter, - ["L" ] = GeneralUnicodeCategory.Letter, - - // Marks - ["Mn"] = GeneralUnicodeCategory.NonSpacingMark, - ["Mc"] = GeneralUnicodeCategory.SpacingCombiningMark, - ["Me"] = GeneralUnicodeCategory.EnclosingMark, - ["M" ] = GeneralUnicodeCategory.Mark, - - // Numbers - ["Nd"] = GeneralUnicodeCategory.DecimalDigitNumber, - ["Nl"] = GeneralUnicodeCategory.LetterNumber, - ["No"] = GeneralUnicodeCategory.OtherNumber, - ["N" ] = GeneralUnicodeCategory.Number, - - // Separators - ["Zs"] = GeneralUnicodeCategory.SpaceSeparator, - ["Zl"] = GeneralUnicodeCategory.LineSeparator, - ["Zp"] = GeneralUnicodeCategory.ParagraphSeparator, - ["Z" ] = GeneralUnicodeCategory.Separator, - - // Controls - ["Cc"] = GeneralUnicodeCategory.Control, - ["Cf"] = GeneralUnicodeCategory.Format, - ["Cs"] = GeneralUnicodeCategory.Surrogate, - ["Co"] = GeneralUnicodeCategory.PrivateUse, - ["Cn"] = GeneralUnicodeCategory.OtherNotAssigned, - ["C" ] = GeneralUnicodeCategory.Other, - - // Punctuations - ["Pc"] = GeneralUnicodeCategory.ConnectorPunctuation, - ["Pd"] = GeneralUnicodeCategory.DashPunctuation, - ["Ps"] = GeneralUnicodeCategory.OpenPunctuation, - ["Pe"] = GeneralUnicodeCategory.ClosePunctuation, - ["Pi"] = GeneralUnicodeCategory.InitialQuotePunctuation, - ["Pf"] = GeneralUnicodeCategory.FinalQuotePunctuation, - ["Po"] = GeneralUnicodeCategory.OtherPunctuation, - ["P" ] = GeneralUnicodeCategory.Punctuation, - - // Symbols - ["Sm"] = GeneralUnicodeCategory.MathSymbol, - ["Sc"] = GeneralUnicodeCategory.CurrencySymbol, - ["Sk"] = GeneralUnicodeCategory.ModifierSymbol, - ["So"] = GeneralUnicodeCategory.OtherSymbol, - ["S" ] = GeneralUnicodeCategory.Symbol - }; - - /// - /// An array of Unicode category group abbreviations. - /// - public static readonly string[] CategoryGroups = ["L", "M", "N", "Z", "C", "P", "S"]; - - private readonly CharClassRange[] _source; - private readonly CharClassRange[] _ranges; - private readonly CharClassUnicodeCategory _categories; - - /// - /// Gets the source (original) ranges of characters. - /// - public CharClassRange[] Source => _source; - - /// - /// Gets the extended ranges of characters, which include additional - /// characters based on the specified Unicode categories. - /// - public CharClassRange[] Ranges => _ranges; - - /// - /// Gets the Unicode categories associated with this character class. - /// - public CharClassUnicodeCategory UnicodeCategories => _categories; - - /// - /// Gets the total number of unique symbols across all ranges. - /// - /// - /// This property calculates the sum of all symbols included in the ranges. - /// Each range contributes the count of its symbols, determined as Hi - Lo + 1. - /// - public int SymbolCount { get; } - - /// - /// Gets the total width between the lowest and highest characters across all ranges. - /// - /// - /// This property calculates the difference between the lowest character in the first range - /// and the highest character in the last range, inclusive. - /// The width represents the entire span covered by all ranges, including any gaps between them. - /// This value is useful for determining the required size of data structures (like bit arrays) - /// that need to represent the full range. - /// - public int OverallWidth { get; } - - /// - /// Initializes a new instance of the class using the specified elements. - /// - /// The character class elements to initialize from. - public CharClass(CharClassElement[] elements) - : this(ExtractRanges(elements), ExtractCategory(elements)) - { - } - - /// - /// Initializes a new instance of the class using the specified ranges. - /// - /// The character ranges to initialize from. - public CharClass(CharClassRange[] ranges) : this(ranges, ranges, default) - { - } - - /// - /// Initializes a new instance of the class using the specified ranges and Unicode categories. - /// - /// The character ranges to initialize from. - /// The Unicode categories to initialize from. - public CharClass(CharClassRange[] ranges, CharClassUnicodeCategory categories) : this(ranges, ranges, categories) - { - } - - /// - /// Initializes a new instance of the class using the specified Unicode categories. - /// - /// The Unicode categories to initialize from. - public CharClass(CharClassUnicodeCategory categories) : this([], [], categories) - { - } - - /// - /// Initializes a new instance of the class using the specified source ranges, - /// optimized ranges, and Unicode categories. - /// - /// The source character ranges. - /// The optimized character ranges. - /// The Unicode categories. - private CharClass(CharClassRange[] source, CharClassRange[] ranges, CharClassUnicodeCategory categories) - { - Array.Sort(ranges); - - // If Unicode categories are specified, extend the lower ranges - // with characters belonging to the specified categories. - if (categories.Value != 0 && ranges.Length != 0) - ranges = EnrichRangesCore(ranges, categories); - - source = source.Optimize(); - ranges = ranges.Optimize(); - - if (ranges.AsSpan().SequenceEqual(source.AsSpan())) - source = ranges; - - if (ranges is [{ IsFullRange: true }]) - categories = default; - - foreach (var range in ranges) - SymbolCount += range.SymbolCount; - - if (ranges.Length != 0) - OverallWidth = ranges[^1].Hi - ranges[0].Lo + 1; - - (_source, _ranges, _categories) = (source, ranges, categories); - } - - /// - /// Returns a string containing all characters represented in the current character class. - /// - /// If set to , the length of the resulting string is aligned - /// to the nearest multiple of a SIMD-friendly size (16 or 32 bytes) if SIMD instructions are supported. - /// - /// - /// A string consisting of all characters within the defined ranges of this instance. - /// - public string GetSymbols(bool simdAligned = false) - { - var align = Avx2.IsSupported - ? 16 - : Sse41.IsSupported || AdvSimd.IsSupported - ? 8 - : 0; - - var count = simdAligned && align != 0 - ? SymbolCount + (align - 1) & -align - : SymbolCount; - - return string.Create(count, (align, Ranges), (span, state) => - { - var i = 0; - foreach (var (lo, hi) in state.Ranges) - for (int c = lo; c <= hi; c++) - if ((uint)i < (uint)span.Length) - span[i++] = (char)c; - - while ((uint)i < (uint)span.Length) - span[i++] = span[0]; - }); - } - - /// - /// Merges this character class with another character class. - /// - /// The other character class to merge with. - /// - /// A new representing the merged result. - /// - public CharClass MergeClasses(CharClass other) - { - return new CharClass( - [.._source, ..other._source], - [.._ranges, ..other._ranges], - _categories.Combine(other._categories) - ); - } - - /// - /// Populates the character class ranges with characters from the specified Unicode categories, - /// ensuring that the ranges contain all relevant characters for optimized processing. - /// - /// - /// A new instance with updated character ranges that include - /// characters from the specified Unicode categories. - /// If no Unicode categories are specified, returns the current instance unchanged. - /// - public CharClass EnrichRanges() - { - if (_categories.IsEmpty) - return this; - - var ranges = EnrichRangesCore(_ranges, _categories); - return new CharClass(_source, ranges, _categories); - } - - /// - /// Generates an extended set of character class ranges by adding characters from the specified Unicode categories. - /// This is useful for cases where Unicode category-based classification is needed in addition to predefined ranges. - /// - /// The Unicode character categories whose characters should be added to the ranges. - /// The existing character class ranges that will be extended. - /// - /// An array of that includes the original ranges - /// and additional characters from the specified Unicode categories. - /// - private static CharClassRange[] EnrichRangesCore(CharClassRange[] ranges, CharClassUnicodeCategory categories) - { - Debug.Assert(!categories.IsEmpty); - - var builder = new ArrayBuilder(); - - // Identify used ASCII characters. - for (var c = '\0'; c < 128; c++) - if (categories.IsInclude(c)) - builder.Add(CharClassRange.Create(c)); - - // Characters in the range [128..511]. - // Only if the specified range is used. - if (ranges.Length != 0 && (int)ranges[^1].Hi is >= 128 and < 512) - for (var c = (char)128; c < 512; c++) - if (categories.IsInclude(c)) - builder.Add(CharClassRange.Create(c)); - - // - // TODO Should we populate non-ASCII ranges if no ASCII values are present? - // - - builder.AddRange(ranges); - return builder.ToArray(); - } - - /// - public override string ToString() => - this.ToPrintable(); - - /// - /// Extracts Unicode categories from the specified elements. - /// - /// The elements to extract categories from. - /// - /// A representing the combined categories. - /// - private static CharClassUnicodeCategory ExtractCategory(CharClassElement[] elements) - { - CharClassUnicodeCategory value = default; - foreach (var element in elements) - if (element.TryGetUnicodeCategory(out var category)) - value = value.Combine(category); - - return value; - } - - /// - /// Extracts character ranges from the specified elements. - /// - /// The elements to extract ranges from. - /// - /// An array of representing the extracted ranges. - /// - private static CharClassRange[] ExtractRanges(CharClassElement[] elements) - { - var builder = new ArrayBuilder(); - foreach (var element in elements) - if (element.TryGetRange(out var range)) - builder.Add(range); - - return builder.ToArray(); - } -} +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a character class that defines a set of characters based on Unicode categories or specific ranges. +/// +[DebuggerDisplay("{ToString(),nq}")] +internal sealed class CharClass +{ + /// + /// A dictionary mapping Unicode category designations to their corresponding values. + /// + public static readonly Dictionary Categories = new Dictionary + { + // Letters + ["Lu"] = GeneralUnicodeCategory.UppercaseLetter, + ["Ll"] = GeneralUnicodeCategory.LowercaseLetter, + ["Lt"] = GeneralUnicodeCategory.TitlecaseLetter, + ["Lm"] = GeneralUnicodeCategory.ModifierLetter, + ["Lo"] = GeneralUnicodeCategory.OtherLetter, + ["L" ] = GeneralUnicodeCategory.Letter, + + // Marks + ["Mn"] = GeneralUnicodeCategory.NonSpacingMark, + ["Mc"] = GeneralUnicodeCategory.SpacingCombiningMark, + ["Me"] = GeneralUnicodeCategory.EnclosingMark, + ["M" ] = GeneralUnicodeCategory.Mark, + + // Numbers + ["Nd"] = GeneralUnicodeCategory.DecimalDigitNumber, + ["Nl"] = GeneralUnicodeCategory.LetterNumber, + ["No"] = GeneralUnicodeCategory.OtherNumber, + ["N" ] = GeneralUnicodeCategory.Number, + + // Separators + ["Zs"] = GeneralUnicodeCategory.SpaceSeparator, + ["Zl"] = GeneralUnicodeCategory.LineSeparator, + ["Zp"] = GeneralUnicodeCategory.ParagraphSeparator, + ["Z" ] = GeneralUnicodeCategory.Separator, + + // Controls + ["Cc"] = GeneralUnicodeCategory.Control, + ["Cf"] = GeneralUnicodeCategory.Format, + ["Cs"] = GeneralUnicodeCategory.Surrogate, + ["Co"] = GeneralUnicodeCategory.PrivateUse, + ["Cn"] = GeneralUnicodeCategory.OtherNotAssigned, + ["C" ] = GeneralUnicodeCategory.Other, + + // Punctuations + ["Pc"] = GeneralUnicodeCategory.ConnectorPunctuation, + ["Pd"] = GeneralUnicodeCategory.DashPunctuation, + ["Ps"] = GeneralUnicodeCategory.OpenPunctuation, + ["Pe"] = GeneralUnicodeCategory.ClosePunctuation, + ["Pi"] = GeneralUnicodeCategory.InitialQuotePunctuation, + ["Pf"] = GeneralUnicodeCategory.FinalQuotePunctuation, + ["Po"] = GeneralUnicodeCategory.OtherPunctuation, + ["P" ] = GeneralUnicodeCategory.Punctuation, + + // Symbols + ["Sm"] = GeneralUnicodeCategory.MathSymbol, + ["Sc"] = GeneralUnicodeCategory.CurrencySymbol, + ["Sk"] = GeneralUnicodeCategory.ModifierSymbol, + ["So"] = GeneralUnicodeCategory.OtherSymbol, + ["S" ] = GeneralUnicodeCategory.Symbol + }; + + /// + /// An array of Unicode category group abbreviations. + /// + public static readonly string[] CategoryGroups = ["L", "M", "N", "Z", "C", "P", "S"]; + + private readonly CharClassRange[] _source; + private readonly CharClassRange[] _ranges; + private readonly CharClassUnicodeCategory _categories; + + /// + /// Gets the source (original) ranges of characters. + /// + public CharClassRange[] Source => _source; + + /// + /// Gets the extended ranges of characters, which include additional + /// characters based on the specified Unicode categories. + /// + public CharClassRange[] Ranges => _ranges; + + /// + /// Gets the Unicode categories associated with this character class. + /// + public CharClassUnicodeCategory UnicodeCategories => _categories; + + /// + /// Gets the total number of unique symbols across all ranges. + /// + /// + /// This property calculates the sum of all symbols included in the ranges. + /// Each range contributes the count of its symbols, determined as Hi - Lo + 1. + /// + public int SymbolCount { get; } + + /// + /// Gets the total width between the lowest and highest characters across all ranges. + /// + /// + /// This property calculates the difference between the lowest character in the first range + /// and the highest character in the last range, inclusive. + /// The width represents the entire span covered by all ranges, including any gaps between them. + /// This value is useful for determining the required size of data structures (like bit arrays) + /// that need to represent the full range. + /// + public int OverallWidth { get; } + + /// + /// Initializes a new instance of the class using the specified elements. + /// + /// The character class elements to initialize from. + public CharClass(CharClassElement[] elements) + : this(ExtractRanges(elements), ExtractCategory(elements)) + { + } + + /// + /// Initializes a new instance of the class using the specified ranges. + /// + /// The character ranges to initialize from. + public CharClass(CharClassRange[] ranges) : this(ranges, ranges, default) + { + } + + /// + /// Initializes a new instance of the class using the specified ranges and Unicode categories. + /// + /// The character ranges to initialize from. + /// The Unicode categories to initialize from. + public CharClass(CharClassRange[] ranges, CharClassUnicodeCategory categories) : this(ranges, ranges, categories) + { + } + + /// + /// Initializes a new instance of the class using the specified Unicode categories. + /// + /// The Unicode categories to initialize from. + public CharClass(CharClassUnicodeCategory categories) : this([], [], categories) + { + } + + /// + /// Initializes a new instance of the class using the specified source ranges, + /// optimized ranges, and Unicode categories. + /// + /// The source character ranges. + /// The optimized character ranges. + /// The Unicode categories. + private CharClass(CharClassRange[] source, CharClassRange[] ranges, CharClassUnicodeCategory categories) + { + Array.Sort(ranges); + + // If Unicode categories are specified, extend the lower ranges + // with characters belonging to the specified categories. + if (categories.Value != 0 && ranges.Length != 0) + ranges = EnrichRangesCore(ranges, categories); + + source = source.Optimize(); + ranges = ranges.Optimize(); + + if (ranges.AsSpan().SequenceEqual(source.AsSpan())) + source = ranges; + + if (ranges is [{ IsFullRange: true }]) + categories = default; + + foreach (var range in ranges) + SymbolCount += range.SymbolCount; + + if (ranges.Length != 0) + OverallWidth = ranges[^1].Hi - ranges[0].Lo + 1; + + (_source, _ranges, _categories) = (source, ranges, categories); + } + + /// + /// Returns a string containing all characters represented in the current character class. + /// + /// If set to , the length of the resulting string is aligned + /// to the nearest multiple of a SIMD-friendly size (16 or 32 bytes) if SIMD instructions are supported. + /// + /// + /// A string consisting of all characters within the defined ranges of this instance. + /// + public string GetSymbols(bool simdAligned = false) + { + var align = Avx2.IsSupported + ? 16 + : Sse41.IsSupported || AdvSimd.IsSupported + ? 8 + : 0; + + var count = simdAligned && align != 0 + ? SymbolCount + (align - 1) & -align + : SymbolCount; + + return string.Create(count, (align, Ranges), (span, state) => + { + var i = 0; + foreach (var (lo, hi) in state.Ranges) + for (int c = lo; c <= hi; c++) + if ((uint)i < (uint)span.Length) + span[i++] = (char)c; + + while ((uint)i < (uint)span.Length) + span[i++] = span[0]; + }); + } + + /// + /// Merges this character class with another character class. + /// + /// The other character class to merge with. + /// + /// A new representing the merged result. + /// + public CharClass MergeClasses(CharClass other) + { + return new CharClass( + [.._source, ..other._source], + [.._ranges, ..other._ranges], + _categories.Combine(other._categories) + ); + } + + /// + /// Populates the character class ranges with characters from the specified Unicode categories, + /// ensuring that the ranges contain all relevant characters for optimized processing. + /// + /// + /// A new instance with updated character ranges that include + /// characters from the specified Unicode categories. + /// If no Unicode categories are specified, returns the current instance unchanged. + /// + public CharClass EnrichRanges() + { + if (_categories.IsEmpty) + return this; + + var ranges = EnrichRangesCore(_ranges, _categories); + return new CharClass(_source, ranges, _categories); + } + + /// + /// Generates an extended set of character class ranges by adding characters from the specified Unicode categories. + /// This is useful for cases where Unicode category-based classification is needed in addition to predefined ranges. + /// + /// The Unicode character categories whose characters should be added to the ranges. + /// The existing character class ranges that will be extended. + /// + /// An array of that includes the original ranges + /// and additional characters from the specified Unicode categories. + /// + private static CharClassRange[] EnrichRangesCore(CharClassRange[] ranges, CharClassUnicodeCategory categories) + { + Debug.Assert(!categories.IsEmpty); + + var builder = new ArrayBuilder(); + + // Identify used ASCII characters. + for (var c = '\0'; c < 128; c++) + if (categories.IsInclude(c)) + builder.Add(CharClassRange.Create(c)); + + // Characters in the range [128..511]. + // Only if the specified range is used. + if (ranges.Length != 0 && (int)ranges[^1].Hi is >= 128 and < 512) + for (var c = (char)128; c < 512; c++) + if (categories.IsInclude(c)) + builder.Add(CharClassRange.Create(c)); + + // + // TODO Should we populate non-ASCII ranges if no ASCII values are present? + // + + builder.AddRange(ranges); + return builder.ToArray(); + } + + /// + public override string ToString() => + this.ToPrintable(); + + /// + /// Extracts Unicode categories from the specified elements. + /// + /// The elements to extract categories from. + /// + /// A representing the combined categories. + /// + private static CharClassUnicodeCategory ExtractCategory(CharClassElement[] elements) + { + CharClassUnicodeCategory value = default; + foreach (var element in elements) + if (element.TryGetUnicodeCategory(out var category)) + value = value.Combine(category); + + return value; + } + + /// + /// Extracts character ranges from the specified elements. + /// + /// The elements to extract ranges from. + /// + /// An array of representing the extracted ranges. + /// + private static CharClassRange[] ExtractRanges(CharClassElement[] elements) + { + var builder = new ArrayBuilder(); + foreach (var element in elements) + if (element.TryGetRange(out var range)) + builder.Add(range); + + return builder.ToArray(); + } +} diff --git a/src/Ramstack.Parsing/Utilities/CharClassElement.cs b/src/Ramstack.Parsing/Utilities/CharClassElement.cs index d2e5263..e301bb9 100644 --- a/src/Ramstack.Parsing/Utilities/CharClassElement.cs +++ b/src/Ramstack.Parsing/Utilities/CharClassElement.cs @@ -1,138 +1,138 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents an element of a character class, which can either be a range of characters or a Unicode category. -/// -internal readonly struct CharClassElement -{ - private readonly long _value; - - /// - /// Gets a value indicating whether the element represents a character range. - /// - public bool IsRange => _value >>> 63 != 0; - - /// - /// Gets a value indicating whether the element represents a Unicode category. - /// - public bool IsUnicodeCategory => _value >>> 63 == 0; - - /// - /// Initializes a new instance of the struct with the specified value. - /// - /// The raw value representing the character class element. - private CharClassElement(long value) => - _value = value; - - /// - /// Attempts to retrieve the Unicode category represented by this element. - /// - /// When this method returns, contains the if successful; - /// otherwise, the default value. - /// - /// if the element represents a Unicode category; - /// otherwise, . - /// - public bool TryGetUnicodeCategory(out CharClassUnicodeCategory category) - { - return TryGetUnicodeCategoryImpl(_value, out category); - - static bool TryGetUnicodeCategoryImpl(long value, out CharClassUnicodeCategory category) - { - if (value >>> 63 == 0) - { - category = CharClassUnicodeCategory.Create((int)(value >>> 32)); - return true; - } - - category = default; - return false; - } - } - - /// - /// Attempts to retrieve the character range represented by this element. - /// - /// When this method returns, contains the if successful; - /// otherwise, the default value. - /// - /// if the element represents a character range; - /// otherwise, . - /// - public bool TryGetRange(out CharClassRange range) - { - return TryGetRangeImpl(_value, out range); - - static bool TryGetRangeImpl(long value, out CharClassRange range) - { - if (value >>> 63 != 0) - { - range = CharClassRange.CreateMasked((int)value); - return true; - } - - range = default; - return false; - } - } - - /// - /// Creates a new representing the specified character range. - /// - /// The character range to represent. - /// - /// A new representing the specified character range. - /// - public static CharClassElement Create(CharClassRange range) => - new CharClassElement((1L << 63) | (uint)range.Value); - - /// - /// Creates a new representing the specified Unicode category. - /// - /// The Unicode category to represent. - /// - /// A new representing the specified Unicode category. - /// - public static CharClassElement Create(CharClassUnicodeCategory category) => - Create((GeneralUnicodeCategory)category.Value); - - /// - /// Creates a new representing the specified general Unicode category. - /// - /// The general Unicode category to represent. - /// - /// A new representing the specified Unicode category. - /// - public static CharClassElement Create(GeneralUnicodeCategory category) => - new CharClassElement((long)(uint)category << 32); - - /// - /// Implicitly converts a to a . - /// - /// The to convert. - /// - /// A representing the specified character range. - /// - public static implicit operator CharClassElement(CharClassRange range) => - Create(range); - - /// - /// Implicitly converts a to a . - /// - /// The to convert. - /// - /// A representing the specified Unicode category. - /// - public static implicit operator CharClassElement(CharClassUnicodeCategory category) => - Create((GeneralUnicodeCategory)category.Value); - - /// - /// Implicitly converts a to a . - /// - /// The to convert. - /// - /// A representing the specified Unicode category. - /// - public static implicit operator CharClassElement(GeneralUnicodeCategory category) => - Create(category); -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents an element of a character class, which can either be a range of characters or a Unicode category. +/// +internal readonly struct CharClassElement +{ + private readonly long _value; + + /// + /// Gets a value indicating whether the element represents a character range. + /// + public bool IsRange => _value >>> 63 != 0; + + /// + /// Gets a value indicating whether the element represents a Unicode category. + /// + public bool IsUnicodeCategory => _value >>> 63 == 0; + + /// + /// Initializes a new instance of the struct with the specified value. + /// + /// The raw value representing the character class element. + private CharClassElement(long value) => + _value = value; + + /// + /// Attempts to retrieve the Unicode category represented by this element. + /// + /// When this method returns, contains the if successful; + /// otherwise, the default value. + /// + /// if the element represents a Unicode category; + /// otherwise, . + /// + public bool TryGetUnicodeCategory(out CharClassUnicodeCategory category) + { + return TryGetUnicodeCategoryImpl(_value, out category); + + static bool TryGetUnicodeCategoryImpl(long value, out CharClassUnicodeCategory category) + { + if (value >>> 63 == 0) + { + category = CharClassUnicodeCategory.Create((int)(value >>> 32)); + return true; + } + + category = default; + return false; + } + } + + /// + /// Attempts to retrieve the character range represented by this element. + /// + /// When this method returns, contains the if successful; + /// otherwise, the default value. + /// + /// if the element represents a character range; + /// otherwise, . + /// + public bool TryGetRange(out CharClassRange range) + { + return TryGetRangeImpl(_value, out range); + + static bool TryGetRangeImpl(long value, out CharClassRange range) + { + if (value >>> 63 != 0) + { + range = CharClassRange.CreateMasked((int)value); + return true; + } + + range = default; + return false; + } + } + + /// + /// Creates a new representing the specified character range. + /// + /// The character range to represent. + /// + /// A new representing the specified character range. + /// + public static CharClassElement Create(CharClassRange range) => + new CharClassElement((1L << 63) | (uint)range.Value); + + /// + /// Creates a new representing the specified Unicode category. + /// + /// The Unicode category to represent. + /// + /// A new representing the specified Unicode category. + /// + public static CharClassElement Create(CharClassUnicodeCategory category) => + Create((GeneralUnicodeCategory)category.Value); + + /// + /// Creates a new representing the specified general Unicode category. + /// + /// The general Unicode category to represent. + /// + /// A new representing the specified Unicode category. + /// + public static CharClassElement Create(GeneralUnicodeCategory category) => + new CharClassElement((long)(uint)category << 32); + + /// + /// Implicitly converts a to a . + /// + /// The to convert. + /// + /// A representing the specified character range. + /// + public static implicit operator CharClassElement(CharClassRange range) => + Create(range); + + /// + /// Implicitly converts a to a . + /// + /// The to convert. + /// + /// A representing the specified Unicode category. + /// + public static implicit operator CharClassElement(CharClassUnicodeCategory category) => + Create((GeneralUnicodeCategory)category.Value); + + /// + /// Implicitly converts a to a . + /// + /// The to convert. + /// + /// A representing the specified Unicode category. + /// + public static implicit operator CharClassElement(GeneralUnicodeCategory category) => + Create(category); +} diff --git a/src/Ramstack.Parsing/Utilities/CharClassExtensions.cs b/src/Ramstack.Parsing/Utilities/CharClassExtensions.cs index ce917b2..3b1b20d 100644 --- a/src/Ramstack.Parsing/Utilities/CharClassExtensions.cs +++ b/src/Ramstack.Parsing/Utilities/CharClassExtensions.cs @@ -1,401 +1,401 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Provides a collection of extension methods for performing various operations -/// on characters and character-related data structures. -/// -internal static class CharClassExtensions -{ - /// - /// A mask that includes all valid values. - /// Used for category normalization. - /// - public const GeneralUnicodeCategory NormalizedCategoryMask = - GeneralUnicodeCategory.Letter - | GeneralUnicodeCategory.Mark - | GeneralUnicodeCategory.Number - | GeneralUnicodeCategory.Separator - | GeneralUnicodeCategory.Punctuation - | GeneralUnicodeCategory.Symbol - | GeneralUnicodeCategory.Other; - - /// - /// A constant representing a collection of Unicode categories that are considered as printable characters. - /// - public static readonly CharClassUnicodeCategory PrintableCategories = - GeneralUnicodeCategory.UppercaseLetter - | GeneralUnicodeCategory.LowercaseLetter - | GeneralUnicodeCategory.TitlecaseLetter - | GeneralUnicodeCategory.DecimalDigitNumber - | GeneralUnicodeCategory.LetterNumber - | GeneralUnicodeCategory.OtherNumber - | GeneralUnicodeCategory.DashPunctuation - | GeneralUnicodeCategory.OpenPunctuation - | GeneralUnicodeCategory.ClosePunctuation - | GeneralUnicodeCategory.ConnectorPunctuation - | GeneralUnicodeCategory.OtherPunctuation - | GeneralUnicodeCategory.InitialQuotePunctuation - | GeneralUnicodeCategory.FinalQuotePunctuation - | GeneralUnicodeCategory.SpaceSeparator - | GeneralUnicodeCategory.MathSymbol - | GeneralUnicodeCategory.CurrencySymbol; - - /// - /// Optimizes a specified array of character ranges by merging overlapping or adjacent ranges. - /// - /// The array of character ranges to optimize. - /// - /// A new array with optimized character ranges. - /// - public static CharClassRange[] Optimize(this CharClassRange[] ranges) - { - if (CanOptimize(ranges)) - ranges = Optimize(ranges.AsSpan()); - - return ranges; - } - - /// - /// Optimizes a specified span of character ranges by merging overlapping or adjacent ranges. - /// - /// The span of character ranges to optimize. - /// - /// A new array with optimized character ranges. - /// - public static CharClassRange[] Optimize(this Span ranges) - { - if (!CanOptimize(ranges)) - return ranges.ToArray(); - - var builder = new ArrayBuilder(); - foreach (var range in ranges) - { - if (builder.Count != 0) - { - var (lo, hi) = builder[^1]; - - if (range.Lo <= hi + 1) - { - if (range.Hi > hi) - hi = range.Hi; - - builder[^1] = CharClassRange.CreateMasked(lo | (hi << 16)); - continue; - } - } - - builder.Add(range); - } - - return builder.ToArray(); - } - - // /// - // /// Inverts the character ranges. - // /// - // /// The array of character ranges to invert. - // /// - // /// A new array with inverted character ranges. - // /// - // public static CharClassRange[] Invert(this CharClassRange[] ranges) => - // Invert(ranges.AsSpan()); - // - // /// - // /// Inverts the character ranges. - // /// - // /// The span of character ranges to invert. - // /// - // /// A new array with inverted character ranges. - // /// - // public static CharClassRange[] Invert(this Span ranges) - // { - // var array = new ArrayBuilder(); - // var start = 0; - // - // foreach (var (lo, hi) in ranges) - // { - // if (lo - 1 >= start) - // { - // var range = CharClassRange.Create((char)start, (char)(lo - 1)); - // array.Add(range); - // } - // - // start = hi + 1; - // if (start > char.MaxValue) - // break; - // } - // - // if (start <= char.MaxValue) - // { - // var range = CharClassRange.Create((char)start, '\uFFFF'); - // array.Add(range); - // } - // - // return array.ToArray(); - // } - - /// - /// Determines if the specified array of character ranges can be optimized by merging overlapping or adjacent ranges. - /// - /// The array of character ranges to check. - /// - /// if the ranges can be optimized, otherwise . - /// - public static bool CanOptimize(this CharClassRange[] ranges) => - CanOptimize(ranges.AsSpan()); - - /// - /// Determines if the specified span of character ranges can be optimized by merging overlapping or adjacent ranges. - /// - /// The span of character ranges to check. - /// - /// if the ranges can be optimized, otherwise . - /// - public static bool CanOptimize(this Span ranges) - { - ranges.Sort(); - - for (var i = 1; i < ranges.Length; i++) - if (ranges[i - 1].Hi + 1 >= ranges[i].Lo) - return true; - - return false; - } - - /// - /// Determines whether a given character is printable. - /// - /// The character to check. - /// - /// if the character is printable, otherwise . - /// - public static bool IsPrintable(this char c) => - c is >= ' ' and <= '~' || PrintableCategories.IsInclude(c); - - /// - /// Returns the printable string representation of the specified character. - /// - /// The character to format. - /// The control characters that should be escaped. - /// - /// The printable string representation of the specified character. - /// - public static string ToPrintable(this char c, string controls = "'") - { - return c switch - { - '\0' => @"'\0'", - '\a' => @"'\a'", - '\b' => @"'\b'", - '\e' => @"'\e'", - '\f' => @"'\f'", - '\n' => @"'\n'", - '\r' => @"'\r'", - '\t' => @"'\t'", - '\v' => @"'\v'", - '\'' => @"'\''", - '\\' => @"'\\'", - _ when controls.Contains(c) => $"'\\{c}'", - _ when IsPrintable(c) => $"'{c}'", - _ => $"'\\u{(ushort)c:X4}'" - }; - } - - /// - /// Returns the printable string representation of the specified range of characters. - /// - /// The range of characters to format. - /// - /// The printable string representation of the specified range of characters. - /// - public static string ToPrintable(this CharClassRange range) - { - if (range.IsSingleCharacter) - return range.Lo.ToPrintable(); - - var sb = new StringBuffer(); - sb.Append('['); - Format(ref sb, range.Lo, "-"); - if (range.Lo + 1 < range.Hi) - sb.Append('-'); - Format(ref sb, range.Hi, "-"); - sb.Append(']'); - return sb.ToString(); - } - - /// - /// Returns the printable string representation of the specified Unicode categories. - /// - /// The Unicode categories to format. - /// - /// The printable string representation of the specified Unicode categories. - /// - public static string ToPrintable(this GeneralUnicodeCategory category) - { - category &= NormalizedCategoryMask; - if (category == 0) - return ""; - - var sb = new StringBuffer(); - Format(ref sb, category); - return sb.ToString(); - } - - /// - /// Returns the printable string representation of the specified string literal. - /// - /// The string literal to format. - /// - /// The printable string representation of the specified string literal. - /// - public static string ToPrintable(this string literal) - { - var sb = new StringBuffer(); - sb.Append('\''); - foreach (var c in literal) - Format(ref sb, c, "'"); - - sb.Append('\''); - return sb.ToString(); - } - - /// - /// Returns the printable string representation of the specified object. - /// - /// The object to format. - /// - /// The printable string representation of the specified object. - /// - internal static string ToPrintable(this CharClass @class) - { - if (@class is { Source: [{ IsSingleCharacter: true }], UnicodeCategories.IsEmpty: true }) - return @class.Source[0].ToPrintable(); - - var sb = new StringBuffer(); - sb.Append('['); - - var ranges = @class.Source; - - for (var i = 0; i < ranges.Length; i++) - { - var (lo, hi) = ranges[i]; - if (lo != hi) - { - Format(ref sb, lo, "-"); - if (lo + 1 < hi) - sb.Append('-'); - Format(ref sb, hi, "-"); - } - else - { - var controls = i + 1 != ranges.Length ? "-" : ""; - Format(ref sb, lo, controls); - } - } - - Format(ref sb, (GeneralUnicodeCategory)@class.UnicodeCategories.Value); - - sb.Append(']'); - return sb.ToString(); - } - - /// - /// Formats the specified character as a printable string. - /// - /// The buffer to append the formatted string to. - /// The character to format. - /// The control characters that should be escaped. - private static void Format(ref StringBuffer sb, char c, string controls = "") - { - var s = c switch - { - '\0' => @"\0", - '\a' => @"\a", - '\b' => @"\b", - '\e' => @"\e", - '\f' => @"\f", - '\n' => @"\n", - '\r' => @"\r", - '\t' => @"\t", - '\v' => @"\v", - '\'' => @"\'", - '\\' => @"\\", - _ when controls.Contains(c) => "\\" + c, - _ => null - }; - - if (s is not null) - { - sb.Append(s); - } - else if (IsPrintable(c)) - { - sb.Append(c); - } - else - { - sb.Append("\\u"); - sb.Append(((ushort)c).ToString("x4")); - } - } - - /// - /// Formats the specified Unicode categories as a printable string. - /// - /// The buffer to append the formatted string to. - /// The Unicode categories to format. - private static void Format(ref StringBuffer sb, GeneralUnicodeCategory category) - { - category &= NormalizedCategoryMask; - - foreach (var group in CharClass.CategoryGroups) - { - if (category == 0) - return; - - if (!CharClass.Categories.TryGetValue(group, out var c)) - continue; - - var inverted = ~c & NormalizedCategoryMask; - if ((category & inverted) != inverted) - continue; - - sb.Append(@"\P{"); - sb.Append(group); - sb.Append('}'); - category &= c; - } - - foreach (var group in CharClass.CategoryGroups) - { - if (category == 0) - return; - - if (!CharClass.Categories.TryGetValue(group, out var c)) - continue; - - if ((category & c) != c) - continue; - - sb.Append(@"\p{"); - sb.Append(group); - sb.Append('}'); - category &= ~c; - } - - foreach (var (group, c) in CharClass.Categories) - { - if (category == 0) - return; - - if ((category & c) == c) - { - sb.Append(@"\p{"); - sb.Append(group); - sb.Append('}'); - - category &= ~c; - } - } - } -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Provides a collection of extension methods for performing various operations +/// on characters and character-related data structures. +/// +internal static class CharClassExtensions +{ + /// + /// A mask that includes all valid values. + /// Used for category normalization. + /// + public const GeneralUnicodeCategory NormalizedCategoryMask = + GeneralUnicodeCategory.Letter + | GeneralUnicodeCategory.Mark + | GeneralUnicodeCategory.Number + | GeneralUnicodeCategory.Separator + | GeneralUnicodeCategory.Punctuation + | GeneralUnicodeCategory.Symbol + | GeneralUnicodeCategory.Other; + + /// + /// A constant representing a collection of Unicode categories that are considered as printable characters. + /// + public static readonly CharClassUnicodeCategory PrintableCategories = + GeneralUnicodeCategory.UppercaseLetter + | GeneralUnicodeCategory.LowercaseLetter + | GeneralUnicodeCategory.TitlecaseLetter + | GeneralUnicodeCategory.DecimalDigitNumber + | GeneralUnicodeCategory.LetterNumber + | GeneralUnicodeCategory.OtherNumber + | GeneralUnicodeCategory.DashPunctuation + | GeneralUnicodeCategory.OpenPunctuation + | GeneralUnicodeCategory.ClosePunctuation + | GeneralUnicodeCategory.ConnectorPunctuation + | GeneralUnicodeCategory.OtherPunctuation + | GeneralUnicodeCategory.InitialQuotePunctuation + | GeneralUnicodeCategory.FinalQuotePunctuation + | GeneralUnicodeCategory.SpaceSeparator + | GeneralUnicodeCategory.MathSymbol + | GeneralUnicodeCategory.CurrencySymbol; + + /// + /// Optimizes a specified array of character ranges by merging overlapping or adjacent ranges. + /// + /// The array of character ranges to optimize. + /// + /// A new array with optimized character ranges. + /// + public static CharClassRange[] Optimize(this CharClassRange[] ranges) + { + if (CanOptimize(ranges)) + ranges = Optimize(ranges.AsSpan()); + + return ranges; + } + + /// + /// Optimizes a specified span of character ranges by merging overlapping or adjacent ranges. + /// + /// The span of character ranges to optimize. + /// + /// A new array with optimized character ranges. + /// + public static CharClassRange[] Optimize(this Span ranges) + { + if (!CanOptimize(ranges)) + return ranges.ToArray(); + + var builder = new ArrayBuilder(); + foreach (var range in ranges) + { + if (builder.Count != 0) + { + var (lo, hi) = builder[^1]; + + if (range.Lo <= hi + 1) + { + if (range.Hi > hi) + hi = range.Hi; + + builder[^1] = CharClassRange.CreateMasked(lo | (hi << 16)); + continue; + } + } + + builder.Add(range); + } + + return builder.ToArray(); + } + + // /// + // /// Inverts the character ranges. + // /// + // /// The array of character ranges to invert. + // /// + // /// A new array with inverted character ranges. + // /// + // public static CharClassRange[] Invert(this CharClassRange[] ranges) => + // Invert(ranges.AsSpan()); + // + // /// + // /// Inverts the character ranges. + // /// + // /// The span of character ranges to invert. + // /// + // /// A new array with inverted character ranges. + // /// + // public static CharClassRange[] Invert(this Span ranges) + // { + // var array = new ArrayBuilder(); + // var start = 0; + // + // foreach (var (lo, hi) in ranges) + // { + // if (lo - 1 >= start) + // { + // var range = CharClassRange.Create((char)start, (char)(lo - 1)); + // array.Add(range); + // } + // + // start = hi + 1; + // if (start > char.MaxValue) + // break; + // } + // + // if (start <= char.MaxValue) + // { + // var range = CharClassRange.Create((char)start, '\uFFFF'); + // array.Add(range); + // } + // + // return array.ToArray(); + // } + + /// + /// Determines if the specified array of character ranges can be optimized by merging overlapping or adjacent ranges. + /// + /// The array of character ranges to check. + /// + /// if the ranges can be optimized, otherwise . + /// + public static bool CanOptimize(this CharClassRange[] ranges) => + CanOptimize(ranges.AsSpan()); + + /// + /// Determines if the specified span of character ranges can be optimized by merging overlapping or adjacent ranges. + /// + /// The span of character ranges to check. + /// + /// if the ranges can be optimized, otherwise . + /// + public static bool CanOptimize(this Span ranges) + { + ranges.Sort(); + + for (var i = 1; i < ranges.Length; i++) + if (ranges[i - 1].Hi + 1 >= ranges[i].Lo) + return true; + + return false; + } + + /// + /// Determines whether a given character is printable. + /// + /// The character to check. + /// + /// if the character is printable, otherwise . + /// + public static bool IsPrintable(this char c) => + c is >= ' ' and <= '~' || PrintableCategories.IsInclude(c); + + /// + /// Returns the printable string representation of the specified character. + /// + /// The character to format. + /// The control characters that should be escaped. + /// + /// The printable string representation of the specified character. + /// + public static string ToPrintable(this char c, string controls = "'") + { + return c switch + { + '\0' => @"'\0'", + '\a' => @"'\a'", + '\b' => @"'\b'", + '\e' => @"'\e'", + '\f' => @"'\f'", + '\n' => @"'\n'", + '\r' => @"'\r'", + '\t' => @"'\t'", + '\v' => @"'\v'", + '\'' => @"'\''", + '\\' => @"'\\'", + _ when controls.Contains(c) => $"'\\{c}'", + _ when IsPrintable(c) => $"'{c}'", + _ => $"'\\u{(ushort)c:X4}'" + }; + } + + /// + /// Returns the printable string representation of the specified range of characters. + /// + /// The range of characters to format. + /// + /// The printable string representation of the specified range of characters. + /// + public static string ToPrintable(this CharClassRange range) + { + if (range.IsSingleCharacter) + return range.Lo.ToPrintable(); + + var sb = new StringBuffer(); + sb.Append('['); + Format(ref sb, range.Lo, "-"); + if (range.Lo + 1 < range.Hi) + sb.Append('-'); + Format(ref sb, range.Hi, "-"); + sb.Append(']'); + return sb.ToString(); + } + + /// + /// Returns the printable string representation of the specified Unicode categories. + /// + /// The Unicode categories to format. + /// + /// The printable string representation of the specified Unicode categories. + /// + public static string ToPrintable(this GeneralUnicodeCategory category) + { + category &= NormalizedCategoryMask; + if (category == 0) + return ""; + + var sb = new StringBuffer(); + Format(ref sb, category); + return sb.ToString(); + } + + /// + /// Returns the printable string representation of the specified string literal. + /// + /// The string literal to format. + /// + /// The printable string representation of the specified string literal. + /// + public static string ToPrintable(this string literal) + { + var sb = new StringBuffer(); + sb.Append('\''); + foreach (var c in literal) + Format(ref sb, c, "'"); + + sb.Append('\''); + return sb.ToString(); + } + + /// + /// Returns the printable string representation of the specified object. + /// + /// The object to format. + /// + /// The printable string representation of the specified object. + /// + internal static string ToPrintable(this CharClass @class) + { + if (@class is { Source: [{ IsSingleCharacter: true }], UnicodeCategories.IsEmpty: true }) + return @class.Source[0].ToPrintable(); + + var sb = new StringBuffer(); + sb.Append('['); + + var ranges = @class.Source; + + for (var i = 0; i < ranges.Length; i++) + { + var (lo, hi) = ranges[i]; + if (lo != hi) + { + Format(ref sb, lo, "-"); + if (lo + 1 < hi) + sb.Append('-'); + Format(ref sb, hi, "-"); + } + else + { + var controls = i + 1 != ranges.Length ? "-" : ""; + Format(ref sb, lo, controls); + } + } + + Format(ref sb, (GeneralUnicodeCategory)@class.UnicodeCategories.Value); + + sb.Append(']'); + return sb.ToString(); + } + + /// + /// Formats the specified character as a printable string. + /// + /// The buffer to append the formatted string to. + /// The character to format. + /// The control characters that should be escaped. + private static void Format(ref StringBuffer sb, char c, string controls = "") + { + var s = c switch + { + '\0' => @"\0", + '\a' => @"\a", + '\b' => @"\b", + '\e' => @"\e", + '\f' => @"\f", + '\n' => @"\n", + '\r' => @"\r", + '\t' => @"\t", + '\v' => @"\v", + '\'' => @"\'", + '\\' => @"\\", + _ when controls.Contains(c) => "\\" + c, + _ => null + }; + + if (s is not null) + { + sb.Append(s); + } + else if (IsPrintable(c)) + { + sb.Append(c); + } + else + { + sb.Append("\\u"); + sb.Append(((ushort)c).ToString("x4")); + } + } + + /// + /// Formats the specified Unicode categories as a printable string. + /// + /// The buffer to append the formatted string to. + /// The Unicode categories to format. + private static void Format(ref StringBuffer sb, GeneralUnicodeCategory category) + { + category &= NormalizedCategoryMask; + + foreach (var group in CharClass.CategoryGroups) + { + if (category == 0) + return; + + if (!CharClass.Categories.TryGetValue(group, out var c)) + continue; + + var inverted = ~c & NormalizedCategoryMask; + if ((category & inverted) != inverted) + continue; + + sb.Append(@"\P{"); + sb.Append(group); + sb.Append('}'); + category &= c; + } + + foreach (var group in CharClass.CategoryGroups) + { + if (category == 0) + return; + + if (!CharClass.Categories.TryGetValue(group, out var c)) + continue; + + if ((category & c) != c) + continue; + + sb.Append(@"\p{"); + sb.Append(group); + sb.Append('}'); + category &= ~c; + } + + foreach (var (group, c) in CharClass.Categories) + { + if (category == 0) + return; + + if ((category & c) == c) + { + sb.Append(@"\p{"); + sb.Append(group); + sb.Append('}'); + + category &= ~c; + } + } + } +} diff --git a/src/Ramstack.Parsing/Utilities/CharClassParser.cs b/src/Ramstack.Parsing/Utilities/CharClassParser.cs index 0a7b033..4f5ca88 100644 --- a/src/Ramstack.Parsing/Utilities/CharClassParser.cs +++ b/src/Ramstack.Parsing/Utilities/CharClassParser.cs @@ -1,220 +1,220 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a character class expression parser. -/// -internal static class CharClassParser -{ - /// - /// Parses a character class from the specified regular expression pattern. - /// - /// The string containing the character class to parse. - /// - /// A object representing the parsed character class. - /// - public static CharClass Parse(string pattern) - { - // // TODO: Add special handling for patterns enclosed in square brackets - // // Temporary workaround to handle patterns enclosed in square brackets. - // if (pattern is ['[', .., ']']) - // pattern = pattern[1..^1]; - - Argument.ThrowIfNullOrEmpty(nameof(pattern)); - - var elements = new List(); - var p = 0; - - while (p < pattern.Length) - { - if (ProcessCategory()) - continue; - - if (ProcessShorthand()) - continue; - - ProcessRange(); - } - - return new CharClass(elements.ToArray()); - - bool ProcessCategory() - { - var s = pattern.AsSpan(p); - if (!s.StartsWith("\\p{") && !s.StartsWith("\\P{")) - return false; - - var end = pattern.IndexOf('}', p + 3); - if (end < 0) - Error_InvalidPattern(pattern); - - // Named Unicode blocks are not supported. Use explicit character ranges instead. - // Unicode Block Names: https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt - - var key = pattern[(p + 3)..end]; - - if (!CharClass.Categories.TryGetValue(key, out var category)) - Error_UnknownProperty(pattern, key); - - if (pattern[p + 1] == 'P') - category = ~category; - - p = end + 1; - - var element = CharClassUnicodeCategory.Create(category); - elements.Add(element); - return true; - } - - bool ProcessShorthand() - { - var s = pattern.AsSpan(p); - if (s.Length > 1 && s[0] == '\\') - { - switch (s[1]) - { - case 's': - elements.Add(CharClassRange.Create('\u0009')); - elements.Add(CharClassRange.Create('\u000a')); - elements.Add(CharClassRange.Create('\u000b')); - elements.Add(CharClassRange.Create('\u000c')); - elements.Add(CharClassRange.Create('\u000d')); - elements.Add(CharClassRange.Create('\u0020')); - elements.Add(CharClassRange.Create('\u0085')); - elements.Add( - CharClassUnicodeCategory.Create( - GeneralUnicodeCategory.Separator)); - - p += 2; - return true; - - case 'S': - elements.Add(CharClassRange.Create('\u0000', '\u0008')); - elements.Add(CharClassRange.Create('\u000e', '\u001f')); - elements.Add(CharClassRange.Create('\u0021', '\u0084')); - elements.Add( - CharClassUnicodeCategory.Create( - ~GeneralUnicodeCategory.Separator)); - - p += 2; - return true; - - case 'd': - case 'D': - var digits = GeneralUnicodeCategory.DecimalDigitNumber; - if (s[1] == 'D') - digits = ~digits; - - elements.Add( - CharClassUnicodeCategory.Create( - digits)); - - p += 2; - return true; - - case 'w': - case 'W': - var word = GeneralUnicodeCategory.Letter - | GeneralUnicodeCategory.NonSpacingMark - | GeneralUnicodeCategory.DecimalDigitNumber - | GeneralUnicodeCategory.ConnectorPunctuation; - - if (s[1] == 'W') - word = ~word; - - elements.Add( - CharClassUnicodeCategory.Create( - word)); - - p += 2; - return true; - } - } - - return false; - } - - void ProcessRange() - { - var lo = ParseSymbol(); - var hi = lo; - - if (p + 1 < pattern.Length && pattern[p] == '-') - { - p++; - - hi = ParseSymbol(); - if (lo > hi) - Error_InvalidRange(pattern); - } - - var range = CharClassRange.Create(lo, hi); - elements.Add(range); - } - - char ParseSymbol() - { - var s = pattern.AsSpan(p); - if (s.StartsWith(@"\u")) - return ParseUnicodeSequence(); - - if (s[0] == '\\') - return ParseEscapeSequence(); - - p++; - return s[0]; - } - - char ParseEscapeSequence() - { - if ((uint)p + 1 >= (uint)pattern.Length - || !"0abeftnvr\\-".Contains(pattern[++p])) - Error_InvalidEscapeSequence(pattern); - - return pattern[p++] switch - { - '0' => '\0', - 'a' => '\a', - 'b' => '\b', - 'e' => '\u001b', - 'f' => '\f', - 't' => '\t', - 'n' => '\n', - 'v' => '\v', - 'r' => '\r', - var c => c - }; - } - - char ParseUnicodeSequence() - { - if (p + 5 >= pattern.Length) - Error_InvalidUnicodeSequence(pattern); - - if (!uint.TryParse(pattern.AsSpan(p + 2, 4), NumberStyles.AllowHexSpecifier, null, out var n)) - Error_InvalidUnicodeSequence(pattern); - - p += 6; - return (char)n; - } - } - - [DoesNotReturn] - private static void Error_InvalidPattern(string pattern) => - throw new ArgumentException($"Invalid pattern '{pattern}'."); - - [DoesNotReturn] - private static void Error_InvalidEscapeSequence(string pattern) => - throw new ArgumentException($"Invalid pattern '{pattern}'. Unrecognized escape sequence."); - - [DoesNotReturn] - private static void Error_InvalidUnicodeSequence(string pattern) => - throw new ArgumentException($"Invalid pattern '{pattern}'. Invalid Unicode escape sequence."); - - [DoesNotReturn] - private static void Error_UnknownProperty(string pattern, string name) => - throw new ArgumentException($"Invalid pattern '{pattern}'. Unknown property '{name}'."); - - [DoesNotReturn] - private static void Error_InvalidRange(string pattern) => - throw new ArgumentException($"Invalid pattern '{pattern}'. Range in reverse order."); -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a character class expression parser. +/// +internal static class CharClassParser +{ + /// + /// Parses a character class from the specified regular expression pattern. + /// + /// The string containing the character class to parse. + /// + /// A object representing the parsed character class. + /// + public static CharClass Parse(string pattern) + { + // // TODO: Add special handling for patterns enclosed in square brackets + // // Temporary workaround to handle patterns enclosed in square brackets. + // if (pattern is ['[', .., ']']) + // pattern = pattern[1..^1]; + + Argument.ThrowIfNullOrEmpty(nameof(pattern)); + + var elements = new List(); + var p = 0; + + while (p < pattern.Length) + { + if (ProcessCategory()) + continue; + + if (ProcessShorthand()) + continue; + + ProcessRange(); + } + + return new CharClass(elements.ToArray()); + + bool ProcessCategory() + { + var s = pattern.AsSpan(p); + if (!s.StartsWith("\\p{") && !s.StartsWith("\\P{")) + return false; + + var end = pattern.IndexOf('}', p + 3); + if (end < 0) + Error_InvalidPattern(pattern); + + // Named Unicode blocks are not supported. Use explicit character ranges instead. + // Unicode Block Names: https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt + + var key = pattern[(p + 3)..end]; + + if (!CharClass.Categories.TryGetValue(key, out var category)) + Error_UnknownProperty(pattern, key); + + if (pattern[p + 1] == 'P') + category = ~category; + + p = end + 1; + + var element = CharClassUnicodeCategory.Create(category); + elements.Add(element); + return true; + } + + bool ProcessShorthand() + { + var s = pattern.AsSpan(p); + if (s.Length > 1 && s[0] == '\\') + { + switch (s[1]) + { + case 's': + elements.Add(CharClassRange.Create('\u0009')); + elements.Add(CharClassRange.Create('\u000a')); + elements.Add(CharClassRange.Create('\u000b')); + elements.Add(CharClassRange.Create('\u000c')); + elements.Add(CharClassRange.Create('\u000d')); + elements.Add(CharClassRange.Create('\u0020')); + elements.Add(CharClassRange.Create('\u0085')); + elements.Add( + CharClassUnicodeCategory.Create( + GeneralUnicodeCategory.Separator)); + + p += 2; + return true; + + case 'S': + elements.Add(CharClassRange.Create('\u0000', '\u0008')); + elements.Add(CharClassRange.Create('\u000e', '\u001f')); + elements.Add(CharClassRange.Create('\u0021', '\u0084')); + elements.Add( + CharClassUnicodeCategory.Create( + ~GeneralUnicodeCategory.Separator)); + + p += 2; + return true; + + case 'd': + case 'D': + var digits = GeneralUnicodeCategory.DecimalDigitNumber; + if (s[1] == 'D') + digits = ~digits; + + elements.Add( + CharClassUnicodeCategory.Create( + digits)); + + p += 2; + return true; + + case 'w': + case 'W': + var word = GeneralUnicodeCategory.Letter + | GeneralUnicodeCategory.NonSpacingMark + | GeneralUnicodeCategory.DecimalDigitNumber + | GeneralUnicodeCategory.ConnectorPunctuation; + + if (s[1] == 'W') + word = ~word; + + elements.Add( + CharClassUnicodeCategory.Create( + word)); + + p += 2; + return true; + } + } + + return false; + } + + void ProcessRange() + { + var lo = ParseSymbol(); + var hi = lo; + + if (p + 1 < pattern.Length && pattern[p] == '-') + { + p++; + + hi = ParseSymbol(); + if (lo > hi) + Error_InvalidRange(pattern); + } + + var range = CharClassRange.Create(lo, hi); + elements.Add(range); + } + + char ParseSymbol() + { + var s = pattern.AsSpan(p); + if (s.StartsWith(@"\u")) + return ParseUnicodeSequence(); + + if (s[0] == '\\') + return ParseEscapeSequence(); + + p++; + return s[0]; + } + + char ParseEscapeSequence() + { + if ((uint)p + 1 >= (uint)pattern.Length + || !"0abeftnvr\\-".Contains(pattern[++p])) + Error_InvalidEscapeSequence(pattern); + + return pattern[p++] switch + { + '0' => '\0', + 'a' => '\a', + 'b' => '\b', + 'e' => '\u001b', + 'f' => '\f', + 't' => '\t', + 'n' => '\n', + 'v' => '\v', + 'r' => '\r', + var c => c + }; + } + + char ParseUnicodeSequence() + { + if (p + 5 >= pattern.Length) + Error_InvalidUnicodeSequence(pattern); + + if (!uint.TryParse(pattern.AsSpan(p + 2, 4), NumberStyles.AllowHexSpecifier, null, out var n)) + Error_InvalidUnicodeSequence(pattern); + + p += 6; + return (char)n; + } + } + + [DoesNotReturn] + private static void Error_InvalidPattern(string pattern) => + throw new ArgumentException($"Invalid pattern '{pattern}'."); + + [DoesNotReturn] + private static void Error_InvalidEscapeSequence(string pattern) => + throw new ArgumentException($"Invalid pattern '{pattern}'. Unrecognized escape sequence."); + + [DoesNotReturn] + private static void Error_InvalidUnicodeSequence(string pattern) => + throw new ArgumentException($"Invalid pattern '{pattern}'. Invalid Unicode escape sequence."); + + [DoesNotReturn] + private static void Error_UnknownProperty(string pattern, string name) => + throw new ArgumentException($"Invalid pattern '{pattern}'. Unknown property '{name}'."); + + [DoesNotReturn] + private static void Error_InvalidRange(string pattern) => + throw new ArgumentException($"Invalid pattern '{pattern}'. Range in reverse order."); +} diff --git a/src/Ramstack.Parsing/Utilities/CharClassRange.cs b/src/Ramstack.Parsing/Utilities/CharClassRange.cs index 1aea925..26a192e 100644 --- a/src/Ramstack.Parsing/Utilities/CharClassRange.cs +++ b/src/Ramstack.Parsing/Utilities/CharClassRange.cs @@ -1,193 +1,193 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a range of characters with inclusive lower and upper bounds. -/// -[DebuggerDisplay("{ToString(),nq}")] -internal readonly struct CharClassRange : IEquatable, IComparable -{ - /// - /// The character range merged value. - /// - public readonly int Value; - - /// - /// Gets the lower bound of the character range. - /// - public char Lo => (char)Value; - - /// - /// Gets the upper bound of the character range. - /// - public char Hi => (char)(Value >>> 16); - - /// - /// Gets a value that indicates whether this range represents a single character. - /// - public bool IsSingleCharacter => Lo == Value >>> 16; - - /// - /// Gets a value that indicates whether this range covers the entire possible range of characters. - /// - public bool IsFullRange => Value == '\uFFFF' << 16; - - /// - /// Gets the total number of symbols within this character range. - /// - /// - /// This property calculates the number of symbols included in the range, - /// determined as the difference between the upper bound () - /// and the lower bound () of the range, inclusive. - /// - public int SymbolCount => (Value >>> 16) - Lo + 1; - - /// - /// Initializes a new instance of the structure. - /// - /// The character range. - private CharClassRange(int value) => - Value = value; - - /// - /// Deconstructs the range into its lower and upper bounds. - /// - /// The inclusive lower bound of the range. - /// The inclusive upper bound of the range. - public void Deconstruct(out char lo, out char hi) - { - lo = Lo; - hi = Hi; - } - - /// - public bool Equals(CharClassRange other) => - Value == other.Value; - - /// - public int CompareTo(CharClassRange other) - { - return CompareToImpl(this, other); - - static int CompareToImpl(CharClassRange a, CharClassRange b) - { - var result = a.Lo - b.Lo; - return result == 0 - ? (a.Value >>> 16) - (b.Value >>> 16) - : result; - } - } - - /// - public override bool Equals(object? obj) => - obj is CharClassRange other && Equals(other); - - /// - public override int GetHashCode() => - Value; - - /// - public override string ToString() => - this.ToPrintable(); - - /// - /// Creates an array of instances from the specified span of characters. - /// - /// A read-only span of characters to convert. - /// - /// An array of instances representing the specified characters. - /// - public static CharClassRange[] Create(ReadOnlySpan chars) - { - Argument.ThrowIfEmpty(chars); - - var ranges = new CharClassRange[chars.Length]; - - // Note: The following condition is redundant, but it is included specifically - // to eliminate bounds checks generated by the JIT compiler. - if (ranges.Length == chars.Length) - for (var i = 0; i < chars.Length; i++) - ranges[i] = Create(chars[i]); - - return ranges; - } - - /// - /// Creates a for a single character. - /// - /// The character to define the range. - /// - /// A representing the single character. - /// - public static CharClassRange Create(char c) => - new CharClassRange(c | (c << 16)); - - /// - /// Creates a with specified lower and upper bounds. - /// - /// The inclusive lower bound of the range. - /// The inclusive upper bound of the range. - /// - /// A representing the specified range. - /// - public static CharClassRange Create(char lo, char hi) - { - Argument.ThrowIfGreaterThan(lo, hi); - return new CharClassRange(lo | (hi << 16)); - } - - /// - /// Creates a for the specified character range. - /// - /// The character range. - /// - /// A representing the specified range. - /// - public static CharClassRange CreateMasked(int value) => - new CharClassRange(value); - - /// - /// Implicitly converts a to a tuple of its bounds. - /// - /// The to convert. - /// - /// A tuple containing the lower and upper bounds. - /// - public static implicit operator (char lo, char hi)(CharClassRange range) => - (range.Lo, range.Hi); - - /// - /// Compares two instances for equality. - /// - public static bool operator ==(CharClassRange left, CharClassRange right) => - left.Value == right.Value; - - /// - /// Compares two instances for inequality. - /// - public static bool operator !=(CharClassRange left, CharClassRange right) => - left.Value != right.Value; - - /// - /// Compares whether one instance is less than another. - /// - public static bool operator <(CharClassRange left, CharClassRange right) => - left.CompareTo(right) < 0; - - /// - /// Compares whether one instance is greater than another. - /// - public static bool operator >(CharClassRange left, CharClassRange right) => - left.CompareTo(right) > 0; - - /// - /// Compares whether one instance is less than or equal to another. - /// - public static bool operator <=(CharClassRange left, CharClassRange right) => - left.CompareTo(right) <= 0; - - /// - /// Compares whether one instance is greater than or equal to another. - /// - public static bool operator >=(CharClassRange left, CharClassRange right) => - left.CompareTo(right) >= 0; -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a range of characters with inclusive lower and upper bounds. +/// +[DebuggerDisplay("{ToString(),nq}")] +internal readonly struct CharClassRange : IEquatable, IComparable +{ + /// + /// The character range merged value. + /// + public readonly int Value; + + /// + /// Gets the lower bound of the character range. + /// + public char Lo => (char)Value; + + /// + /// Gets the upper bound of the character range. + /// + public char Hi => (char)(Value >>> 16); + + /// + /// Gets a value that indicates whether this range represents a single character. + /// + public bool IsSingleCharacter => Lo == Value >>> 16; + + /// + /// Gets a value that indicates whether this range covers the entire possible range of characters. + /// + public bool IsFullRange => Value == '\uFFFF' << 16; + + /// + /// Gets the total number of symbols within this character range. + /// + /// + /// This property calculates the number of symbols included in the range, + /// determined as the difference between the upper bound () + /// and the lower bound () of the range, inclusive. + /// + public int SymbolCount => (Value >>> 16) - Lo + 1; + + /// + /// Initializes a new instance of the structure. + /// + /// The character range. + private CharClassRange(int value) => + Value = value; + + /// + /// Deconstructs the range into its lower and upper bounds. + /// + /// The inclusive lower bound of the range. + /// The inclusive upper bound of the range. + public void Deconstruct(out char lo, out char hi) + { + lo = Lo; + hi = Hi; + } + + /// + public bool Equals(CharClassRange other) => + Value == other.Value; + + /// + public int CompareTo(CharClassRange other) + { + return CompareToImpl(this, other); + + static int CompareToImpl(CharClassRange a, CharClassRange b) + { + var result = a.Lo - b.Lo; + return result == 0 + ? (a.Value >>> 16) - (b.Value >>> 16) + : result; + } + } + + /// + public override bool Equals(object? obj) => + obj is CharClassRange other && Equals(other); + + /// + public override int GetHashCode() => + Value; + + /// + public override string ToString() => + this.ToPrintable(); + + /// + /// Creates an array of instances from the specified span of characters. + /// + /// A read-only span of characters to convert. + /// + /// An array of instances representing the specified characters. + /// + public static CharClassRange[] Create(ReadOnlySpan chars) + { + Argument.ThrowIfEmpty(chars); + + var ranges = new CharClassRange[chars.Length]; + + // Note: The following condition is redundant, but it is included specifically + // to eliminate bounds checks generated by the JIT compiler. + if (ranges.Length == chars.Length) + for (var i = 0; i < chars.Length; i++) + ranges[i] = Create(chars[i]); + + return ranges; + } + + /// + /// Creates a for a single character. + /// + /// The character to define the range. + /// + /// A representing the single character. + /// + public static CharClassRange Create(char c) => + new CharClassRange(c | (c << 16)); + + /// + /// Creates a with specified lower and upper bounds. + /// + /// The inclusive lower bound of the range. + /// The inclusive upper bound of the range. + /// + /// A representing the specified range. + /// + public static CharClassRange Create(char lo, char hi) + { + Argument.ThrowIfGreaterThan(lo, hi); + return new CharClassRange(lo | (hi << 16)); + } + + /// + /// Creates a for the specified character range. + /// + /// The character range. + /// + /// A representing the specified range. + /// + public static CharClassRange CreateMasked(int value) => + new CharClassRange(value); + + /// + /// Implicitly converts a to a tuple of its bounds. + /// + /// The to convert. + /// + /// A tuple containing the lower and upper bounds. + /// + public static implicit operator (char lo, char hi)(CharClassRange range) => + (range.Lo, range.Hi); + + /// + /// Compares two instances for equality. + /// + public static bool operator ==(CharClassRange left, CharClassRange right) => + left.Value == right.Value; + + /// + /// Compares two instances for inequality. + /// + public static bool operator !=(CharClassRange left, CharClassRange right) => + left.Value != right.Value; + + /// + /// Compares whether one instance is less than another. + /// + public static bool operator <(CharClassRange left, CharClassRange right) => + left.CompareTo(right) < 0; + + /// + /// Compares whether one instance is greater than another. + /// + public static bool operator >(CharClassRange left, CharClassRange right) => + left.CompareTo(right) > 0; + + /// + /// Compares whether one instance is less than or equal to another. + /// + public static bool operator <=(CharClassRange left, CharClassRange right) => + left.CompareTo(right) <= 0; + + /// + /// Compares whether one instance is greater than or equal to another. + /// + public static bool operator >=(CharClassRange left, CharClassRange right) => + left.CompareTo(right) >= 0; +} diff --git a/src/Ramstack.Parsing/Utilities/CharClassUnicodeCategory.cs b/src/Ramstack.Parsing/Utilities/CharClassUnicodeCategory.cs index a21d591..21db66f 100644 --- a/src/Ramstack.Parsing/Utilities/CharClassUnicodeCategory.cs +++ b/src/Ramstack.Parsing/Utilities/CharClassUnicodeCategory.cs @@ -1,168 +1,168 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a Unicode category mask used for character classification. -/// -[DebuggerDisplay("{ToString(),nq}")] -internal readonly struct CharClassUnicodeCategory : IEquatable -{ - /// - /// The raw integer value representing the Unicode category mask. - /// - public readonly int Value; - - /// - /// Gets a value indicating whether the category mask is empty. - /// - public bool IsEmpty => Value == 0; - - /// - /// Gets a value indicating whether the category mask is specified (non-empty). - /// - public bool IsSpecified => Value != 0; - - /// - /// Initializes a new instance of the struct with the specified category mask. - /// - /// The category mask to initialize with. - private CharClassUnicodeCategory(int categories) => - Value = categories & (int)CharClassExtensions.NormalizedCategoryMask; - - /// - /// Determines whether the specified is included in this category mask. - /// - /// The to check. - /// - /// if the category is included; otherwise, . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsInclude(UnicodeCategory category) => - (Value & (1 << (int)category)) != 0; - - /// - /// Determines whether the category of the specified character is included in this category mask. - /// - /// The character to check. - /// - /// if the character's category is included; - /// otherwise, . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsInclude(char c) - { - return Value != 0 && IsIncludeImpl(Value, c); - - static bool IsIncludeImpl(int value, char c) - { - var category = char.GetUnicodeCategory(c); - return (value & (1 << (int)category)) != 0; - } - } - - /// - /// Combines this category mask with the specified . - /// - /// The to combine with. - /// - /// A new representing the combined category mask. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CharClassUnicodeCategory Combine(UnicodeCategory category) => - new CharClassUnicodeCategory(Value | (1 << (int)category)); - - /// - /// Combines this category mask with the specified . - /// - /// The to combine with. - /// - /// A new representing the combined category mask. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CharClassUnicodeCategory Combine(GeneralUnicodeCategory category) => - new CharClassUnicodeCategory(Value | (int)category); - - /// - /// Combines this category mask with another . - /// - /// The other to combine with. - /// - /// A new representing the combined category mask. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public CharClassUnicodeCategory Combine(CharClassUnicodeCategory category) => - new CharClassUnicodeCategory(Value | category.Value); - - /// - /// Creates a new from a . - /// - /// The to represent. - /// - /// A new representing the specified Unicode categories. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static CharClassUnicodeCategory Create(GeneralUnicodeCategory category) => - new CharClassUnicodeCategory((int)category); - - /// - /// Creates a new from a . - /// - /// The to represent. - /// - /// A new representing the specified Unicode category. - /// - public static CharClassUnicodeCategory Create(UnicodeCategory category) => - new CharClassUnicodeCategory(1 << (int)category); - - /// - /// Creates a new from a raw mask value. - /// - /// The integer mask representing the Unicode categories. - /// - /// A new . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static CharClassUnicodeCategory Create(int mask) => - new CharClassUnicodeCategory(mask); - - /// - public override string ToString() => - ((GeneralUnicodeCategory)Value).ToPrintable(); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(CharClassUnicodeCategory other) => - Value == other.Value; - - /// - public override bool Equals(object? obj) => - obj is CharClassUnicodeCategory other && Equals(other); - - /// - public override int GetHashCode() => - Value; - - /// - /// Implicitly converts a to a . - /// - /// The to convert. - /// - /// A representing the specified Unicode category. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator CharClassUnicodeCategory(GeneralUnicodeCategory category) => - new CharClassUnicodeCategory((int)category); - - /// - /// Compares two instances for equality. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(CharClassUnicodeCategory left, CharClassUnicodeCategory right) => - left.Value == right.Value; - - /// - /// Compares two instances for inequality. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(CharClassUnicodeCategory left, CharClassUnicodeCategory right) => - left.Value != right.Value; -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a Unicode category mask used for character classification. +/// +[DebuggerDisplay("{ToString(),nq}")] +internal readonly struct CharClassUnicodeCategory : IEquatable +{ + /// + /// The raw integer value representing the Unicode category mask. + /// + public readonly int Value; + + /// + /// Gets a value indicating whether the category mask is empty. + /// + public bool IsEmpty => Value == 0; + + /// + /// Gets a value indicating whether the category mask is specified (non-empty). + /// + public bool IsSpecified => Value != 0; + + /// + /// Initializes a new instance of the struct with the specified category mask. + /// + /// The category mask to initialize with. + private CharClassUnicodeCategory(int categories) => + Value = categories & (int)CharClassExtensions.NormalizedCategoryMask; + + /// + /// Determines whether the specified is included in this category mask. + /// + /// The to check. + /// + /// if the category is included; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsInclude(UnicodeCategory category) => + (Value & (1 << (int)category)) != 0; + + /// + /// Determines whether the category of the specified character is included in this category mask. + /// + /// The character to check. + /// + /// if the character's category is included; + /// otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsInclude(char c) + { + return Value != 0 && IsIncludeImpl(Value, c); + + static bool IsIncludeImpl(int value, char c) + { + var category = char.GetUnicodeCategory(c); + return (value & (1 << (int)category)) != 0; + } + } + + /// + /// Combines this category mask with the specified . + /// + /// The to combine with. + /// + /// A new representing the combined category mask. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CharClassUnicodeCategory Combine(UnicodeCategory category) => + new CharClassUnicodeCategory(Value | (1 << (int)category)); + + /// + /// Combines this category mask with the specified . + /// + /// The to combine with. + /// + /// A new representing the combined category mask. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CharClassUnicodeCategory Combine(GeneralUnicodeCategory category) => + new CharClassUnicodeCategory(Value | (int)category); + + /// + /// Combines this category mask with another . + /// + /// The other to combine with. + /// + /// A new representing the combined category mask. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public CharClassUnicodeCategory Combine(CharClassUnicodeCategory category) => + new CharClassUnicodeCategory(Value | category.Value); + + /// + /// Creates a new from a . + /// + /// The to represent. + /// + /// A new representing the specified Unicode categories. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static CharClassUnicodeCategory Create(GeneralUnicodeCategory category) => + new CharClassUnicodeCategory((int)category); + + /// + /// Creates a new from a . + /// + /// The to represent. + /// + /// A new representing the specified Unicode category. + /// + public static CharClassUnicodeCategory Create(UnicodeCategory category) => + new CharClassUnicodeCategory(1 << (int)category); + + /// + /// Creates a new from a raw mask value. + /// + /// The integer mask representing the Unicode categories. + /// + /// A new . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static CharClassUnicodeCategory Create(int mask) => + new CharClassUnicodeCategory(mask); + + /// + public override string ToString() => + ((GeneralUnicodeCategory)Value).ToPrintable(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(CharClassUnicodeCategory other) => + Value == other.Value; + + /// + public override bool Equals(object? obj) => + obj is CharClassUnicodeCategory other && Equals(other); + + /// + public override int GetHashCode() => + Value; + + /// + /// Implicitly converts a to a . + /// + /// The to convert. + /// + /// A representing the specified Unicode category. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator CharClassUnicodeCategory(GeneralUnicodeCategory category) => + new CharClassUnicodeCategory((int)category); + + /// + /// Compares two instances for equality. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(CharClassUnicodeCategory left, CharClassUnicodeCategory right) => + left.Value == right.Value; + + /// + /// Compares two instances for inequality. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(CharClassUnicodeCategory left, CharClassUnicodeCategory right) => + left.Value != right.Value; +} diff --git a/src/Ramstack.Parsing/Utilities/CharMap.cs b/src/Ramstack.Parsing/Utilities/CharMap.cs index f4cd523..975be31 100644 --- a/src/Ramstack.Parsing/Utilities/CharMap.cs +++ b/src/Ramstack.Parsing/Utilities/CharMap.cs @@ -1,68 +1,68 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Provides a mapping structure for character-to-string-array lookups. -/// -internal readonly struct CharMap -{ - private readonly string[][] _array; - private readonly Dictionary? _dictionary; - - /// - /// Character offset used to normalize array indices in the array-based storage strategy. - /// - private readonly int _offset; - - /// - /// Provides indexed access to string arrays mapped to specific characters. - /// - /// The character to look up. - /// - /// The string array associated with the character, or if no mapping exists. - /// - public string[]? this[char c] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - var index = c - _offset; - var array = _array; - - if ((uint)index < (uint)array.Length) - return array[index]; - - var dictionary = _dictionary; - if (dictionary is not null) - { - dictionary.TryGetValue(c, out var result); - return result; - } - - return null; - } - } - - /// - /// Initializes a new instance of the CharMap struct with the specified character mappings. - /// - /// A dictionary containing the initial character-to-string-array mappings. - public CharMap(Dictionary dictionary) - { - var chars = dictionary.Keys.ToArray(); - Array.Sort(chars); - - if ((uint)chars[^1] - chars[0] + 1 < 128u) - { - _offset = chars[0]; - _array = new string[chars[^1] - chars[0] + 1][]; - - foreach (var c in chars) - _array[c - _offset] = dictionary[c]; - } - else - { - _array = []; - _dictionary = dictionary; - } - } -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Provides a mapping structure for character-to-string-array lookups. +/// +internal readonly struct CharMap +{ + private readonly string[][] _array; + private readonly Dictionary? _dictionary; + + /// + /// Character offset used to normalize array indices in the array-based storage strategy. + /// + private readonly int _offset; + + /// + /// Provides indexed access to string arrays mapped to specific characters. + /// + /// The character to look up. + /// + /// The string array associated with the character, or if no mapping exists. + /// + public string[]? this[char c] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var index = c - _offset; + var array = _array; + + if ((uint)index < (uint)array.Length) + return array[index]; + + var dictionary = _dictionary; + if (dictionary is not null) + { + dictionary.TryGetValue(c, out var result); + return result; + } + + return null; + } + } + + /// + /// Initializes a new instance of the CharMap struct with the specified character mappings. + /// + /// A dictionary containing the initial character-to-string-array mappings. + public CharMap(Dictionary dictionary) + { + var chars = dictionary.Keys.ToArray(); + Array.Sort(chars); + + if ((uint)chars[^1] - chars[0] + 1 < 128u) + { + _offset = chars[0]; + _array = new string[chars[^1] - chars[0] + 1][]; + + foreach (var c in chars) + _array[c - _offset] = dictionary[c]; + } + else + { + _array = []; + _dictionary = dictionary; + } + } +} diff --git a/src/Ramstack.Parsing/Utilities/ListFactory.cs b/src/Ramstack.Parsing/Utilities/ListFactory.cs index eb37654..ea34faa 100644 --- a/src/Ramstack.Parsing/Utilities/ListFactory.cs +++ b/src/Ramstack.Parsing/Utilities/ListFactory.cs @@ -1,68 +1,68 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents the helper class responsible for creating instances -/// with optimized initialization based on the target framework version. -/// -/// The type of the elements in the list. -internal static class ListFactory -{ - /// - /// Creates a new initialized with the elements from the specified . - /// - /// The span of items to populate the list with. - /// - /// A new containing the specified elements. - /// - /// - /// This method leverages internal optimizations depending on the target framework version - /// to efficiently populate the list with the specified items. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List CreateList(ReadOnlySpan items) - { - #if NET9_0_OR_GREATER - var list = new List(); - GetCount(list) = items.Length; - GetArray(list) = items.ToArray(); - return list; - #elif NET8_0_OR_GREATER - var list = new List(items.Length); - if (items.Length != 0) - { - CollectionsMarshal.SetCount(list, items.Length); - items.TryCopyTo(CollectionsMarshal.AsSpan(list)); - } - return list; - #else - var list = new List(items.Length); - foreach (var item in items) - list.Add(item); - return list; - #endif - } - - #if NET9_0_OR_GREATER - /// - /// Returns a reference to the internal array buffer of the specified . - /// - /// The list whose internal buffer is to be accessed. - /// - /// A reference to the internal array used by the list. - /// - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")] - private static extern ref T[] GetArray(List list); - - /// - /// Returns a reference to the internal "_size" field of the specified - /// representing the number of elements in the list. - /// - /// The list whose internal "_size" field is to be accessed. - /// - /// A reference to the internal "_size" field representing the number of elements in the list. - /// - [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_size")] - private static extern ref int GetCount(List list); - #endif -} - +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents the helper class responsible for creating instances +/// with optimized initialization based on the target framework version. +/// +/// The type of the elements in the list. +internal static class ListFactory +{ + /// + /// Creates a new initialized with the elements from the specified . + /// + /// The span of items to populate the list with. + /// + /// A new containing the specified elements. + /// + /// + /// This method leverages internal optimizations depending on the target framework version + /// to efficiently populate the list with the specified items. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List CreateList(ReadOnlySpan items) + { + #if NET9_0_OR_GREATER + var list = new List(); + GetCount(list) = items.Length; + GetArray(list) = items.ToArray(); + return list; + #elif NET8_0_OR_GREATER + var list = new List(items.Length); + if (items.Length != 0) + { + CollectionsMarshal.SetCount(list, items.Length); + items.TryCopyTo(CollectionsMarshal.AsSpan(list)); + } + return list; + #else + var list = new List(items.Length); + foreach (var item in items) + list.Add(item); + return list; + #endif + } + + #if NET9_0_OR_GREATER + /// + /// Returns a reference to the internal array buffer of the specified . + /// + /// The list whose internal buffer is to be accessed. + /// + /// A reference to the internal array used by the list. + /// + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")] + private static extern ref T[] GetArray(List list); + + /// + /// Returns a reference to the internal "_size" field of the specified + /// representing the number of elements in the list. + /// + /// The list whose internal "_size" field is to be accessed. + /// + /// A reference to the internal "_size" field representing the number of elements in the list. + /// + [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_size")] + private static extern ref int GetCount(List list); + #endif +} + diff --git a/src/Ramstack.Parsing/Utilities/StringBuffer.cs b/src/Ramstack.Parsing/Utilities/StringBuffer.cs index 6840c81..7e185e9 100644 --- a/src/Ramstack.Parsing/Utilities/StringBuffer.cs +++ b/src/Ramstack.Parsing/Utilities/StringBuffer.cs @@ -1,193 +1,193 @@ -using System.Buffers; - -namespace Ramstack.Parsing.Utilities; - -/// -/// Represents a mutable buffer for building strings. -/// -/// -/// This structure is optimized for performance and relies on proper initialization. -/// -/// Always initialize instances using one of the available constructors with the new keyword, -/// including the parameterless constructor: new StringBuffer(). -/// -/// Do not use default(StringBuffer) or similar patterns, as this results -/// in an uninitialized instance with an invalid internal state, -/// causing a when methods are called. -/// -/// This design choice was made to avoid unnecessary overhead and maintain control over -/// internal state initialization in modern C# versions where parameterless constructors -/// in structs are supported. -/// -internal struct StringBuffer : IDisposable -{ - private char[] _chars; - private int _count; - - /// - /// Gets the current length of the string buffer. - /// - public int Length => _count; - - /// - /// Initializes a new instance of the structure. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public StringBuffer() => - _chars = []; - - /// - /// Initializes a new instance of the structure. - /// - /// The initial capacity of the buffer. - public StringBuffer(int capacity) => - _chars = ArrayPool.Shared.Rent(capacity); - - /// - /// Appends a single character to the string buffer. - /// - /// The character to append. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(char c) - { - var chars = _chars; - var count = _count; - - if ((uint)count < (uint)chars.Length) - { - chars[count] = c; - } - else - { - _chars = AppendSlow(chars, count, c); - } - - _count = count + 1; - } - - /// - /// Appends a span of characters to the string buffer. - /// - /// The span of characters to append. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Append(ReadOnlySpan text) - { - if (text.Length != 0) - { - var chars = _chars; - var count = _count; - - if ((uint)(count + text.Length) <= (uint)chars.Length) - { - ref var p = ref Unsafe.Add( - ref MemoryMarshal.GetArrayDataReference(chars), - (nint)(uint)count); - - if (text.Length <= 2) - { - p = text[0]; - if (text.Length == 2) - Unsafe.Add(ref p, 1) = text[1]; - } - else - { - text.TryCopyTo(MemoryMarshal.CreateSpan(ref p, text.Length)); - } - } - else - { - _chars = AppendSlow(chars, count, ref MemoryMarshal.GetReference(text), text.Length); - } - - _count = count + text.Length; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override string ToString() - { - var chars = _chars; - var count = _count; - _chars = []; - _count = 0; - - return ToStringImpl(chars, count); - - [MethodImpl(MethodImplOptions.NoInlining)] - static string ToStringImpl(char[] buffer, int length) - { - if (buffer.Length != 0 && (uint)length <= (uint)buffer.Length) - { - var result = new string(buffer.AsSpan(0, length)); - ArrayPool.Shared.Return(buffer); - return result; - } - - return ""; - } - } - - /// - /// Releases resources used by the string buffer, - /// returning its internal buffer to the pool if applicable. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dispose() - { - if (_count != 0) - DisposeImpl(_chars); - - [MethodImpl(MethodImplOptions.NoInlining)] - static void DisposeImpl(char[] buffer) - { - if (buffer.Length != 0) - ArrayPool.Shared.Return(buffer); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static char[] AppendSlow(char[] rented, int index, char c) - { - var capacity = rented.Length != 0 - ? rented.Length * 2 - : 128; - - var buffer = ArrayPool.Shared.Rent(capacity); - _ = buffer.Length; - - rented.AsSpan().TryCopyTo(buffer); - if ((uint)index < (uint)buffer.Length) - buffer[index] = c; - - if (rented.Length != 0) - ArrayPool.Shared.Return(rented); - - return buffer; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static char[] AppendSlow(char[] rented, int index, ref char s, int length) - { - var required = checked(rented.Length + length); - var capacity = rented.Length != 0 - ? rented.Length * 2 - : 128; - - if (capacity < required) - capacity = required; - - var buffer = ArrayPool.Shared.Rent(capacity); - _ = buffer.Length; - - rented.AsSpan(0, index).TryCopyTo(buffer); - MemoryMarshal - .CreateSpan(ref s, length) - .TryCopyTo(buffer.AsSpan(index)); - - if (rented.Length != 0) - ArrayPool.Shared.Return(rented); - - return buffer; - } -} +using System.Buffers; + +namespace Ramstack.Parsing.Utilities; + +/// +/// Represents a mutable buffer for building strings. +/// +/// +/// This structure is optimized for performance and relies on proper initialization. +/// +/// Always initialize instances using one of the available constructors with the new keyword, +/// including the parameterless constructor: new StringBuffer(). +/// +/// Do not use default(StringBuffer) or similar patterns, as this results +/// in an uninitialized instance with an invalid internal state, +/// causing a when methods are called. +/// +/// This design choice was made to avoid unnecessary overhead and maintain control over +/// internal state initialization in modern C# versions where parameterless constructors +/// in structs are supported. +/// +internal struct StringBuffer : IDisposable +{ + private char[] _chars; + private int _count; + + /// + /// Gets the current length of the string buffer. + /// + public int Length => _count; + + /// + /// Initializes a new instance of the structure. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StringBuffer() => + _chars = []; + + /// + /// Initializes a new instance of the structure. + /// + /// The initial capacity of the buffer. + public StringBuffer(int capacity) => + _chars = ArrayPool.Shared.Rent(capacity); + + /// + /// Appends a single character to the string buffer. + /// + /// The character to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(char c) + { + var chars = _chars; + var count = _count; + + if ((uint)count < (uint)chars.Length) + { + chars[count] = c; + } + else + { + _chars = AppendSlow(chars, count, c); + } + + _count = count + 1; + } + + /// + /// Appends a span of characters to the string buffer. + /// + /// The span of characters to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(ReadOnlySpan text) + { + if (text.Length != 0) + { + var chars = _chars; + var count = _count; + + if ((uint)(count + text.Length) <= (uint)chars.Length) + { + ref var p = ref Unsafe.Add( + ref MemoryMarshal.GetArrayDataReference(chars), + (nint)(uint)count); + + if (text.Length <= 2) + { + p = text[0]; + if (text.Length == 2) + Unsafe.Add(ref p, 1) = text[1]; + } + else + { + text.TryCopyTo(MemoryMarshal.CreateSpan(ref p, text.Length)); + } + } + else + { + _chars = AppendSlow(chars, count, ref MemoryMarshal.GetReference(text), text.Length); + } + + _count = count + text.Length; + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() + { + var chars = _chars; + var count = _count; + _chars = []; + _count = 0; + + return ToStringImpl(chars, count); + + [MethodImpl(MethodImplOptions.NoInlining)] + static string ToStringImpl(char[] buffer, int length) + { + if (buffer.Length != 0 && (uint)length <= (uint)buffer.Length) + { + var result = new string(buffer.AsSpan(0, length)); + ArrayPool.Shared.Return(buffer); + return result; + } + + return ""; + } + } + + /// + /// Releases resources used by the string buffer, + /// returning its internal buffer to the pool if applicable. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + if (_count != 0) + DisposeImpl(_chars); + + [MethodImpl(MethodImplOptions.NoInlining)] + static void DisposeImpl(char[] buffer) + { + if (buffer.Length != 0) + ArrayPool.Shared.Return(buffer); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static char[] AppendSlow(char[] rented, int index, char c) + { + var capacity = rented.Length != 0 + ? rented.Length * 2 + : 128; + + var buffer = ArrayPool.Shared.Rent(capacity); + _ = buffer.Length; + + rented.AsSpan().TryCopyTo(buffer); + if ((uint)index < (uint)buffer.Length) + buffer[index] = c; + + if (rented.Length != 0) + ArrayPool.Shared.Return(rented); + + return buffer; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static char[] AppendSlow(char[] rented, int index, ref char s, int length) + { + var required = checked(rented.Length + length); + var capacity = rented.Length != 0 + ? rented.Length * 2 + : 128; + + if (capacity < required) + capacity = required; + + var buffer = ArrayPool.Shared.Rent(capacity); + _ = buffer.Length; + + rented.AsSpan(0, index).TryCopyTo(buffer); + MemoryMarshal + .CreateSpan(ref s, length) + .TryCopyTo(buffer.AsSpan(index)); + + if (rented.Length != 0) + ArrayPool.Shared.Return(rented); + + return buffer; + } +} diff --git a/src/Ramstack.Parsing/Utilities/TextHelper.cs b/src/Ramstack.Parsing/Utilities/TextHelper.cs index c2d83ad..547131e 100644 --- a/src/Ramstack.Parsing/Utilities/TextHelper.cs +++ b/src/Ramstack.Parsing/Utilities/TextHelper.cs @@ -1,44 +1,44 @@ -namespace Ramstack.Parsing.Utilities; - -/// -/// Provides helper methods for working with text. -/// -internal static class TextHelper -{ - /// - /// Calculates the line and column numbers for the specified index in the specified character span. - /// - /// The character span representing the text content. - /// The index within the span for which to determine the line and column numbers. - /// - /// A tuple containing the line and column numbers corresponding to the specified index. - /// - public static (int Line, int Column) GetLineColumn(ReadOnlySpan source, int index) - { - var line = 1; - var column = 1; - - if ((uint)index < (uint)source.Length) - source = source[..index]; - - for (var i = 0; i < source.Length; i++) - { - column++; - - if (source[i] == '\n') - { - line++; - column = 1; - } - else if (source[i] == '\r' && i + 1 < source.Length && source[i + 1] == '\n') - { - i++; - - line++; - column = 1; - } - } - - return (line, column); - } -} +namespace Ramstack.Parsing.Utilities; + +/// +/// Provides helper methods for working with text. +/// +internal static class TextHelper +{ + /// + /// Calculates the line and column numbers for the specified index in the specified character span. + /// + /// The character span representing the text content. + /// The index within the span for which to determine the line and column numbers. + /// + /// A tuple containing the line and column numbers corresponding to the specified index. + /// + public static (int Line, int Column) GetLineColumn(ReadOnlySpan source, int index) + { + var line = 1; + var column = 1; + + if ((uint)index < (uint)source.Length) + source = source[..index]; + + for (var i = 0; i < source.Length; i++) + { + column++; + + if (source[i] == '\n') + { + line++; + column = 1; + } + else if (source[i] == '\r' && i + 1 < source.Length && source[i + 1] == '\n') + { + i++; + + line++; + column = 1; + } + } + + return (line, column); + } +} diff --git a/tests/Ramstack.Parsing.Tests/CharacterTests.cs b/tests/Ramstack.Parsing.Tests/CharacterTests.cs index aa755aa..0c05e7e 100644 --- a/tests/Ramstack.Parsing.Tests/CharacterTests.cs +++ b/tests/Ramstack.Parsing.Tests/CharacterTests.cs @@ -1,29 +1,29 @@ -namespace Ramstack.Parsing; - -using static Character; - -[TestFixture] -public class CharacterTests -{ - [Test] - public void DigitTest() - { - for (var c = '0'; c <= '9'; c++) - { - var s = c.ToString(); - Assert.That(Digit.Parse(s).Success, Is.True); - Assert.That(Digit.Parse(s).Value, Is.EqualTo(c)); - Assert.That(Digit.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); - } - - Assert.That(Digit.Parse("a").Success, Is.False); - Assert.That(Digit.Parse("*").Success, Is.False); - } - - [Test] - public void SpaceTest() - { - Assert.That(WhiteSpace.Parse(" ").Success, Is.True); - Assert.That(WhiteSpace.Map(m => (m.Index, m.Length)).Parse(" ").Value, Is.EqualTo((0, 1))); - } -} +namespace Ramstack.Parsing; + +using static Character; + +[TestFixture] +public class CharacterTests +{ + [Test] + public void DigitTest() + { + for (var c = '0'; c <= '9'; c++) + { + var s = c.ToString(); + Assert.That(Digit.Parse(s).Success, Is.True); + Assert.That(Digit.Parse(s).Value, Is.EqualTo(c)); + Assert.That(Digit.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); + } + + Assert.That(Digit.Parse("a").Success, Is.False); + Assert.That(Digit.Parse("*").Success, Is.False); + } + + [Test] + public void SpaceTest() + { + Assert.That(WhiteSpace.Parse(" ").Success, Is.True); + Assert.That(WhiteSpace.Map(m => (m.Index, m.Length)).Parse(" ").Value, Is.EqualTo((0, 1))); + } +} diff --git a/tests/Ramstack.Parsing.Tests/Collections/ArrayBuilderTests.cs b/tests/Ramstack.Parsing.Tests/Collections/ArrayBuilderTests.cs index 7754d1b..0dddad8 100644 --- a/tests/Ramstack.Parsing.Tests/Collections/ArrayBuilderTests.cs +++ b/tests/Ramstack.Parsing.Tests/Collections/ArrayBuilderTests.cs @@ -1,108 +1,108 @@ -#if TEST_INTERNALS -namespace Ramstack.Parsing.Collections; - -[TestFixture] -public class ArrayBuilderTests -{ - [Test] - public void Ctor_ZeroCapacity() - { - var builder = new ArrayBuilder(); - - foreach (var _ in builder.AsSpan()) - Assert.Fail(); - - Assert.That(builder.Count, Is.Zero); - Assert.That(builder.InnerBuffer.Length, Is.EqualTo(0)); - Assert.That(builder.Clear, Throws.Nothing); - } - - [Test] - public void Add_ValueAdded() - { - var builder = new ArrayBuilder(); - builder.Add(10); - - Assert.That(builder.Count, Is.EqualTo(1)); - Assert.That(builder.InnerBuffer.Length > 1, Is.True); - Assert.That(builder.AsSpan().Length, Is.EqualTo(1)); - Assert.That(builder.AsSpan()[0], Is.EqualTo(10)); - } - - [Test] - public void Add_MultipleValues_AllValuesAdded() - { - const int Count = 999; - - var builder = new ArrayBuilder(); - for (var i = 0; i < Count; i++) - builder.Add(i); - - Assert.That(builder.Count, Is.EqualTo(Count)); - Assert.That(builder.InnerBuffer.Length, Is.GreaterThan(Count)); - Assert.That(builder.AsSpan().Length, Is.EqualTo(Count)); - } - - [Test] - public void AddRange_AllValuesAdded() - { - var builder = new ArrayBuilder(); - builder.Add(1); - builder.AddRange([2, 3, 4, 5, 6, 7, 8, 9, 10]); - - Assert.That(builder.Count, Is.EqualTo(10)); - Assert.That(builder.AsSpan().Length, Is.EqualTo(10)); - Assert.That(builder.InnerBuffer.Length, Is.GreaterThan(10)); - Assert.That(builder.ToArray(), Is.EquivalentTo(Enumerable.Range(1, 10))); - } - - [Test] - public void Clear_ReferenceValues_Keep() - { - var builder = new ArrayBuilder(); - for (var i = 0; i < 10; i++) - builder.Add(i.ToString()); - - builder.Clear(); - - Assert.That(builder.Count, Is.Zero); - Assert.That(builder.InnerBuffer.Take(10).All(v => v is not null), Is.True); - } - - [Test] - public void Clear_BufferUnchanged() - { - var builder = new ArrayBuilder(); - for (var i = 0; i < 10; i++) - builder.Add(i); - - var buffer = builder.InnerBuffer; - builder.Clear(); - - Assert.That(builder.InnerBuffer, Is.SameAs(buffer)); - } - - [TestCase(0)] - [TestCase(1)] - [TestCase(4)] - [TestCase(7)] - [TestCase(9)] - public void AsSpan(int count) - { - var builder = new ArrayBuilder(); - for (var i = 0; i < count; i++) - builder.Add(i); - - Assert.That( - builder.ToArray(), - Is.EquivalentTo(Enumerable.Range(0, count))); - } - - [Test] - public void AsSpan_Empty() - { - var view = new ArrayBuilder().AsSpan(); - Assert.That(view.Length, Is.Zero); - } -} -#endif +#if TEST_INTERNALS +namespace Ramstack.Parsing.Collections; + +[TestFixture] +public class ArrayBuilderTests +{ + [Test] + public void Ctor_ZeroCapacity() + { + var builder = new ArrayBuilder(); + + foreach (var _ in builder.AsSpan()) + Assert.Fail(); + + Assert.That(builder.Count, Is.Zero); + Assert.That(builder.InnerBuffer.Length, Is.EqualTo(0)); + Assert.That(builder.Clear, Throws.Nothing); + } + + [Test] + public void Add_ValueAdded() + { + var builder = new ArrayBuilder(); + builder.Add(10); + + Assert.That(builder.Count, Is.EqualTo(1)); + Assert.That(builder.InnerBuffer.Length > 1, Is.True); + Assert.That(builder.AsSpan().Length, Is.EqualTo(1)); + Assert.That(builder.AsSpan()[0], Is.EqualTo(10)); + } + + [Test] + public void Add_MultipleValues_AllValuesAdded() + { + const int Count = 999; + + var builder = new ArrayBuilder(); + for (var i = 0; i < Count; i++) + builder.Add(i); + + Assert.That(builder.Count, Is.EqualTo(Count)); + Assert.That(builder.InnerBuffer.Length, Is.GreaterThan(Count)); + Assert.That(builder.AsSpan().Length, Is.EqualTo(Count)); + } + + [Test] + public void AddRange_AllValuesAdded() + { + var builder = new ArrayBuilder(); + builder.Add(1); + builder.AddRange([2, 3, 4, 5, 6, 7, 8, 9, 10]); + + Assert.That(builder.Count, Is.EqualTo(10)); + Assert.That(builder.AsSpan().Length, Is.EqualTo(10)); + Assert.That(builder.InnerBuffer.Length, Is.GreaterThan(10)); + Assert.That(builder.ToArray(), Is.EquivalentTo(Enumerable.Range(1, 10))); + } + + [Test] + public void Clear_ReferenceValues_Keep() + { + var builder = new ArrayBuilder(); + for (var i = 0; i < 10; i++) + builder.Add(i.ToString()); + + builder.Clear(); + + Assert.That(builder.Count, Is.Zero); + Assert.That(builder.InnerBuffer.Take(10).All(v => v is not null), Is.True); + } + + [Test] + public void Clear_BufferUnchanged() + { + var builder = new ArrayBuilder(); + for (var i = 0; i < 10; i++) + builder.Add(i); + + var buffer = builder.InnerBuffer; + builder.Clear(); + + Assert.That(builder.InnerBuffer, Is.SameAs(buffer)); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(4)] + [TestCase(7)] + [TestCase(9)] + public void AsSpan(int count) + { + var builder = new ArrayBuilder(); + for (var i = 0; i < count; i++) + builder.Add(i); + + Assert.That( + builder.ToArray(), + Is.EquivalentTo(Enumerable.Range(0, count))); + } + + [Test] + public void AsSpan_Empty() + { + var view = new ArrayBuilder().AsSpan(); + Assert.That(view.Length, Is.Zero); + } +} +#endif diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.Char.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.Char.cs index 732d62a..42ea519 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.Char.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.Char.cs @@ -1,31 +1,31 @@ -namespace Ramstack.Parsing; - -partial class LiteralTests -{ - [TestCase("'1'", '1')] - [TestCase("'\\u0045'", '\u0045')] - [TestCase("'\\r'", '\r')] - [TestCase("'\\n'", '\n')] - [TestCase("'\\t'", '\t')] - public void QuotedCharacterTest(string input, char expected) - { - Assert.That( - Literal.QuotedCharacter.Parse(input).Value, - Is.EqualTo(expected)); - } - - [TestCase("'1")] - [TestCase("'\\uabcw'")] - [TestCase("'\\u123q'")] - [TestCase("'\\z'")] - [TestCase("'\r'")] - [TestCase("'\n'")] - [TestCase("'\''")] - [TestCase("'a")] - [TestCase("'ab'")] - public void QuotedCharacter_Error(string input) - { - var parser = Literal.QuotedCharacter; - Assert.That(parser.Parse(input).Success, Is.False); - } -} +namespace Ramstack.Parsing; + +partial class LiteralTests +{ + [TestCase("'1'", '1')] + [TestCase("'\\u0045'", '\u0045')] + [TestCase("'\\r'", '\r')] + [TestCase("'\\n'", '\n')] + [TestCase("'\\t'", '\t')] + public void QuotedCharacterTest(string input, char expected) + { + Assert.That( + Literal.QuotedCharacter.Parse(input).Value, + Is.EqualTo(expected)); + } + + [TestCase("'1")] + [TestCase("'\\uabcw'")] + [TestCase("'\\u123q'")] + [TestCase("'\\z'")] + [TestCase("'\r'")] + [TestCase("'\n'")] + [TestCase("'\''")] + [TestCase("'a")] + [TestCase("'ab'")] + public void QuotedCharacter_Error(string input) + { + var parser = Literal.QuotedCharacter; + Assert.That(parser.Parse(input).Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.EscapeSequence.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.EscapeSequence.cs index 8074d95..2d89af3 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.EscapeSequence.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.EscapeSequence.cs @@ -1,24 +1,24 @@ -namespace Ramstack.Parsing; - -partial class LiteralTests -{ - [TestCase(@"\""", '"')] - [TestCase(@"\'", '\'')] - [TestCase(@"\/", '/')] - [TestCase(@"\\", '\\')] - [TestCase(@"\0", '\0')] - [TestCase(@"\a", '\a')] - [TestCase(@"\b", '\b')] - [TestCase(@"\e", '\e')] - [TestCase(@"\f", '\f')] - [TestCase(@"\n", '\n')] - [TestCase(@"\r", '\r')] - [TestCase(@"\t", '\t')] - [TestCase(@"\v", '\v')] - public void EscapeSequenceTest(string source, char expected) - { - Assert.That( - Literal.EscapeSequence.Parse(source).Value, - Is.EqualTo(expected)); - } -} +namespace Ramstack.Parsing; + +partial class LiteralTests +{ + [TestCase(@"\""", '"')] + [TestCase(@"\'", '\'')] + [TestCase(@"\/", '/')] + [TestCase(@"\\", '\\')] + [TestCase(@"\0", '\0')] + [TestCase(@"\a", '\a')] + [TestCase(@"\b", '\b')] + [TestCase(@"\e", '\e')] + [TestCase(@"\f", '\f')] + [TestCase(@"\n", '\n')] + [TestCase(@"\r", '\r')] + [TestCase(@"\t", '\t')] + [TestCase(@"\v", '\v')] + public void EscapeSequenceTest(string source, char expected) + { + Assert.That( + Literal.EscapeSequence.Parse(source).Value, + Is.EqualTo(expected)); + } +} diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.Number.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.Number.cs index 4cddb98..d9c12f4 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.Number.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.Number.cs @@ -1,143 +1,143 @@ -using System.Globalization; -using System.Numerics; - -namespace Ramstack.Parsing; - -partial class LiteralTests -{ - [Test] - public void Number_HexNumber() - { - Assert.That( - Literal.Number(NumberKind.HexNumber).Parse(long.MaxValue.ToString("x8")).Value, - Is.EqualTo(long.MaxValue)); - } - - [Test] - public void Number_BinaryNumber() - { - #if NET8_0_OR_GREATER - Assert.That( - Literal.Number(NumberKind.BinaryNumber).Parse(long.MaxValue.ToString("b8")).Value, - Is.EqualTo(long.MaxValue)); - #endif - } - - [Test] - public void Number_Byte() - { - Assert.That( - Literal.Number().Parse(byte.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(byte.MaxValue)); - } - - [Test] - public void Number_Short() - { - Assert.That( - Literal.Number().Parse(short.MinValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(short.MinValue)); - } - - [Test] - public void Number_Long() - { - Assert.That( - Literal.Number().Parse(long.MinValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(long.MinValue)); - } - - [Test] - public void Number_Int128() - { - #if NET7_0_OR_GREATER - Assert.That( - Literal.Number().Parse(Int128.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(Int128.MaxValue)); - #endif - } - - [Test] - public void Number_BigInteger() - { - const string Number = "1701411834604692317316873037158841057271701411834604692317316873037158841057271701411834604692311701411834604692"; - Assert.That( - Literal.Number().Parse(Number).Value, - Is.EqualTo(BigInteger.Parse(Number))); - } - - [Test] - public void Number_DecimalNumber() - { - Assert.That( - Literal.Number().Parse(decimal.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(decimal.MaxValue)); - } - - [Test] - public void Number_HalfNumber() - { - Assert.That( - Literal.Number().Parse(Half.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(Half.MaxValue)); - } - - [Test] - public void Number_FloatNumber() - { - Assert.That( - Literal.Number().Parse(float.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(float.MaxValue)); - } - - [Test] - public void Number_DoubleNumber() - { - Assert.That( - Literal.Number().Parse(double.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, - Is.EqualTo(double.MaxValue)); - } - - [Test] - public void Number_UnsignedNumber() - { - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((byte)123)); - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((ushort)123)); - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((uint)123)); - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((ulong)123)); - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((nuint)123)); - - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - - #if NET7_0_OR_GREATER - Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((UInt128)123)); - Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); - #endif - } - - [Test] - public void Number_Sign() - { - Assert.That( - Literal.Number(NumberKind.HexNumber).Parse("-FF").Success, - Is.False); - - Assert.That( - Literal.Number(NumberKind.HexNumber).Parse("+FF").Success, - Is.False); - - #if NET8_0_OR_GREATER - Assert.That( - Literal.Number(NumberKind.BinaryNumber).Parse("-11101").Success, - Is.False); - - Assert.That( - Literal.Number(NumberKind.BinaryNumber).Parse("+11101").Success, - Is.False); - #endif - } -} +using System.Globalization; +using System.Numerics; + +namespace Ramstack.Parsing; + +partial class LiteralTests +{ + [Test] + public void Number_HexNumber() + { + Assert.That( + Literal.Number(NumberKind.HexNumber).Parse(long.MaxValue.ToString("x8")).Value, + Is.EqualTo(long.MaxValue)); + } + + [Test] + public void Number_BinaryNumber() + { + #if NET8_0_OR_GREATER + Assert.That( + Literal.Number(NumberKind.BinaryNumber).Parse(long.MaxValue.ToString("b8")).Value, + Is.EqualTo(long.MaxValue)); + #endif + } + + [Test] + public void Number_Byte() + { + Assert.That( + Literal.Number().Parse(byte.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(byte.MaxValue)); + } + + [Test] + public void Number_Short() + { + Assert.That( + Literal.Number().Parse(short.MinValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(short.MinValue)); + } + + [Test] + public void Number_Long() + { + Assert.That( + Literal.Number().Parse(long.MinValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(long.MinValue)); + } + + [Test] + public void Number_Int128() + { + #if NET7_0_OR_GREATER + Assert.That( + Literal.Number().Parse(Int128.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(Int128.MaxValue)); + #endif + } + + [Test] + public void Number_BigInteger() + { + const string Number = "1701411834604692317316873037158841057271701411834604692317316873037158841057271701411834604692311701411834604692"; + Assert.That( + Literal.Number().Parse(Number).Value, + Is.EqualTo(BigInteger.Parse(Number))); + } + + [Test] + public void Number_DecimalNumber() + { + Assert.That( + Literal.Number().Parse(decimal.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(decimal.MaxValue)); + } + + [Test] + public void Number_HalfNumber() + { + Assert.That( + Literal.Number().Parse(Half.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(Half.MaxValue)); + } + + [Test] + public void Number_FloatNumber() + { + Assert.That( + Literal.Number().Parse(float.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(float.MaxValue)); + } + + [Test] + public void Number_DoubleNumber() + { + Assert.That( + Literal.Number().Parse(double.MaxValue.ToString(CultureInfo.InvariantCulture)).Value, + Is.EqualTo(double.MaxValue)); + } + + [Test] + public void Number_UnsignedNumber() + { + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((byte)123)); + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((ushort)123)); + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((uint)123)); + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((ulong)123)); + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((nuint)123)); + + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + + #if NET7_0_OR_GREATER + Assert.That(Literal.Number(NumberKind.Integer).Parse("+123").Value, Is.EqualTo((UInt128)123)); + Assert.That(Literal.Number(NumberKind.Integer).Parse("-123").Success, Is.False); + #endif + } + + [Test] + public void Number_Sign() + { + Assert.That( + Literal.Number(NumberKind.HexNumber).Parse("-FF").Success, + Is.False); + + Assert.That( + Literal.Number(NumberKind.HexNumber).Parse("+FF").Success, + Is.False); + + #if NET8_0_OR_GREATER + Assert.That( + Literal.Number(NumberKind.BinaryNumber).Parse("-11101").Success, + Is.False); + + Assert.That( + Literal.Number(NumberKind.BinaryNumber).Parse("+11101").Success, + Is.False); + #endif + } +} diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.String.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.String.cs index 789a481..9729e3e 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.String.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.String.cs @@ -1,62 +1,62 @@ -namespace Ramstack.Parsing; - -partial class LiteralTests -{ - [TestCase("'11111111111111111'", "11111111111111111")] - [TestCase("'1'", "1")] - [TestCase("''", "")] - [TestCase("'1\\u00452\\u00453\\u0045'", "1\u00452\u00453\u0045")] - [TestCase("'1\\r2\\n3\\t4'", "1\r2\n3\t4")] - [TestCase("'\\t'", "\t")] - [TestCase("'\\u1234'", "\u1234")] - [TestCase("'a\\u1234a'", "a\u1234a")] - [TestCase("'ab\\u1234ab'", "ab\u1234ab")] - [TestCase("'abc\\u1234abc'", "abc\u1234abc")] - public void SingleQuotedStringTest(string input, string expected) - { - var parser = Literal.SingleQuotedString; - - Assert.That(parser.Parse(input).Value, Is.EqualTo(expected)); - Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); - } - - [TestCase("\"11111111111111111\"", "11111111111111111")] - [TestCase("\"1\"", "1")] - [TestCase("\"\"", "")] - [TestCase("\"1\\u00452\"", "1\u00452")] - [TestCase("\"1\\u00452\\u00453\\u0045\"", "1\u00452\u00453\u0045")] - [TestCase("\"1\\r2\\n3\\t4\"", "1\r2\n3\t4")] - [TestCase("\"\\t\"", "\t")] - [TestCase("\"\\u1234\"", "\u1234")] - [TestCase("\"a\\u1234a\"", "a\u1234a")] - [TestCase("\"ab\\u1234ab\"", "ab\u1234ab")] - [TestCase("\"abc\\u1234abc\"", "abc\u1234abc")] - public void DoubleQuotedStringTest(string input, string expected) - { - var parser = Literal.DoubleQuotedString; - - var result = parser.Parse(input); - Assert.That(result.Value, Is.EqualTo(expected)); - Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); - } - - [TestCase("'124")] - [TestCase("'\\uabcw'")] - [TestCase("'\\u123q'")] - [TestCase("'\\z'")] - public void SingleQuotedString_Error(string input) - { - var parser = Literal.SingleQuotedString; - Assert.That(parser.Parse(input).Success, Is.False); - } - - [TestCase("\"124")] - [TestCase("\"\\uabcw\"")] - [TestCase("\"\\u123q\"")] - [TestCase("\"\\z\"")] - public void DoubleQuotedString_Error(string input) - { - var parser = Literal.DoubleQuotedString; - Assert.That(parser.Parse(input).Success, Is.False); - } -} +namespace Ramstack.Parsing; + +partial class LiteralTests +{ + [TestCase("'11111111111111111'", "11111111111111111")] + [TestCase("'1'", "1")] + [TestCase("''", "")] + [TestCase("'1\\u00452\\u00453\\u0045'", "1\u00452\u00453\u0045")] + [TestCase("'1\\r2\\n3\\t4'", "1\r2\n3\t4")] + [TestCase("'\\t'", "\t")] + [TestCase("'\\u1234'", "\u1234")] + [TestCase("'a\\u1234a'", "a\u1234a")] + [TestCase("'ab\\u1234ab'", "ab\u1234ab")] + [TestCase("'abc\\u1234abc'", "abc\u1234abc")] + public void SingleQuotedStringTest(string input, string expected) + { + var parser = Literal.SingleQuotedString; + + Assert.That(parser.Parse(input).Value, Is.EqualTo(expected)); + Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); + } + + [TestCase("\"11111111111111111\"", "11111111111111111")] + [TestCase("\"1\"", "1")] + [TestCase("\"\"", "")] + [TestCase("\"1\\u00452\"", "1\u00452")] + [TestCase("\"1\\u00452\\u00453\\u0045\"", "1\u00452\u00453\u0045")] + [TestCase("\"1\\r2\\n3\\t4\"", "1\r2\n3\t4")] + [TestCase("\"\\t\"", "\t")] + [TestCase("\"\\u1234\"", "\u1234")] + [TestCase("\"a\\u1234a\"", "a\u1234a")] + [TestCase("\"ab\\u1234ab\"", "ab\u1234ab")] + [TestCase("\"abc\\u1234abc\"", "abc\u1234abc")] + public void DoubleQuotedStringTest(string input, string expected) + { + var parser = Literal.DoubleQuotedString; + + var result = parser.Parse(input); + Assert.That(result.Value, Is.EqualTo(expected)); + Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); + } + + [TestCase("'124")] + [TestCase("'\\uabcw'")] + [TestCase("'\\u123q'")] + [TestCase("'\\z'")] + public void SingleQuotedString_Error(string input) + { + var parser = Literal.SingleQuotedString; + Assert.That(parser.Parse(input).Success, Is.False); + } + + [TestCase("\"124")] + [TestCase("\"\\uabcw\"")] + [TestCase("\"\\u123q\"")] + [TestCase("\"\\z\"")] + public void DoubleQuotedString_Error(string input) + { + var parser = Literal.DoubleQuotedString; + Assert.That(parser.Parse(input).Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs index 7f9681f..8a8b2e3 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.UnicodeEscapeSequence.cs @@ -1,48 +1,48 @@ -using static Ramstack.Parsing.Parser; -using static Ramstack.Parsing.Literal; - -namespace Ramstack.Parsing; - -partial class LiteralTests -{ - [TestCase(@"\u0063", '\u0063')] - [TestCase(@"\u0041", '\u0041')] - [TestCase(@"\u0020", '\u0020')] - [TestCase(@"\uDD20", '\uDD20')] - [TestCase(@"\u0000", '\u0000')] - [TestCase(@"\uFFFF", '\uFFFF')] - [TestCase(@"\uBEEF", '\uBEEF')] - [TestCase(@"\ubeef", '\uBEEF')] - [TestCase(@"\u1234", '\u1234')] - [TestCase(@"\u4321", '\u4321')] - [TestCase(@"\uabcd", '\uABCD')] - [TestCase(@"\uDCBA", '\uDCBA')] - public void UnicodeEscapeSequenceTest(string input, char symbol) - { - var parser = UnicodeEscapeSequence.ThenIgnore(Eof); - Assert.That(parser.Parse(input).Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((0, 6))); - Assert.That(parser.Parse(input).Value, Is.EqualTo(symbol)); - Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); - } - - [TestCase(@"\u")] - [TestCase(@"\u0")] - [TestCase(@"\u01")] - [TestCase(@"\u012")] - [TestCase(@"\u012g")] - [TestCase(@"\uqqqq")] - [TestCase(@"\u000q")] - [TestCase(@"\u00q0")] - [TestCase(@"\u0q00")] - [TestCase(@"\uq000")] - [TestCase(@"\uя000")] - [TestCase(@"\u0я00")] - [TestCase(@"\u00я0")] - [TestCase(@"\u000я")] - public void UnicodeEscapeSequence_InvalidInput(string input) - { - var parser = UnicodeEscapeSequence.ThenIgnore(Eof); - Assert.That(parser.Parse(input).Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; +using static Ramstack.Parsing.Literal; + +namespace Ramstack.Parsing; + +partial class LiteralTests +{ + [TestCase(@"\u0063", '\u0063')] + [TestCase(@"\u0041", '\u0041')] + [TestCase(@"\u0020", '\u0020')] + [TestCase(@"\uDD20", '\uDD20')] + [TestCase(@"\u0000", '\u0000')] + [TestCase(@"\uFFFF", '\uFFFF')] + [TestCase(@"\uBEEF", '\uBEEF')] + [TestCase(@"\ubeef", '\uBEEF')] + [TestCase(@"\u1234", '\u1234')] + [TestCase(@"\u4321", '\u4321')] + [TestCase(@"\uabcd", '\uABCD')] + [TestCase(@"\uDCBA", '\uDCBA')] + public void UnicodeEscapeSequenceTest(string input, char symbol) + { + var parser = UnicodeEscapeSequence.ThenIgnore(Eof); + Assert.That(parser.Parse(input).Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((0, 6))); + Assert.That(parser.Parse(input).Value, Is.EqualTo(symbol)); + Assert.That(parser.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(input)); + } + + [TestCase(@"\u")] + [TestCase(@"\u0")] + [TestCase(@"\u01")] + [TestCase(@"\u012")] + [TestCase(@"\u012g")] + [TestCase(@"\uqqqq")] + [TestCase(@"\u000q")] + [TestCase(@"\u00q0")] + [TestCase(@"\u0q00")] + [TestCase(@"\uq000")] + [TestCase(@"\uя000")] + [TestCase(@"\u0я00")] + [TestCase(@"\u00я0")] + [TestCase(@"\u000я")] + public void UnicodeEscapeSequence_InvalidInput(string input) + { + var parser = UnicodeEscapeSequence.ThenIgnore(Eof); + Assert.That(parser.Parse(input).Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/LiteralTests.cs b/tests/Ramstack.Parsing.Tests/LiteralTests.cs index 8fbeea7..257f625 100644 --- a/tests/Ramstack.Parsing.Tests/LiteralTests.cs +++ b/tests/Ramstack.Parsing.Tests/LiteralTests.cs @@ -1,4 +1,4 @@ -namespace Ramstack.Parsing; - -[TestFixture] -public partial class LiteralTests; +namespace Ramstack.Parsing; + +[TestFixture] +public partial class LiteralTests; diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.And.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.And.cs index 6be7c26..8e137db 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.And.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.And.cs @@ -1,18 +1,18 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void AndTest() - { - var parser = And(L('a')); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 0))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("b").Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void AndTest() + { + var parser = And(L('a')); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 0))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("b").Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Any.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Any.cs index 72e628d..5a64d88 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Any.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Any.cs @@ -1,19 +1,19 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void AnyTest() - { - Assert.That(Any.Parse("1").Success, Is.True); - Assert.That(Any.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - Assert.That(Any.Map(m => m.ToString()).Parse("1").Value, Is.EqualTo("1")); - Assert.That(Any.Parse("1").Value, Is.EqualTo('1')); - Assert.That(Any.Parse("").Success, Is.False); - Assert.That(Any.Then(Any).Parse("12").Success, Is.True); - Assert.That(Any.Then(Any).Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((1, 1))); - Assert.That(Any.Then(Any).Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("2")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void AnyTest() + { + Assert.That(Any.Parse("1").Success, Is.True); + Assert.That(Any.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + Assert.That(Any.Map(m => m.ToString()).Parse("1").Value, Is.EqualTo("1")); + Assert.That(Any.Parse("1").Value, Is.EqualTo('1')); + Assert.That(Any.Parse("").Success, Is.False); + Assert.That(Any.Then(Any).Parse("12").Success, Is.True); + Assert.That(Any.Then(Any).Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((1, 1))); + Assert.That(Any.Then(Any).Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("2")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.As.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.As.cs index 0d3efce..f3b8769 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.As.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.As.cs @@ -1,14 +1,14 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void AsTest() - { - Assert.That( - Any.As("character").Parse("").ErrorMessage, - Is.EqualTo("(1:1) Expected character")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void AsTest() + { + Assert.That( + Any.As("character").Parse("").ErrorMessage, + Is.EqualTo("(1:1) Expected character")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Between.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Between.cs index 00cea56..acdcf5f 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Between.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Between.cs @@ -1,43 +1,43 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void BetweenTest() - { - var parser = Any.Between(L("<["), L("]>")); - - Assert.That(parser.Parse("<[a]>").Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("<[a]>").Value, Is.EqualTo((2, 1))); - Assert.That(parser.Map(m => m.ToString()).Parse("<[a]>").Value, Is.EqualTo("a")); - Assert.That(parser.Parse("<[a]>").Value, Is.EqualTo('a')); - - Assert.That(parser.Parse("<[]>").Success, Is.False); - Assert.That(parser.Parse("[]").Success, Is.False); - Assert.That(parser.Parse("<[a]").Success, Is.False); - Assert.That(parser.Parse("<[a>").Success, Is.False); - Assert.That(parser.Parse("[a]>").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("[a]").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - } - - [Test] - public void BetweenTest_2() - { - var parser = Character.Digit.Between(L('[').Repeat(1, 2), L(']')); - Assert.That(parser.Parse("[1]").Success, Is.EqualTo(true)); - Assert.That(parser.Parse("[[1]").Success, Is.EqualTo(true)); - Assert.That(parser.Parse("[c]").Success, Is.EqualTo(false)); - Assert.That(parser.Parse("[c]").ToString(), Is.EqualTo("(1:2) Expected '[' or digit")); - Assert.That(parser.Parse("1]").ToString(), Is.EqualTo("(1:1) Expected '['")); - Assert.That(parser.Parse("(1]").ToString(), Is.EqualTo("(1:1) Expected '['")); - Assert.That(parser.Parse("[1)").ToString(), Is.EqualTo("(1:3) Expected ']'")); - Assert.That(parser.Parse("[1").ToString(), Is.EqualTo("(1:3) Expected ']'")); - Assert.That(parser.Parse("[[1").ToString(), Is.EqualTo("(1:4) Expected ']'")); - Assert.That(parser.Parse("[(1]").ToString(), Is.EqualTo("(1:2) Expected '[' or digit")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void BetweenTest() + { + var parser = Any.Between(L("<["), L("]>")); + + Assert.That(parser.Parse("<[a]>").Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("<[a]>").Value, Is.EqualTo((2, 1))); + Assert.That(parser.Map(m => m.ToString()).Parse("<[a]>").Value, Is.EqualTo("a")); + Assert.That(parser.Parse("<[a]>").Value, Is.EqualTo('a')); + + Assert.That(parser.Parse("<[]>").Success, Is.False); + Assert.That(parser.Parse("[]").Success, Is.False); + Assert.That(parser.Parse("<[a]").Success, Is.False); + Assert.That(parser.Parse("<[a>").Success, Is.False); + Assert.That(parser.Parse("[a]>").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("[a]").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + } + + [Test] + public void BetweenTest_2() + { + var parser = Character.Digit.Between(L('[').Repeat(1, 2), L(']')); + Assert.That(parser.Parse("[1]").Success, Is.EqualTo(true)); + Assert.That(parser.Parse("[[1]").Success, Is.EqualTo(true)); + Assert.That(parser.Parse("[c]").Success, Is.EqualTo(false)); + Assert.That(parser.Parse("[c]").ToString(), Is.EqualTo("(1:2) Expected '[' or digit")); + Assert.That(parser.Parse("1]").ToString(), Is.EqualTo("(1:1) Expected '['")); + Assert.That(parser.Parse("(1]").ToString(), Is.EqualTo("(1:1) Expected '['")); + Assert.That(parser.Parse("[1)").ToString(), Is.EqualTo("(1:3) Expected ']'")); + Assert.That(parser.Parse("[1").ToString(), Is.EqualTo("(1:3) Expected ']'")); + Assert.That(parser.Parse("[[1").ToString(), Is.EqualTo("(1:4) Expected ']'")); + Assert.That(parser.Parse("[(1]").ToString(), Is.EqualTo("(1:2) Expected '[' or digit")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Char.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Char.cs index 37c59c9..739c96c 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Char.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Char.cs @@ -1,50 +1,50 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void CharTest() - { - var parser = L('a'); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); - Assert.That(parser.Map(m => m.ToString()).Parse("a").Value, Is.EqualTo("a")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - } - - [TestCase("a")] - [TestCase("ab")] - [TestCase("ace")] - [TestCase("adgl")] - [TestCase("adglp")] - [TestCase("adglpv")] - [TestCase("abc")] - [TestCase("abcd")] - [TestCase("abcde")] - [TestCase("abcdef")] - [TestCase("abcdefgh")] - [TestCase("0123456789")] - public void OneOf_CharTest(string chars) - { - var parser = OneOf(chars); - - foreach (var c in chars) - { - var source = c.ToString(); - - Assert.That(parser.Parse(source).Value, Is.EqualTo(c)); - Assert.That(parser.Parse(source).Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(source).Value, Is.EqualTo((0, 1))); - Assert.That(parser.Map(m => m.ToString()).Parse(source).Value, Is.EqualTo(source)); - } - - Assert.That(parser.Parse("*").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void CharTest() + { + var parser = L('a'); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); + Assert.That(parser.Map(m => m.ToString()).Parse("a").Value, Is.EqualTo("a")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + } + + [TestCase("a")] + [TestCase("ab")] + [TestCase("ace")] + [TestCase("adgl")] + [TestCase("adglp")] + [TestCase("adglpv")] + [TestCase("abc")] + [TestCase("abcd")] + [TestCase("abcde")] + [TestCase("abcdef")] + [TestCase("abcdefgh")] + [TestCase("0123456789")] + public void OneOf_CharTest(string chars) + { + var parser = OneOf(chars); + + foreach (var c in chars) + { + var source = c.ToString(); + + Assert.That(parser.Parse(source).Value, Is.EqualTo(c)); + Assert.That(parser.Parse(source).Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(source).Value, Is.EqualTo((0, 1))); + Assert.That(parser.Map(m => m.ToString()).Parse(source).Value, Is.EqualTo(source)); + } + + Assert.That(parser.Parse("*").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Choice.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Choice.cs index 429963a..910328a 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Choice.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Choice.cs @@ -1,50 +1,50 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void ChoiceTest_1() - { - var parsers = Choice(L("Sun"), L("Sunset")); - - Assert.That( - parsers.Parse("Sunset").Success, - Is.True); - - Assert.That( - parsers.Parse("Sunset").Value, - Is.EqualTo("Sun")); - - Assert.That( - parsers.Parse("Sun").Value, - Is.EqualTo("Sun")); - - Assert.That( - parsers.Map(m => (m.Index, m.Length)).Parse("Sunset").Value, - Is.EqualTo((0, 3))); - } - - [Test] - public void ChoiceTest_2() - { - var parsers = Choice(L("Sun"), L("Sunset"), L("Book"), L("Bookstore")); - - Assert.That( - parsers.Parse("Sunset").Success, - Is.True); - - Assert.That( - parsers.Parse("Sunset").Value, - Is.EqualTo("Sun")); - - Assert.That( - parsers.Parse("Sun").Value, - Is.EqualTo("Sun")); - - Assert.That( - parsers.Map(m => (m.Index, m.Length)).Parse("Sunset").Value, - Is.EqualTo((0, 3))); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void ChoiceTest_1() + { + var parsers = Choice(L("Sun"), L("Sunset")); + + Assert.That( + parsers.Parse("Sunset").Success, + Is.True); + + Assert.That( + parsers.Parse("Sunset").Value, + Is.EqualTo("Sun")); + + Assert.That( + parsers.Parse("Sun").Value, + Is.EqualTo("Sun")); + + Assert.That( + parsers.Map(m => (m.Index, m.Length)).Parse("Sunset").Value, + Is.EqualTo((0, 3))); + } + + [Test] + public void ChoiceTest_2() + { + var parsers = Choice(L("Sun"), L("Sunset"), L("Book"), L("Bookstore")); + + Assert.That( + parsers.Parse("Sunset").Success, + Is.True); + + Assert.That( + parsers.Parse("Sunset").Value, + Is.EqualTo("Sun")); + + Assert.That( + parsers.Parse("Sun").Value, + Is.EqualTo("Sun")); + + Assert.That( + parsers.Map(m => (m.Index, m.Length)).Parse("Sunset").Value, + Is.EqualTo((0, 3))); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Choice_Opt.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Choice_Opt.cs index c5e770f..0cae6b9 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Choice_Opt.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Choice_Opt.cs @@ -1,153 +1,153 @@ -using System.Globalization; -using System.Reflection; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void Choice_CharClass_ShouldMerged_1() - { - var parser = Choice( - L('b'), - Set("a-c"), - Set("d-z"), - Set("0-5"), - Set("3-7"), - Set("6-9"), - OneOf("abcdefg"), - L(UnicodeCategory.DecimalDigitNumber), - L(GeneralUnicodeCategory.DecimalDigitNumber)); - - Assert.That(parser.Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - } - - [Test] - public void Choice_CharClass_ShouldMerged_2() - { - var parser = Choice( - L('b'), - Set("a-c"), - Set("d-z"), - Literal.EscapeSequence, - Set("0-5"), - Set("3-7"), - Set("6-9"), - OneOf("abcdefg"), - L(GeneralUnicodeCategory.DecimalDigitNumber)); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+DeferredDiagnosticChoiceParser`1[System.Char]")); - - var parsers = (Parser[])parser.GetType().GetField("_parsers", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(parser)!; - Assert.That(parsers.Length, Is.EqualTo(2)); - - Assert.That(parsers[0].Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); - Assert.That(parsers[0].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - - Assert.That(parsers[1].Name, Is.EqualTo(@"escape sequence")); - Assert.That(parsers[1].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Literal+EscapeSequenceParser`1[System.Char]")); - } - - [Test] - public void Choice_CharClass_ShouldMerged_3() - { - var parser = Choice( - L('c'), - Set("a-c"), - Set("d-z"), - Not(Eof).Then(Literal.EscapeSequence), - Set("0-5"), - Set("3-7"), - Literal.UnicodeEscapeSequence, - Set("6-9"), - OneOf("abcdefg"), - L(GeneralUnicodeCategory.DecimalDigitNumber)); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+ChoiceParser`1[System.Char]")); - - var parsers = (Parser[])parser.GetType().GetProperty("Parsers", BindingFlags.Instance | BindingFlags.Public)!.GetValue(parser)!; - - Assert.That(parsers.Length, Is.EqualTo(3)); - - Assert.That(parsers[0].Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); - Assert.That(parsers[0].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - Assert.That(parsers[1].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+ThenParser`1[System.Char]")); - Assert.That(parsers[2].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Literal+UnicodeEscapeSequenceParser`1[System.Char]")); - } - - [Test] - public void ChoiceTest_UnicodeCategories() - { - var parser = Choice( - Character.Uppercase, - Character.Digit, - L(UnicodeCategory.LowercaseLetter), - L(GeneralUnicodeCategory.DecimalDigitNumber)); - - Assert.That(parser.Name, Is.EqualTo(@"[\p{Lu}\p{Ll}\p{Nd}]")); - - Assert.That( - parser.GetType().ToString(), - Is.EqualTo("Ramstack.Parsing.Parser+UnicodeCategoryParser`1[System.Char]")); - - Assert.That(parser.Parse("A").Success, Is.True); - Assert.That(parser.Parse("A").Value, Is.EqualTo('A')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("A").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("1").Success, Is.True); - Assert.That(parser.Parse("1").Value, Is.EqualTo('1')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("*").Success, Is.False); - Assert.That(parser.Parse("*").ToString(), Is.EqualTo(@"(1:1) Expected [\p{Lu}\p{Ll}\p{Nd}]")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("*").Value, Is.EqualTo((0, 0))); - } - - [Test] - public void ChoiceTest_CharClass() - { - var parser1 = Choice(Set("a-e"), Set("A-C"), Set("0-9"), L('~')); - var parser2 = Choice(Character.Control, Set("a-z"), OneOf("0123456789"), Set('\0', '\u007f')); - var parser3 = Choice(Character.Control, Set("A-E"), L('z'), OneOf("0123456789"), Set('\0', '\u007f')); - var parser4 = Choice(Character.Control, Set('\0', '\u007f')); - - Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - Assert.That(parser4.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - - Assert.That(parser1.Name, Is.EqualTo("[0-9A-Ca-e~]")); - Assert.That(parser2.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); - Assert.That(parser3.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); - Assert.That(parser4.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); - - foreach (var parser in new[] { parser1, parser2, parser3, parser4 }) - { - Assert.That(parser.Parse("A").Success, Is.True); - Assert.That(parser.Parse("A").Value, Is.EqualTo('A')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("A").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("1").Success, Is.True); - Assert.That(parser.Parse("1").Value, Is.EqualTo('1')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("~").Success, Is.True); - Assert.That(parser.Parse("~").Value, Is.EqualTo('~')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("~").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("Ф").Success, Is.False); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("Ф").Value, Is.EqualTo((0, 0))); - } - } -} +using System.Globalization; +using System.Reflection; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void Choice_CharClass_ShouldMerged_1() + { + var parser = Choice( + L('b'), + Set("a-c"), + Set("d-z"), + Set("0-5"), + Set("3-7"), + Set("6-9"), + OneOf("abcdefg"), + L(UnicodeCategory.DecimalDigitNumber), + L(GeneralUnicodeCategory.DecimalDigitNumber)); + + Assert.That(parser.Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + } + + [Test] + public void Choice_CharClass_ShouldMerged_2() + { + var parser = Choice( + L('b'), + Set("a-c"), + Set("d-z"), + Literal.EscapeSequence, + Set("0-5"), + Set("3-7"), + Set("6-9"), + OneOf("abcdefg"), + L(GeneralUnicodeCategory.DecimalDigitNumber)); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+DeferredDiagnosticChoiceParser`1[System.Char]")); + + var parsers = (Parser[])parser.GetType().GetField("_parsers", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(parser)!; + Assert.That(parsers.Length, Is.EqualTo(2)); + + Assert.That(parsers[0].Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); + Assert.That(parsers[0].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + + Assert.That(parsers[1].Name, Is.EqualTo(@"escape sequence")); + Assert.That(parsers[1].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Literal+EscapeSequenceParser`1[System.Char]")); + } + + [Test] + public void Choice_CharClass_ShouldMerged_3() + { + var parser = Choice( + L('c'), + Set("a-c"), + Set("d-z"), + Not(Eof).Then(Literal.EscapeSequence), + Set("0-5"), + Set("3-7"), + Literal.UnicodeEscapeSequence, + Set("6-9"), + OneOf("abcdefg"), + L(GeneralUnicodeCategory.DecimalDigitNumber)); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+ChoiceParser`1[System.Char]")); + + var parsers = (Parser[])parser.GetType().GetProperty("Parsers", BindingFlags.Instance | BindingFlags.Public)!.GetValue(parser)!; + + Assert.That(parsers.Length, Is.EqualTo(3)); + + Assert.That(parsers[0].Name, Is.EqualTo(@"[0-9a-z\p{Nd}]")); + Assert.That(parsers[0].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + Assert.That(parsers[1].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+ThenParser`1[System.Char]")); + Assert.That(parsers[2].GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Literal+UnicodeEscapeSequenceParser`1[System.Char]")); + } + + [Test] + public void ChoiceTest_UnicodeCategories() + { + var parser = Choice( + Character.Uppercase, + Character.Digit, + L(UnicodeCategory.LowercaseLetter), + L(GeneralUnicodeCategory.DecimalDigitNumber)); + + Assert.That(parser.Name, Is.EqualTo(@"[\p{Lu}\p{Ll}\p{Nd}]")); + + Assert.That( + parser.GetType().ToString(), + Is.EqualTo("Ramstack.Parsing.Parser+UnicodeCategoryParser`1[System.Char]")); + + Assert.That(parser.Parse("A").Success, Is.True); + Assert.That(parser.Parse("A").Value, Is.EqualTo('A')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("A").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("1").Success, Is.True); + Assert.That(parser.Parse("1").Value, Is.EqualTo('1')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("*").Success, Is.False); + Assert.That(parser.Parse("*").ToString(), Is.EqualTo(@"(1:1) Expected [\p{Lu}\p{Ll}\p{Nd}]")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("*").Value, Is.EqualTo((0, 0))); + } + + [Test] + public void ChoiceTest_CharClass() + { + var parser1 = Choice(Set("a-e"), Set("A-C"), Set("0-9"), L('~')); + var parser2 = Choice(Character.Control, Set("a-z"), OneOf("0123456789"), Set('\0', '\u007f')); + var parser3 = Choice(Character.Control, Set("A-E"), L('z'), OneOf("0123456789"), Set('\0', '\u007f')); + var parser4 = Choice(Character.Control, Set('\0', '\u007f')); + + Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + Assert.That(parser4.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + + Assert.That(parser1.Name, Is.EqualTo("[0-9A-Ca-e~]")); + Assert.That(parser2.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); + Assert.That(parser3.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); + Assert.That(parser4.Name, Is.EqualTo(@"[\0-\u007f\p{Cc}]")); + + foreach (var parser in new[] { parser1, parser2, parser3, parser4 }) + { + Assert.That(parser.Parse("A").Success, Is.True); + Assert.That(parser.Parse("A").Value, Is.EqualTo('A')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("A").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("1").Success, Is.True); + Assert.That(parser.Parse("1").Value, Is.EqualTo('1')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("~").Success, Is.True); + Assert.That(parser.Parse("~").Value, Is.EqualTo('~')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("~").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("Ф").Success, Is.False); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("Ф").Value, Is.EqualTo((0, 0))); + } + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.DefaultOnFail.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.DefaultOnFail.cs index 214568d..f7efe68 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.DefaultOnFail.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.DefaultOnFail.cs @@ -1,38 +1,38 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void DefaultOnFail_ValueType() - { - var parser = L('a').DefaultOnFail(); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("b").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); - Assert.That(parser.Parse("b").Value, Is.EqualTo('\0')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("abc").Value, Is.EqualTo((0, 1))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("bac").Value, Is.EqualTo((0, 0))); - - parser = parser.DefaultOnFail('!'); - Assert.That(parser.Parse("b").Value, Is.EqualTo('!')); - } - - [Test] - public void DefaultOnFail_ReferenceType() - { - var parser = L("true").DefaultOnFail(); - - Assert.That(parser.Parse("true").Success, Is.True); - Assert.That(parser.Parse("test").Success, Is.True); - Assert.That(parser.Parse("true").Value, Is.EqualTo("true")); - Assert.That(parser.Parse("test").Value, Is.Null); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("true").Value, Is.EqualTo((0, 4))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("test").Value, Is.EqualTo((0, 0))); - - Parser newParser = parser.DefaultOnFail("Wow!")!; - Assert.That(newParser.Parse("test").Value, Is.EqualTo("Wow!")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void DefaultOnFail_ValueType() + { + var parser = L('a').DefaultOnFail(); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("b").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EqualTo('a')); + Assert.That(parser.Parse("b").Value, Is.EqualTo('\0')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("abc").Value, Is.EqualTo((0, 1))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("bac").Value, Is.EqualTo((0, 0))); + + parser = parser.DefaultOnFail('!'); + Assert.That(parser.Parse("b").Value, Is.EqualTo('!')); + } + + [Test] + public void DefaultOnFail_ReferenceType() + { + var parser = L("true").DefaultOnFail(); + + Assert.That(parser.Parse("true").Success, Is.True); + Assert.That(parser.Parse("test").Success, Is.True); + Assert.That(parser.Parse("true").Value, Is.EqualTo("true")); + Assert.That(parser.Parse("test").Value, Is.Null); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("true").Value, Is.EqualTo((0, 4))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("test").Value, Is.EqualTo((0, 0))); + + Parser newParser = parser.DefaultOnFail("Wow!")!; + Assert.That(newParser.Parse("test").Value, Is.EqualTo("Wow!")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Eof.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Eof.cs index e820c49..e0fe46e 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Eof.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Eof.cs @@ -1,20 +1,20 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [TestCase("")] - [TestCase("1")] - [TestCase("\n")] - public void EofTest(string input) - { - Assert.That(Eof.Parse(input).Success, Is.EqualTo(input.Length == 0)); - - if (input.Length != 0) - return; - - Assert.That(Eol.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((0, 0))); - Assert.That(Eol.Map(m => m.ToString()).Parse(input).Value, Is.Empty); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [TestCase("")] + [TestCase("1")] + [TestCase("\n")] + public void EofTest(string input) + { + Assert.That(Eof.Parse(input).Success, Is.EqualTo(input.Length == 0)); + + if (input.Length != 0) + return; + + Assert.That(Eol.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((0, 0))); + Assert.That(Eol.Map(m => m.ToString()).Parse(input).Value, Is.Empty); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Eol.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Eol.cs index 3e0c9db..d6e49fd 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Eol.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Eol.cs @@ -1,28 +1,28 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [TestCase("", true, 0, 0, "")] - [TestCase("1", false, 0, 0, null)] - [TestCase("\n", true, 0, 1, "\n")] - [TestCase("\r", true, 0, 1, "\r")] - [TestCase("\r\n", true, 0, 2, "\r\n")] - [TestCase("\n\n", true, 0, 1, "\n")] - [TestCase("\n\r", true, 0, 1, "\n")] - [TestCase("\r\r", true, 0, 1, "\r")] - [TestCase("\u0085", true, 0, 1, "\u0085")] - [TestCase("\u2028", true, 0, 1, "\u2028")] - [TestCase("\u2029", true, 0, 1, "\u2029")] - public void EolTest(string input, bool succeed, int index, int length, string value) - { - Assert.That(Eol.Parse(input).Success, Is.EqualTo(succeed)); - - if (!succeed) - return; - - Assert.That(Eol.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((index, length))); - Assert.That(Eol.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(value)); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [TestCase("", true, 0, 0, "")] + [TestCase("1", false, 0, 0, null)] + [TestCase("\n", true, 0, 1, "\n")] + [TestCase("\r", true, 0, 1, "\r")] + [TestCase("\r\n", true, 0, 2, "\r\n")] + [TestCase("\n\n", true, 0, 1, "\n")] + [TestCase("\n\r", true, 0, 1, "\n")] + [TestCase("\r\r", true, 0, 1, "\r")] + [TestCase("\u0085", true, 0, 1, "\u0085")] + [TestCase("\u2028", true, 0, 1, "\u2028")] + [TestCase("\u2029", true, 0, 1, "\u2029")] + public void EolTest(string input, bool succeed, int index, int length, string value) + { + Assert.That(Eol.Parse(input).Success, Is.EqualTo(succeed)); + + if (!succeed) + return; + + Assert.That(Eol.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((index, length))); + Assert.That(Eol.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(value)); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Fail.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Fail.cs index 18c4723..059e3f5 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Fail.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Fail.cs @@ -1,15 +1,15 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void FailTest() - { - var parser = Fail("failure message"); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:1) failure message")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void FailTest() + { + var parser = Fail("failure message"); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:1) failure message")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Fold.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Fold.cs index f158e5d..bea7a1a 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Fold.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Fold.cs @@ -1,227 +1,227 @@ -using System.Numerics; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [TestCase("1", 1, 1)] - [TestCase("1+", 1, 1)] - [TestCase("1+2$", 3, 3)] - [TestCase("1+2-", 3, 3)] - [TestCase("1+2+3+4-2", 8, 9)] - [TestCase("1+2+3+4-2$", 8, 9)] - public void FoldTest(string expr, int result, int length) - { - var number = Literal.Number(); - var parser = number.Fold(OneOf("+-"), (l, r, o) => o == '+' ? l + r : l - r); - - Assert.That(parser.Parse(expr).Success, Is.True); - Assert.That(parser.Parse(expr).Value, Is.EqualTo(result)); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(expr).Value, Is.EqualTo((0, length))); - } - - [TestCase("2", "2", 1)] - [TestCase("2$", "2", 1)] - [TestCase("2**", "2", 1)] - [TestCase("2**3", "8", 4)] - [TestCase("2**3**", "8", 4)] - [TestCase("2**3**4**1$", "2417851639229258349412352", 10)] - public void FoldRTest(string expr, string result, int length) - { - var number = Literal.Number(); - var parser = number.FoldR(L("**"), (l, r, _) => BigInteger.Pow(l, (int)r)); - - Assert.That(parser.Parse(expr).Success, Is.True); - Assert.That(parser.Parse(expr).Value, Is.EqualTo(BigInteger.Parse(result))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(expr).Value, Is.EqualTo((0, length))); - } - - [Test] - public void Fold_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative) - { - var operand = new BoundedParser(Return(2)); - var op = Return('-'); - - var reductions = 0; - var reduce = (int l, int r, char _) => - { - reductions++; - return l - r; - }; - - var parser = rightAssociative - ? operand.FoldR(op, reduce) - : operand.Fold(op, reduce); - - var context = new ParseContext("!tail"); - context.Advance(1); - - var success = parser.TryParse(ref context, out var value); - - Assert.That(success, Is.True); - Assert.That(value, Is.EqualTo(2)); - Assert.That(reductions, Is.Zero); - - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); - } - - [Test] - public void FoldVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative) - { - var operand = new BoundedParser(Return(2)); - var op = Return('-'); - - var reductions = 0; - var reduce = (int l, int r, char _) => - { - reductions++; - return l - r; - }; - - var parser = rightAssociative - ? operand.FoldR(op, reduce) - : operand.Fold(op, reduce); - - var context = new ParseContext("!tail"); - context.Advance(1); - - var success = parser.Void().TryParse(ref context, out _); - - Assert.That(success, Is.True); - Assert.That(reductions, Is.Zero); - - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); - } - - [TestCase(false, 6)] - [TestCase(true, 8)] - public void Fold_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, int expected) - { - var operand = new BoundedParser( - Literal.Number().DefaultOnFail(2) - ); - - var op = L('-').DefaultOnFail('-'); - - var reductions = 0; - var reduce = (int l, int r, char _) => - { - reductions++; - return l - r; - }; - - var parser = rightAssociative - ? operand.FoldR(op, reduce) - : operand.Fold(op, reduce); - - var result = parser.Parse("10-3-1!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Value, Is.EqualTo(expected)); - Assert.That(result.Length, Is.EqualTo(6)); - Assert.That(reductions, Is.EqualTo(2)); - } - - [Test] - public void FoldVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightAssociative) - { - var operand = new BoundedParser( - Literal.Number().DefaultOnFail(2) - ); - - var op = L('-').DefaultOnFail('-'); - - var reductions = 0; - var reduce = (int l, int r, char _) => - { - reductions++; - return l - r; - }; - - var parser = rightAssociative - ? operand.FoldR(op, reduce) - : operand.Fold(op, reduce); - - var result = parser.Void().Parse("10-3-1!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Length, Is.EqualTo(6)); - Assert.That(reductions, Is.Zero); - } - - [TestCase(false, -4)] - [TestCase(true, 2)] - public void Fold_OnlyOperandConsumesInput_ContinuesParsing(bool rightAssociative, int expected) - { - var operand = Set('0', '9').Do(c => c - '0'); - var op = Return('-'); - - var parser = rightAssociative - ? operand.FoldR(op, (l, r, _) => l - r) - : operand.Fold(op, (l, r, _) => l - r); - - var result = parser.Parse("123!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Value, Is.EqualTo(expected)); - Assert.That(result.Length, Is.EqualTo(3)); - } - - [Test] - public void FoldVoid_OnlyOperandConsumesInput_ContinuesParsing([Values] bool rightAssociative) - { - var operand = Set('0', '9').Do(c => c - '0'); - var op = Return('-'); - - var parser = rightAssociative - ? operand.FoldR(op, (l, r, _) => l - r) - : operand.Fold(op, (l, r, _) => l - r); - - var result = parser.Void().Parse("123!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Length, Is.EqualTo(3)); - } - - [TestCase(false, -2)] - [TestCase(true, 2)] - public void Fold_OnlyOperatorConsumesInput_ContinuesParsing(bool rightAssociative, int expected) - { - var operand = Return(2); - var op = L('-'); - - var parser = rightAssociative - ? operand.FoldR(op, (l, r, _) => l - r) - : operand.Fold(op, (l, r, _) => l - r); - - var result = parser.Parse("--!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Value, Is.EqualTo(expected)); - Assert.That(result.Length, Is.EqualTo(2)); - } - - [Test] - public void FoldVoid_OnlyOperatorConsumesInput_ContinuesParsing([Values] bool rightAssociative) - { - var operand = Return(2); - var op = L('-'); - - var parser = rightAssociative - ? operand.FoldR(op, (l, r, _) => l - r) - : operand.Fold(op, (l, r, _) => l - r); - - var result = parser.Void().Parse("--!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Length, Is.EqualTo(2)); - } -} +using System.Numerics; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [TestCase("1", 1, 1)] + [TestCase("1+", 1, 1)] + [TestCase("1+2$", 3, 3)] + [TestCase("1+2-", 3, 3)] + [TestCase("1+2+3+4-2", 8, 9)] + [TestCase("1+2+3+4-2$", 8, 9)] + public void FoldTest(string expr, int result, int length) + { + var number = Literal.Number(); + var parser = number.Fold(OneOf("+-"), (l, r, o) => o == '+' ? l + r : l - r); + + Assert.That(parser.Parse(expr).Success, Is.True); + Assert.That(parser.Parse(expr).Value, Is.EqualTo(result)); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(expr).Value, Is.EqualTo((0, length))); + } + + [TestCase("2", "2", 1)] + [TestCase("2$", "2", 1)] + [TestCase("2**", "2", 1)] + [TestCase("2**3", "8", 4)] + [TestCase("2**3**", "8", 4)] + [TestCase("2**3**4**1$", "2417851639229258349412352", 10)] + public void FoldRTest(string expr, string result, int length) + { + var number = Literal.Number(); + var parser = number.FoldR(L("**"), (l, r, _) => BigInteger.Pow(l, (int)r)); + + Assert.That(parser.Parse(expr).Success, Is.True); + Assert.That(parser.Parse(expr).Value, Is.EqualTo(BigInteger.Parse(result))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(expr).Value, Is.EqualTo((0, length))); + } + + [Test] + public void Fold_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative) + { + var operand = new BoundedParser(Return(2)); + var op = Return('-'); + + var reductions = 0; + var reduce = (int l, int r, char _) => + { + reductions++; + return l - r; + }; + + var parser = rightAssociative + ? operand.FoldR(op, reduce) + : operand.Fold(op, reduce); + + var context = new ParseContext("!tail"); + context.Advance(1); + + var success = parser.TryParse(ref context, out var value); + + Assert.That(success, Is.True); + Assert.That(value, Is.EqualTo(2)); + Assert.That(reductions, Is.Zero); + + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); + } + + [Test] + public void FoldVoid_NoInputConsumed_StopsWithoutReducing([Values] bool rightAssociative) + { + var operand = new BoundedParser(Return(2)); + var op = Return('-'); + + var reductions = 0; + var reduce = (int l, int r, char _) => + { + reductions++; + return l - r; + }; + + var parser = rightAssociative + ? operand.FoldR(op, reduce) + : operand.Fold(op, reduce); + + var context = new ParseContext("!tail"); + context.Advance(1); + + var success = parser.Void().TryParse(ref context, out _); + + Assert.That(success, Is.True); + Assert.That(reductions, Is.Zero); + + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); + } + + [TestCase(false, 6)] + [TestCase(true, 8)] + public void Fold_TrailingEmptyPair_StopsWithoutReducing(bool rightAssociative, int expected) + { + var operand = new BoundedParser( + Literal.Number().DefaultOnFail(2) + ); + + var op = L('-').DefaultOnFail('-'); + + var reductions = 0; + var reduce = (int l, int r, char _) => + { + reductions++; + return l - r; + }; + + var parser = rightAssociative + ? operand.FoldR(op, reduce) + : operand.Fold(op, reduce); + + var result = parser.Parse("10-3-1!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Value, Is.EqualTo(expected)); + Assert.That(result.Length, Is.EqualTo(6)); + Assert.That(reductions, Is.EqualTo(2)); + } + + [Test] + public void FoldVoid_TrailingEmptyPair_StopsWithoutReducing([Values] bool rightAssociative) + { + var operand = new BoundedParser( + Literal.Number().DefaultOnFail(2) + ); + + var op = L('-').DefaultOnFail('-'); + + var reductions = 0; + var reduce = (int l, int r, char _) => + { + reductions++; + return l - r; + }; + + var parser = rightAssociative + ? operand.FoldR(op, reduce) + : operand.Fold(op, reduce); + + var result = parser.Void().Parse("10-3-1!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Length, Is.EqualTo(6)); + Assert.That(reductions, Is.Zero); + } + + [TestCase(false, -4)] + [TestCase(true, 2)] + public void Fold_OnlyOperandConsumesInput_ContinuesParsing(bool rightAssociative, int expected) + { + var operand = Set('0', '9').Do(c => c - '0'); + var op = Return('-'); + + var parser = rightAssociative + ? operand.FoldR(op, (l, r, _) => l - r) + : operand.Fold(op, (l, r, _) => l - r); + + var result = parser.Parse("123!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Value, Is.EqualTo(expected)); + Assert.That(result.Length, Is.EqualTo(3)); + } + + [Test] + public void FoldVoid_OnlyOperandConsumesInput_ContinuesParsing([Values] bool rightAssociative) + { + var operand = Set('0', '9').Do(c => c - '0'); + var op = Return('-'); + + var parser = rightAssociative + ? operand.FoldR(op, (l, r, _) => l - r) + : operand.Fold(op, (l, r, _) => l - r); + + var result = parser.Void().Parse("123!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Length, Is.EqualTo(3)); + } + + [TestCase(false, -2)] + [TestCase(true, 2)] + public void Fold_OnlyOperatorConsumesInput_ContinuesParsing(bool rightAssociative, int expected) + { + var operand = Return(2); + var op = L('-'); + + var parser = rightAssociative + ? operand.FoldR(op, (l, r, _) => l - r) + : operand.Fold(op, (l, r, _) => l - r); + + var result = parser.Parse("--!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Value, Is.EqualTo(expected)); + Assert.That(result.Length, Is.EqualTo(2)); + } + + [Test] + public void FoldVoid_OnlyOperatorConsumesInput_ContinuesParsing([Values] bool rightAssociative) + { + var operand = Return(2); + var op = L('-'); + + var parser = rightAssociative + ? operand.FoldR(op, (l, r, _) => l - r) + : operand.Fold(op, (l, r, _) => l - r); + + var result = parser.Void().Parse("--!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Length, Is.EqualTo(2)); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Not.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Not.cs index 9dbc0f9..cad8ae7 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Not.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Not.cs @@ -1,18 +1,18 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void NotTest() - { - var parser = Not(L('a')); - - Assert.That(parser.Parse("b").Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("b").Value, Is.EqualTo((0, 0))); - - Assert.That(parser.Parse("").Success, Is.True); - Assert.That(parser.Parse("a").Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void NotTest() + { + var parser = Not(L('a')); + + Assert.That(parser.Parse("b").Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("b").Value, Is.EqualTo((0, 0))); + + Assert.That(parser.Parse("").Success, Is.True); + Assert.That(parser.Parse("a").Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Optional.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Optional.cs index f606abf..e449429 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Optional.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Optional.cs @@ -1,21 +1,21 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void OptionalTest() - { - var parser = L('a').Optional(); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("b").Success, Is.True); - Assert.That(parser.Parse("a").Value.HasValue, Is.True); - Assert.That(parser.Parse("a").Value.Value, Is.EqualTo('a')); - Assert.That(parser.Parse("b").Value.HasValue, Is.False); - Assert.That(parser.Parse("b").Value.Value, Is.EqualTo('\0')); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("abc").Value, Is.EqualTo((0, 1))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("bac").Value, Is.EqualTo((0, 0))); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void OptionalTest() + { + var parser = L('a').Optional(); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("b").Success, Is.True); + Assert.That(parser.Parse("a").Value.HasValue, Is.True); + Assert.That(parser.Parse("a").Value.Value, Is.EqualTo('a')); + Assert.That(parser.Parse("b").Value.HasValue, Is.False); + Assert.That(parser.Parse("b").Value.Value, Is.EqualTo('\0')); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("abc").Value, Is.EqualTo((0, 1))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("bac").Value, Is.EqualTo((0, 0))); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Regex.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Regex.cs index 661de11..ce34873 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Regex.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Regex.cs @@ -1,38 +1,38 @@ -#if NET7_0_OR_GREATER - -using System.Text.RegularExpressions; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void RegexTest() - { - var parser = L('-').Then(Regex(new Regex(@"^\d+"))); - - Assert.That(parser.Parse("-12345abc").Success, Is.True); - Assert.That(parser.Parse("-a12345abc").Success, Is.False); - Assert.That(parser.Parse("-12345abc").Value, Is.EqualTo("12345")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("-12345abc").Value, Is.EqualTo((1, 5))); - } - - [Test] - public void Regex_Failed_IfNotStartsWithAnchor() - { - var list = new[] { new Regex(@"^\d+") }; - foreach (var expr in list) - { - var parser = L('-').Then(Regex(expr)); - - Assert.That(parser.Parse("-12345abc").Success, Is.True); - Assert.That(parser.Parse("-a12345abc").Success, Is.False); - Assert.That(parser.Parse("-12345abc").Value, Is.EqualTo("12345")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("-12345abc").Value, Is.EqualTo((1, 5))); - } - } -} - -#endif +#if NET7_0_OR_GREATER + +using System.Text.RegularExpressions; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void RegexTest() + { + var parser = L('-').Then(Regex(new Regex(@"^\d+"))); + + Assert.That(parser.Parse("-12345abc").Success, Is.True); + Assert.That(parser.Parse("-a12345abc").Success, Is.False); + Assert.That(parser.Parse("-12345abc").Value, Is.EqualTo("12345")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("-12345abc").Value, Is.EqualTo((1, 5))); + } + + [Test] + public void Regex_Failed_IfNotStartsWithAnchor() + { + var list = new[] { new Regex(@"^\d+") }; + foreach (var expr in list) + { + var parser = L('-').Then(Regex(expr)); + + Assert.That(parser.Parse("-12345abc").Success, Is.True); + Assert.That(parser.Parse("-a12345abc").Success, Is.False); + Assert.That(parser.Parse("-12345abc").Value, Is.EqualTo("12345")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("-12345abc").Value, Is.EqualTo((1, 5))); + } + } +} + +#endif diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs index 7a5dc0e..5915027 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat.cs @@ -1,80 +1,80 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void Repeat_0_1_Test() - { - var parser = L('a').Repeat(0, 1); - - Assert.That(parser.Parse("").Success, Is.True); - Assert.That(parser.Parse("").Value, Is.Empty); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("").Value, Is.EqualTo((0, 0))); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EquivalentTo("a")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); - } - - [Test] - public void Repeat_1_1_Test() - { - var parser = L('a').Repeat(1, 1); - - Assert.That(parser.Parse("a").Success, Is.True); - Assert.That(parser.Parse("a").Value, Is.EquivalentTo("a")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("").Success, Is.False); - } - - [Test] - public void Repeat_5_10_Test() - { - var parser = L('a').Repeat(5, 10); - - Assert.That(parser.Parse("aaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaaaaaa").Success, Is.True); - Assert.That(parser.Parse("aaaaaaaaaaa").Success, Is.True); - - Assert.That(parser.Parse("aaaaa").Value, Is.EquivalentTo("aaaaa")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("aaaaa").Value, Is.EqualTo((0, 5))); - - Assert.That(parser.Parse("aaaaaaaaaaaaaaaaaaaa").Value, Is.EquivalentTo("aaaaaaaaaa")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("aaaaaaaaaaaaaaa").Value, Is.EqualTo((0, 10))); - - Assert.That(parser.Parse("aaaa").Success, Is.False); - } - - [Test] - [SuppressMessage("ReSharper", "InconsistentNaming")] - public void Repeat_InfiniteLoopPrevention_ZeroLength() - { - var digit = Character.Digit; - var digit_list = digit.Separated(L(',')); - var index_list = digit_list.Between(L('['), L(']')).Many(); - - Assert.That(index_list.Parse("[]").Length, Is.EqualTo(2)); - Assert.That(index_list.Parse("[][]").Length, Is.EqualTo(4)); - Assert.That(index_list.Parse("[][][]").Length, Is.EqualTo(6)); - - Assert.That(index_list.Parse("[1,2][1,2][2,6]").Length, Is.EqualTo(15)); - Assert.That(index_list.Parse("[][][1,2][][][1,2][][][2,6][][]").Length, Is.EqualTo(31)); - } - - [Test] - public void Repeat_InfiniteLoopPrevention_ZeroConsuming() - { - var parser = And(Character.Digit).AtLeast(2); - - Assert.That(parser.Parse("1234567890").Success, Is.True); - Assert.That(parser.Parse("1234567890").Length, Is.Zero); - Assert.That(parser.Text().Parse("1234567890").Value, Is.Empty); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void Repeat_0_1_Test() + { + var parser = L('a').Repeat(0, 1); + + Assert.That(parser.Parse("").Success, Is.True); + Assert.That(parser.Parse("").Value, Is.Empty); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("").Value, Is.EqualTo((0, 0))); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EquivalentTo("a")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); + } + + [Test] + public void Repeat_1_1_Test() + { + var parser = L('a').Repeat(1, 1); + + Assert.That(parser.Parse("a").Success, Is.True); + Assert.That(parser.Parse("a").Value, Is.EquivalentTo("a")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("a").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("").Success, Is.False); + } + + [Test] + public void Repeat_5_10_Test() + { + var parser = L('a').Repeat(5, 10); + + Assert.That(parser.Parse("aaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaaaaaa").Success, Is.True); + Assert.That(parser.Parse("aaaaaaaaaaa").Success, Is.True); + + Assert.That(parser.Parse("aaaaa").Value, Is.EquivalentTo("aaaaa")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("aaaaa").Value, Is.EqualTo((0, 5))); + + Assert.That(parser.Parse("aaaaaaaaaaaaaaaaaaaa").Value, Is.EquivalentTo("aaaaaaaaaa")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("aaaaaaaaaaaaaaa").Value, Is.EqualTo((0, 10))); + + Assert.That(parser.Parse("aaaa").Success, Is.False); + } + + [Test] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public void Repeat_InfiniteLoopPrevention_ZeroLength() + { + var digit = Character.Digit; + var digit_list = digit.Separated(L(',')); + var index_list = digit_list.Between(L('['), L(']')).Many(); + + Assert.That(index_list.Parse("[]").Length, Is.EqualTo(2)); + Assert.That(index_list.Parse("[][]").Length, Is.EqualTo(4)); + Assert.That(index_list.Parse("[][][]").Length, Is.EqualTo(6)); + + Assert.That(index_list.Parse("[1,2][1,2][2,6]").Length, Is.EqualTo(15)); + Assert.That(index_list.Parse("[][][1,2][][][1,2][][][2,6][][]").Length, Is.EqualTo(31)); + } + + [Test] + public void Repeat_InfiniteLoopPrevention_ZeroConsuming() + { + var parser = And(Character.Digit).AtLeast(2); + + Assert.That(parser.Parse("1234567890").Success, Is.True); + Assert.That(parser.Parse("1234567890").Length, Is.Zero); + Assert.That(parser.Text().Parse("1234567890").Value, Is.Empty); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat_Opt.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat_Opt.cs index cfc9c6b..a4d9638 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat_Opt.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Repeat_Opt.cs @@ -1,227 +1,227 @@ -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using System.Text; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void Repeat_SingleRange() - { - var parser = Set(@"\u3040-\u309F").Many(); - - #if NET8_0_OR_GREATER - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); - #else - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - #endif - - var text = parser.Text(); - - Assert.That(text.Parse("\u3040\u3041\u3042\u3043").Value, Is.EqualTo("\u3040\u3041\u3042\u3043")); - Assert.That(text.Parse("\u30FF\u3041\u3042\u3043").Value, Is.Empty); - Assert.That(text.Parse("\u3040\u3041\u3042\u30FF").Value, Is.EqualTo("\u3040\u3041\u3042")); - } - - [Test] - public void Repeat_Ascii() - { - var parser = Set("0-9A-Za-z").Many(); - var p = parser.Text(); - - #if NET8_0_OR_GREATER - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); - #else - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - #endif - - Assert.That(p.Parse("1234567890").Value, Is.EqualTo("1234567890")); - Assert.That(p.Parse("!123456789").Value, Is.Empty); - Assert.That(p.Parse("123456789!").Value, Is.EqualTo("123456789")); - } - - [TestCase("1")] - [TestCase("12")] - [TestCase("123")] - [TestCase("1234")] - [TestCase("12345")] - public void Repeat_SymbolCount(string chars) - { - var parser = OneOf(chars).Many(); - var p = parser.Text(); - - #if NET8_0_OR_GREATER - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); - #else - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - #endif - - Assert.That(p.Parse(chars).Value, Is.EqualTo(chars)); - Assert.That(p.Parse("!" + chars).Value, Is.Empty); - Assert.That(p.Parse(chars + "!").Value, Is.EqualTo(chars)); - } - - [Test] - public void Repeat_BitVectorSearcher() - { - var parser = Set("а-жА-Ж").Many(); - var p = parser.Text(); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - - Assert.That(p.Parse("абвгд").Value, Is.EqualTo("абвгд")); - Assert.That(p.Parse("!абвгд").Value, Is.Empty); - Assert.That(p.Parse("абвгд!").Value, Is.EqualTo("абвгд")); - } - - [Test] - public void Repeat_BitVectorSearcher_UnicodeCategories() - { - var parser = Choice( - Set("a-z0-9"), - Set(@"\p{L}\p{Nd}") - ).Many(); - var p = parser.Text(); - - #if NET8_0_OR_GREATER - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); - #else - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - #endif - - Assert.That(p.Parse("абвгд").Value, Is.EqualTo("абвгд")); - Assert.That(p.Parse("aаbбcвdгeдf").Value, Is.EqualTo("aаbбcвdгeдf")); - Assert.That(p.Parse("!абвгд").Value, Is.Empty); - Assert.That(p.Parse("абвгд0123456789абвгд!").Value, Is.EqualTo("абвгд0123456789абвгд")); - } - - [Test] - public void Repeat_Simd256Searcher() - { - if (!Avx2.IsSupported) - return; - - var set = new StringBuilder(); - var chars = new StringBuilder(); - - set.Append('\0'); - - for (var c = (char)1000; c < 1050; c = (char)(c + 5)) - { - set.Append($"\\u{c+0:x4}"); - set.Append($"\\u{c+1:x4}"); - set.Append($"\\u{c+2:x4}"); - - chars.Append(c); - chars.Append((char)(c + 1)); - chars.Append((char)(c + 2)); - } - - var parser = Set(set.ToString()).Many(); - var p = parser.Text(); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); - - var s = chars.ToString(); - var r = string.Join("", s.Reverse()); - - Assert.That(p.Parse(s).Value, Is.EqualTo(s)); - Assert.That(p.Parse(r).Value, Is.EqualTo(r)); - Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); - Assert.That(p.Parse("!" + s).Value, Is.Empty); - } - - [Test] - public void Repeat_Simd128Searcher() - { - if (!Sse41.IsSupported && !AdvSimd.IsSupported) - return; - - var set = new StringBuilder(); - var chars = new StringBuilder(); - - set.Append('\0'); - - for (var c = (char)1000; c < 1030; c = (char)(c + 5)) - { - set.Append($"\\u{c+0:x4}"); - set.Append($"\\u{c+1:x4}"); - set.Append($"\\u{c+2:x4}"); - - chars.Append(c); - chars.Append((char)(c + 1)); - chars.Append((char)(c + 2)); - } - - var parser = Set(set.ToString()).Many(); - var p = parser.Text(); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - - var s = chars.ToString(); - var r = string.Join("", s.Reverse()); - - Assert.That(p.Parse(s).Value, Is.EqualTo(s)); - Assert.That(p.Parse(r).Value, Is.EqualTo(r)); - Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); - Assert.That(p.Parse("!" + s).Value, Is.Empty); - } - - [Test] - public void Repeat_BinarySearcher() - { - var set = new StringBuilder(); - var chars = new StringBuilder(); - - for (var c = '\0'; c < 10240; c = (char)(c + 2)) - { - set.Append($"\\u{(int)c:x4}"); - chars.Append(c); - } - - var parser = Set(set.ToString()).Many(); - var p = parser.Text(); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BinaryRangeSearcher]")); - - var s = chars.ToString(); - var r = string.Join("", s.Reverse()); - - Assert.That(p.Parse(s).Value, Is.EqualTo(s)); - Assert.That(p.Parse(r).Value, Is.EqualTo(r)); - Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); - Assert.That(p.Parse("!" + s).Value, Is.Empty); - } - - [Test] - public void Repeat_ContainsSearcher() - { - var set = new StringBuilder(); - var chars = new StringBuilder(); - - for (var c = '\0'; c < 128*5; c = (char)(c + 5)) - { - set.Append($"\\u{(int)c:x4}"); - chars.Append(c); - } - - var parser = Set(set.ToString()).Many(); - var p = parser.Text(); - - Assert.That(parser.GetType().ToString(), - Avx2.IsSupported - ? Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+ContainsSearcher]") - : Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BinaryRangeSearcher]")); - - var s = chars.ToString(); - var r = string.Join("", s.Reverse()); - - Assert.That(p.Parse(s).Value, Is.EqualTo(s)); - Assert.That(p.Parse(r).Value, Is.EqualTo(r)); - Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); - Assert.That(p.Parse("!" + s).Value, Is.Empty); - } -} +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using System.Text; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void Repeat_SingleRange() + { + var parser = Set(@"\u3040-\u309F").Many(); + + #if NET8_0_OR_GREATER + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); + #else + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + #endif + + var text = parser.Text(); + + Assert.That(text.Parse("\u3040\u3041\u3042\u3043").Value, Is.EqualTo("\u3040\u3041\u3042\u3043")); + Assert.That(text.Parse("\u30FF\u3041\u3042\u3043").Value, Is.Empty); + Assert.That(text.Parse("\u3040\u3041\u3042\u30FF").Value, Is.EqualTo("\u3040\u3041\u3042")); + } + + [Test] + public void Repeat_Ascii() + { + var parser = Set("0-9A-Za-z").Many(); + var p = parser.Text(); + + #if NET8_0_OR_GREATER + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); + #else + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + #endif + + Assert.That(p.Parse("1234567890").Value, Is.EqualTo("1234567890")); + Assert.That(p.Parse("!123456789").Value, Is.Empty); + Assert.That(p.Parse("123456789!").Value, Is.EqualTo("123456789")); + } + + [TestCase("1")] + [TestCase("12")] + [TestCase("123")] + [TestCase("1234")] + [TestCase("12345")] + public void Repeat_SymbolCount(string chars) + { + var parser = OneOf(chars).Many(); + var p = parser.Text(); + + #if NET8_0_OR_GREATER + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); + #else + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + #endif + + Assert.That(p.Parse(chars).Value, Is.EqualTo(chars)); + Assert.That(p.Parse("!" + chars).Value, Is.Empty); + Assert.That(p.Parse(chars + "!").Value, Is.EqualTo(chars)); + } + + [Test] + public void Repeat_BitVectorSearcher() + { + var parser = Set("а-жА-Ж").Many(); + var p = parser.Text(); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + + Assert.That(p.Parse("абвгд").Value, Is.EqualTo("абвгд")); + Assert.That(p.Parse("!абвгд").Value, Is.Empty); + Assert.That(p.Parse("абвгд!").Value, Is.EqualTo("абвгд")); + } + + [Test] + public void Repeat_BitVectorSearcher_UnicodeCategories() + { + var parser = Choice( + Set("a-z0-9"), + Set(@"\p{L}\p{Nd}") + ).Many(); + var p = parser.Text(); + + #if NET8_0_OR_GREATER + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SearchValuesSearcher]")); + #else + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + #endif + + Assert.That(p.Parse("абвгд").Value, Is.EqualTo("абвгд")); + Assert.That(p.Parse("aаbбcвdгeдf").Value, Is.EqualTo("aаbбcвdгeдf")); + Assert.That(p.Parse("!абвгд").Value, Is.Empty); + Assert.That(p.Parse("абвгд0123456789абвгд!").Value, Is.EqualTo("абвгд0123456789абвгд")); + } + + [Test] + public void Repeat_Simd256Searcher() + { + if (!Avx2.IsSupported) + return; + + var set = new StringBuilder(); + var chars = new StringBuilder(); + + set.Append('\0'); + + for (var c = (char)1000; c < 1050; c = (char)(c + 5)) + { + set.Append($"\\u{c+0:x4}"); + set.Append($"\\u{c+1:x4}"); + set.Append($"\\u{c+2:x4}"); + + chars.Append(c); + chars.Append((char)(c + 1)); + chars.Append((char)(c + 2)); + } + + var parser = Set(set.ToString()).Many(); + var p = parser.Text(); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); + + var s = chars.ToString(); + var r = string.Join("", s.Reverse()); + + Assert.That(p.Parse(s).Value, Is.EqualTo(s)); + Assert.That(p.Parse(r).Value, Is.EqualTo(r)); + Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); + Assert.That(p.Parse("!" + s).Value, Is.Empty); + } + + [Test] + public void Repeat_Simd128Searcher() + { + if (!Sse41.IsSupported && !AdvSimd.IsSupported) + return; + + var set = new StringBuilder(); + var chars = new StringBuilder(); + + set.Append('\0'); + + for (var c = (char)1000; c < 1030; c = (char)(c + 5)) + { + set.Append($"\\u{c+0:x4}"); + set.Append($"\\u{c+1:x4}"); + set.Append($"\\u{c+2:x4}"); + + chars.Append(c); + chars.Append((char)(c + 1)); + chars.Append((char)(c + 2)); + } + + var parser = Set(set.ToString()).Many(); + var p = parser.Text(); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + + var s = chars.ToString(); + var r = string.Join("", s.Reverse()); + + Assert.That(p.Parse(s).Value, Is.EqualTo(s)); + Assert.That(p.Parse(r).Value, Is.EqualTo(r)); + Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); + Assert.That(p.Parse("!" + s).Value, Is.Empty); + } + + [Test] + public void Repeat_BinarySearcher() + { + var set = new StringBuilder(); + var chars = new StringBuilder(); + + for (var c = '\0'; c < 10240; c = (char)(c + 2)) + { + set.Append($"\\u{(int)c:x4}"); + chars.Append(c); + } + + var parser = Set(set.ToString()).Many(); + var p = parser.Text(); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BinaryRangeSearcher]")); + + var s = chars.ToString(); + var r = string.Join("", s.Reverse()); + + Assert.That(p.Parse(s).Value, Is.EqualTo(s)); + Assert.That(p.Parse(r).Value, Is.EqualTo(r)); + Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); + Assert.That(p.Parse("!" + s).Value, Is.Empty); + } + + [Test] + public void Repeat_ContainsSearcher() + { + var set = new StringBuilder(); + var chars = new StringBuilder(); + + for (var c = '\0'; c < 128*5; c = (char)(c + 5)) + { + set.Append($"\\u{(int)c:x4}"); + chars.Append(c); + } + + var parser = Set(set.ToString()).Many(); + var p = parser.Text(); + + Assert.That(parser.GetType().ToString(), + Avx2.IsSupported + ? Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+ContainsSearcher]") + : Is.EqualTo("Ramstack.Parsing.Parser+RepeatCharClassParser`1[Ramstack.Parsing.Parser+BinaryRangeSearcher]")); + + var s = chars.ToString(); + var r = string.Join("", s.Reverse()); + + Assert.That(p.Parse(s).Value, Is.EqualTo(s)); + Assert.That(p.Parse(r).Value, Is.EqualTo(r)); + Assert.That(p.Parse(s + "!").Value, Is.EqualTo(s)); + Assert.That(p.Parse("!" + s).Value, Is.Empty); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.S.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.S.cs index 69cb5cf..78c9ea5 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.S.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.S.cs @@ -1,22 +1,22 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [TestCase("", 0, 0, "")] - [TestCase("1", 0, 0, "")] - [TestCase(" ", 0, 1, " ")] - [TestCase("\t", 0, 1, "\t")] - [TestCase("\r", 0, 1, "\r")] - [TestCase("\n", 0, 1, "\n")] - [TestCase("\r\n", 0, 2, "\r\n")] - [TestCase(" ", 0, 4, " ")] - [TestCase(" \r\n\t", 0, 4, " \r\n\t")] - public void STest(string input, int index, int length, string value) - { - Assert.That(S.Parse(input).Success, Is.True); - Assert.That(S.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((index, length))); - Assert.That(S.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(value)); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [TestCase("", 0, 0, "")] + [TestCase("1", 0, 0, "")] + [TestCase(" ", 0, 1, " ")] + [TestCase("\t", 0, 1, "\t")] + [TestCase("\r", 0, 1, "\r")] + [TestCase("\n", 0, 1, "\n")] + [TestCase("\r\n", 0, 2, "\r\n")] + [TestCase(" ", 0, 4, " ")] + [TestCase(" \r\n\t", 0, 4, " \r\n\t")] + public void STest(string input, int index, int length, string value) + { + Assert.That(S.Parse(input).Success, Is.True); + Assert.That(S.Map(m => (m.Index, m.Length)).Parse(input).Value, Is.EqualTo((index, length))); + Assert.That(S.Map(m => m.ToString()).Parse(input).Value, Is.EqualTo(value)); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Separated.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Separated.cs index 928f399..8f1e2ae 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Separated.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Separated.cs @@ -1,336 +1,336 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void Separated__0_1_DisallowTrailing() - { - var parser = Set("0-9").Separated(L(','), max: 1); - - Assert.That(parser.Parse("1").Success, Is.True); - Assert.That(parser.Parse("1").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("1,").Success, Is.True); - Assert.That(parser.Parse("1,").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("1,2").Success, Is.True); - Assert.That(parser.Parse("1,2").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,2").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("12").Success, Is.True); - Assert.That(parser.Parse("12").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 1))); - - foreach (var s in new[] { "", "a", ",", ",1" }) - { - Assert.That(parser.Parse(s).Success, Is.True); - Assert.That(parser.Parse(s).Value, Is.Empty); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 0))); - } - } - - [Test] - public void Separated__0_1_AllowTrailing() - { - var parser = Set("0-9").Separated(L(','), max: 1, allowTrailing: true); - - Assert.That(parser.Parse("1").Success, Is.True); - Assert.That(parser.Parse("1").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - - Assert.That(parser.Parse("1,").Success, Is.True); - Assert.That(parser.Parse("1,").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,").Value, Is.EqualTo((0, 2))); - - Assert.That(parser.Parse("1,2").Success, Is.True); - Assert.That(parser.Parse("1,2").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,2").Value, Is.EqualTo((0, 2))); - - Assert.That(parser.Parse("12").Success, Is.True); - Assert.That(parser.Parse("12").Value, Is.EquivalentTo("1")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); - - foreach (var s in new[] { "", "a", ",", ",1" }) - { - Assert.That(parser.Parse(s).Success, Is.True); - Assert.That(parser.Parse(s).Value, Is.Empty); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 0))); - } - } - - [Test] - public void Separated_DisallowTrailing() - { - var parser = Choice( - Set("0-9"), - Set("a-zA-Z") - ).OneOrMore() - .Separated( - Seq(L(','), S)); - - foreach (var s in new[] { "123", "123,", "123,abc", "123,abc,", "123, abc", "123, abc, "}) - { - Assert.That( - parser.Parse(s).Success, - Is.True); - - Assert.That( - parser.Map(m => (m.Index, m.Length)).Parse(s).Value, - Is.EqualTo((0, s.TrimEnd(' ', ',').Length))); - } - } - - [Test] - public void Separated_AllowTrailing() - { - var parser = Choice( - Set("0-9"), - Set("a-zA-Z") - ).OneOrMore() - .Separated( - Seq(L(','), S), - allowTrailing: true); - - foreach (var s in new[] { "123", "123,", "123,abc", "123,abc,", "123, abc", "123, abc, "}) - { - Assert.That(parser.Parse(s).Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, s.Length))); - } - } - - [TestCase(0, 1, false)] - [TestCase(0, 1, true)] - [TestCase(1, 1, false)] - [TestCase(1, 1, true)] - [TestCase(3, 3, false)] - [TestCase(3, 3, true)] - public void Separated_NoInputConsumed_StopsAfterMinimumReached(int min, int expectedCount, bool allowTrailing) - { - var item = new BoundedParser(Return(7)); - var parser = item.Separated(Return(','), allowTrailing, min); - var context = new ParseContext("!tail"); - context.Advance(1); - - var success = parser.TryParse(ref context, out var value); - - Assert.That(success, Is.True); - Assert.That(value, Is.EqualTo(Enumerable.Repeat(7, expectedCount))); - Assert.That(item.Attempts, Is.EqualTo(expectedCount)); - - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); - } - - [TestCase(0, 1, false)] - [TestCase(0, 1, true)] - [TestCase(1, 1, false)] - [TestCase(1, 1, true)] - [TestCase(3, 3, false)] - [TestCase(3, 3, true)] - public void SeparatedVoid_NoInputConsumed_StopsAfterMinimumReached(int min, int expectedCount, bool allowTrailing) - { - var item = new BoundedParser(Return(7)); - var parser = item.Separated(Return(','), allowTrailing, min).Void(); - var context = new ParseContext("!tail"); - context.Advance(1); - - var success = parser.TryParse(ref context, out _); - - Assert.That(success, Is.True); - Assert.That(item.Attempts, Is.EqualTo(expectedCount)); - - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); - } - - [Test] - public void Separated_TrailingEmptyPair_StopsAfterEmptyItem([Values] bool allowTrailing) - { - var item = new BoundedParser( - Literal.Number().DefaultOnFail(7) - ); - - var parser = item.Separated(Return(','), allowTrailing); - var context = new ParseContext("!12?"); - context.Advance(1); - - var success = parser.TryParse(ref context, out var value); - - Assert.That(success, Is.True); - Assert.That(value, Is.EqualTo(new[] { 12, 7 })); - Assert.That(item.Attempts, Is.EqualTo(2)); - - Assert.That(context.Position, Is.EqualTo(3)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.EqualTo(2)); - Assert.That(context.Remaining.ToString(), Is.EqualTo("?")); - } - - [Test] - public void SeparatedVoid_TrailingEmptyPair_StopsAfterEmptyItem([Values] bool allowTrailing) - { - var item = new BoundedParser( - Literal.Number().DefaultOnFail(7) - ); - - var parser = item.Separated(Return(','), allowTrailing).Void(); - var context = new ParseContext("!12?"); - context.Advance(1); - - var success = parser.TryParse(ref context, out _); - - Assert.That(success, Is.True); - Assert.That(item.Attempts, Is.EqualTo(2)); - - Assert.That(context.Position, Is.EqualTo(3)); - Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.EqualTo(2)); - Assert.That(context.Remaining.ToString(), Is.EqualTo("?")); - } - - [Test] - public void Separated_OnlyItemConsumesInput_ContinuesParsing([Values] bool allowTrailing) - { - var item = Set('a', 'z'); - var separator = Return(','); - var parser = item.Separated(separator, allowTrailing); - - var result = parser.Parse("abc!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Value, Is.EqualTo(new[] { 'a', 'b', 'c' })); - Assert.That(result.Length, Is.EqualTo(3)); - } - - [Test] - public void SeparatedVoid_OnlyItemConsumesInput_ContinuesParsing([Values] bool allowTrailing) - { - var item = Set('a', 'z'); - var separator = Return(','); - var parser = item.Separated(separator, allowTrailing).Void(); - - var result = parser.Parse("abc!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Length, Is.EqualTo(3)); - } - - [Test] - public void Separated_OnlySeparatorConsumesInput_ContinuesParsing([Values] bool allowTrailing) - { - var item = Return('x'); - var separator = L(','); - var parser = item.Separated(separator, allowTrailing); - - var result = parser.Parse(",,!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Value, Is.EqualTo(new[] { 'x', 'x', 'x' })); - Assert.That(result.Length, Is.EqualTo(2)); - } - - [Test] - public void SeparatedVoid_OnlySeparatorConsumesInput_ContinuesParsing([Values] bool allowTrailing) - { - var item = Return('x'); - var separator = L(','); - var parser = item.Separated(separator, allowTrailing).Void(); - - var result = parser.Parse(",,!"); - - Assert.That(result.Success, Is.True); - Assert.That(result.Length, Is.EqualTo(2)); - } - - [TestCase(false, 1)] - [TestCase(true, 2)] - public void Separated_MaximumReached_ConsumesTrailingSeparatorOnlyWhenAllowed(bool allowTrailing, int expectedLength) - { - var parser = L('a').Separated(L(','), allowTrailing, min: 1, max: 1); - var context = new ParseContext("a,a"); - - var success = parser.TryParse(ref context, out var value); - - Assert.That(success, Is.True); - Assert.That(value, Is.EqualTo(new[] { 'a' })); - Assert.That(context.Position, Is.EqualTo(expectedLength)); - Assert.That(context.MatchedSegment.Index, Is.Zero); - Assert.That(context.MatchedSegment.Length, Is.EqualTo(expectedLength)); - } - - [TestCase(false, 1)] - [TestCase(true, 2)] - public void SeparatedVoid_MaximumReached_ConsumesTrailingSeparatorOnlyWhenAllowed(bool allowTrailing, int expectedLength) - { - var parser = L('a').Separated(L(','), allowTrailing, min: 1, max: 1).Void(); - var context = new ParseContext("a,a"); - - var success = parser.TryParse(ref context, out _); - - Assert.That(success, Is.True); - Assert.That(context.Position, Is.EqualTo(expectedLength)); - Assert.That(context.MatchedSegment.Index, Is.Zero); - Assert.That(context.MatchedSegment.Length, Is.EqualTo(expectedLength)); - } - - [Test] - public void Separated_MinimumNotReached_RestoresPosition([Values] bool allowTrailing) - { - var parser = L('a').Separated(L(','), allowTrailing, min: 2); - var context = new ParseContext("!a,?"); - context.Advance(1); - - var success = parser.TryParse(ref context, out var value); - - Assert.That(success, Is.False); - Assert.That(value, Is.Null); - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("a,?")); - Assert.That(context.ToString(), Is.EqualTo("(1:4) Expected 'a'")); - } - - [Test] - public void SeparatedVoid_MinimumNotReached_RestoresPosition([Values] bool allowTrailing) - { - var parser = L('a').Separated(L(','), allowTrailing, min: 2).Void(); - var context = new ParseContext("!a,?"); - context.Advance(1); - - var success = parser.TryParse(ref context, out _); - - Assert.That(success, Is.False); - Assert.That(context.Position, Is.EqualTo(1)); - Assert.That(context.MatchedSegment.Length, Is.Zero); - Assert.That(context.Remaining.ToString(), Is.EqualTo("a,?")); - Assert.That(context.ToString(), Is.EqualTo("(1:4) Expected 'a'")); - } - - [Test] - public void Separated_InvalidBounds_ThrowsArgumentOutOfRangeException() - { - Assert.That( - () => L('a').Separated(L(','), min: -1, max: 1), - Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("min")); - - Assert.That( - () => L('a').Separated(L(','), min: 0, max: -1), - Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("max")); - - Assert.That( - () => L('a').Separated(L(','), min: 0, max: 0), - Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("max")); - - Assert.That( - () => L('a').Separated(L(','), min: 2, max: 1), - Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("min")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void Separated__0_1_DisallowTrailing() + { + var parser = Set("0-9").Separated(L(','), max: 1); + + Assert.That(parser.Parse("1").Success, Is.True); + Assert.That(parser.Parse("1").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("1,").Success, Is.True); + Assert.That(parser.Parse("1,").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("1,2").Success, Is.True); + Assert.That(parser.Parse("1,2").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,2").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("12").Success, Is.True); + Assert.That(parser.Parse("12").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 1))); + + foreach (var s in new[] { "", "a", ",", ",1" }) + { + Assert.That(parser.Parse(s).Success, Is.True); + Assert.That(parser.Parse(s).Value, Is.Empty); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 0))); + } + } + + [Test] + public void Separated__0_1_AllowTrailing() + { + var parser = Set("0-9").Separated(L(','), max: 1, allowTrailing: true); + + Assert.That(parser.Parse("1").Success, Is.True); + Assert.That(parser.Parse("1").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + + Assert.That(parser.Parse("1,").Success, Is.True); + Assert.That(parser.Parse("1,").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,").Value, Is.EqualTo((0, 2))); + + Assert.That(parser.Parse("1,2").Success, Is.True); + Assert.That(parser.Parse("1,2").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1,2").Value, Is.EqualTo((0, 2))); + + Assert.That(parser.Parse("12").Success, Is.True); + Assert.That(parser.Parse("12").Value, Is.EquivalentTo("1")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1").Value, Is.EqualTo((0, 1))); + + foreach (var s in new[] { "", "a", ",", ",1" }) + { + Assert.That(parser.Parse(s).Success, Is.True); + Assert.That(parser.Parse(s).Value, Is.Empty); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 0))); + } + } + + [Test] + public void Separated_DisallowTrailing() + { + var parser = Choice( + Set("0-9"), + Set("a-zA-Z") + ).OneOrMore() + .Separated( + Seq(L(','), S)); + + foreach (var s in new[] { "123", "123,", "123,abc", "123,abc,", "123, abc", "123, abc, "}) + { + Assert.That( + parser.Parse(s).Success, + Is.True); + + Assert.That( + parser.Map(m => (m.Index, m.Length)).Parse(s).Value, + Is.EqualTo((0, s.TrimEnd(' ', ',').Length))); + } + } + + [Test] + public void Separated_AllowTrailing() + { + var parser = Choice( + Set("0-9"), + Set("a-zA-Z") + ).OneOrMore() + .Separated( + Seq(L(','), S), + allowTrailing: true); + + foreach (var s in new[] { "123", "123,", "123,abc", "123,abc,", "123, abc", "123, abc, "}) + { + Assert.That(parser.Parse(s).Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, s.Length))); + } + } + + [TestCase(0, 1, false)] + [TestCase(0, 1, true)] + [TestCase(1, 1, false)] + [TestCase(1, 1, true)] + [TestCase(3, 3, false)] + [TestCase(3, 3, true)] + public void Separated_NoInputConsumed_StopsAfterMinimumReached(int min, int expectedCount, bool allowTrailing) + { + var item = new BoundedParser(Return(7)); + var parser = item.Separated(Return(','), allowTrailing, min); + var context = new ParseContext("!tail"); + context.Advance(1); + + var success = parser.TryParse(ref context, out var value); + + Assert.That(success, Is.True); + Assert.That(value, Is.EqualTo(Enumerable.Repeat(7, expectedCount))); + Assert.That(item.Attempts, Is.EqualTo(expectedCount)); + + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); + } + + [TestCase(0, 1, false)] + [TestCase(0, 1, true)] + [TestCase(1, 1, false)] + [TestCase(1, 1, true)] + [TestCase(3, 3, false)] + [TestCase(3, 3, true)] + public void SeparatedVoid_NoInputConsumed_StopsAfterMinimumReached(int min, int expectedCount, bool allowTrailing) + { + var item = new BoundedParser(Return(7)); + var parser = item.Separated(Return(','), allowTrailing, min).Void(); + var context = new ParseContext("!tail"); + context.Advance(1); + + var success = parser.TryParse(ref context, out _); + + Assert.That(success, Is.True); + Assert.That(item.Attempts, Is.EqualTo(expectedCount)); + + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("tail")); + } + + [Test] + public void Separated_TrailingEmptyPair_StopsAfterEmptyItem([Values] bool allowTrailing) + { + var item = new BoundedParser( + Literal.Number().DefaultOnFail(7) + ); + + var parser = item.Separated(Return(','), allowTrailing); + var context = new ParseContext("!12?"); + context.Advance(1); + + var success = parser.TryParse(ref context, out var value); + + Assert.That(success, Is.True); + Assert.That(value, Is.EqualTo(new[] { 12, 7 })); + Assert.That(item.Attempts, Is.EqualTo(2)); + + Assert.That(context.Position, Is.EqualTo(3)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.EqualTo(2)); + Assert.That(context.Remaining.ToString(), Is.EqualTo("?")); + } + + [Test] + public void SeparatedVoid_TrailingEmptyPair_StopsAfterEmptyItem([Values] bool allowTrailing) + { + var item = new BoundedParser( + Literal.Number().DefaultOnFail(7) + ); + + var parser = item.Separated(Return(','), allowTrailing).Void(); + var context = new ParseContext("!12?"); + context.Advance(1); + + var success = parser.TryParse(ref context, out _); + + Assert.That(success, Is.True); + Assert.That(item.Attempts, Is.EqualTo(2)); + + Assert.That(context.Position, Is.EqualTo(3)); + Assert.That(context.MatchedSegment.Index, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.EqualTo(2)); + Assert.That(context.Remaining.ToString(), Is.EqualTo("?")); + } + + [Test] + public void Separated_OnlyItemConsumesInput_ContinuesParsing([Values] bool allowTrailing) + { + var item = Set('a', 'z'); + var separator = Return(','); + var parser = item.Separated(separator, allowTrailing); + + var result = parser.Parse("abc!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Value, Is.EqualTo(new[] { 'a', 'b', 'c' })); + Assert.That(result.Length, Is.EqualTo(3)); + } + + [Test] + public void SeparatedVoid_OnlyItemConsumesInput_ContinuesParsing([Values] bool allowTrailing) + { + var item = Set('a', 'z'); + var separator = Return(','); + var parser = item.Separated(separator, allowTrailing).Void(); + + var result = parser.Parse("abc!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Length, Is.EqualTo(3)); + } + + [Test] + public void Separated_OnlySeparatorConsumesInput_ContinuesParsing([Values] bool allowTrailing) + { + var item = Return('x'); + var separator = L(','); + var parser = item.Separated(separator, allowTrailing); + + var result = parser.Parse(",,!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Value, Is.EqualTo(new[] { 'x', 'x', 'x' })); + Assert.That(result.Length, Is.EqualTo(2)); + } + + [Test] + public void SeparatedVoid_OnlySeparatorConsumesInput_ContinuesParsing([Values] bool allowTrailing) + { + var item = Return('x'); + var separator = L(','); + var parser = item.Separated(separator, allowTrailing).Void(); + + var result = parser.Parse(",,!"); + + Assert.That(result.Success, Is.True); + Assert.That(result.Length, Is.EqualTo(2)); + } + + [TestCase(false, 1)] + [TestCase(true, 2)] + public void Separated_MaximumReached_ConsumesTrailingSeparatorOnlyWhenAllowed(bool allowTrailing, int expectedLength) + { + var parser = L('a').Separated(L(','), allowTrailing, min: 1, max: 1); + var context = new ParseContext("a,a"); + + var success = parser.TryParse(ref context, out var value); + + Assert.That(success, Is.True); + Assert.That(value, Is.EqualTo(new[] { 'a' })); + Assert.That(context.Position, Is.EqualTo(expectedLength)); + Assert.That(context.MatchedSegment.Index, Is.Zero); + Assert.That(context.MatchedSegment.Length, Is.EqualTo(expectedLength)); + } + + [TestCase(false, 1)] + [TestCase(true, 2)] + public void SeparatedVoid_MaximumReached_ConsumesTrailingSeparatorOnlyWhenAllowed(bool allowTrailing, int expectedLength) + { + var parser = L('a').Separated(L(','), allowTrailing, min: 1, max: 1).Void(); + var context = new ParseContext("a,a"); + + var success = parser.TryParse(ref context, out _); + + Assert.That(success, Is.True); + Assert.That(context.Position, Is.EqualTo(expectedLength)); + Assert.That(context.MatchedSegment.Index, Is.Zero); + Assert.That(context.MatchedSegment.Length, Is.EqualTo(expectedLength)); + } + + [Test] + public void Separated_MinimumNotReached_RestoresPosition([Values] bool allowTrailing) + { + var parser = L('a').Separated(L(','), allowTrailing, min: 2); + var context = new ParseContext("!a,?"); + context.Advance(1); + + var success = parser.TryParse(ref context, out var value); + + Assert.That(success, Is.False); + Assert.That(value, Is.Null); + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("a,?")); + Assert.That(context.ToString(), Is.EqualTo("(1:4) Expected 'a'")); + } + + [Test] + public void SeparatedVoid_MinimumNotReached_RestoresPosition([Values] bool allowTrailing) + { + var parser = L('a').Separated(L(','), allowTrailing, min: 2).Void(); + var context = new ParseContext("!a,?"); + context.Advance(1); + + var success = parser.TryParse(ref context, out _); + + Assert.That(success, Is.False); + Assert.That(context.Position, Is.EqualTo(1)); + Assert.That(context.MatchedSegment.Length, Is.Zero); + Assert.That(context.Remaining.ToString(), Is.EqualTo("a,?")); + Assert.That(context.ToString(), Is.EqualTo("(1:4) Expected 'a'")); + } + + [Test] + public void Separated_InvalidBounds_ThrowsArgumentOutOfRangeException() + { + Assert.That( + () => L('a').Separated(L(','), min: -1, max: 1), + Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("min")); + + Assert.That( + () => L('a').Separated(L(','), min: 0, max: -1), + Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("max")); + + Assert.That( + () => L('a').Separated(L(','), min: 0, max: 0), + Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("max")); + + Assert.That( + () => L('a').Separated(L(','), min: 2, max: 1), + Throws.TypeOf().With.Property(nameof(ArgumentException.ParamName)).EqualTo("min")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Seq.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Seq.cs index 47d5b8e..870918c 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Seq.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Seq.cs @@ -1,294 +1,294 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void SeqTest() - { - var parser = Seq( - Seq( - Seq(Any, Any), - Seq(Any, Any)), - Seq( - Seq(Any, Any), - Seq(Any, Any))); - - Assert.That(parser.Map(m => m.ToString()).Parse("12345678").Value, Is.EqualTo("12345678")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 8))); - } - - [Test] - public void Seq2Test() - { - var parser = Seq(Any, Any); - - Assert.That(parser.Parse("12").Success, Is.True); - Assert.That(parser.Parse("123").Success, Is.True); - parser.Do((c1, c2) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - return (c1, c2); - }).Parse("12"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 2))); - Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("12")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123").Value, Is.EqualTo((0, 2))); - Assert.That(parser.Parse("12").Value, Is.EqualTo(('1', '2'))); - Assert.That(parser.Parse("123").Value, Is.EqualTo(('1', '2'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - } - - [Test] - public void Seq3Test() - { - var parser = Seq(Any, Any, Any); - - Assert.That(parser.Parse("123").Success, Is.True); - Assert.That(parser.Parse("1234").Success, Is.True); - parser.Do((c1, c2, c3) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - return (c1, c2, c3); - }).Parse("123"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123").Value, Is.EqualTo((0, 3))); - Assert.That(parser.Map(m => m.ToString()).Parse("123").Value, Is.EqualTo("123")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234").Value, Is.EqualTo((0, 3))); - Assert.That(parser.Parse("123").Value, Is.EqualTo(('1', '2', '3'))); - Assert.That(parser.Parse("1234").Value, Is.EqualTo(('1', '2', '3'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - } - - [Test] - public void Seq4Test() - { - var parser = Seq(Any, Any, Any, Any); - - Assert.That(parser.Parse("1234").Success, Is.True); - Assert.That(parser.Parse("12345").Success, Is.True); - parser.Do((c1, c2, c3, c4) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - Assert.That(c4, Is.EqualTo('4')); - return (c1, c2, c3, c4); - }).Parse("1234"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234").Value, Is.EqualTo((0, 4))); - Assert.That(parser.Map(m => m.ToString()).Parse("1234").Value, Is.EqualTo("1234")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345").Value, Is.EqualTo((0, 4))); - Assert.That(parser.Parse("1234").Value, Is.EqualTo(('1', '2', '3', '4'))); - Assert.That(parser.Parse("12345").Value, Is.EqualTo(('1', '2', '3', '4'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - - Assert.That(parser.Parse("123").Success, Is.False); - Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); - } - - [Test] - public void Seq5Test() - { - var parser = Seq(Any, Any, Any, Any, Any); - - Assert.That(parser.Parse("12345").Success, Is.True); - Assert.That(parser.Parse("123456").Success, Is.True); - parser.Do((c1, c2, c3, c4, c5) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - Assert.That(c4, Is.EqualTo('4')); - Assert.That(c5, Is.EqualTo('5')); - return (c1, c2, c3, c4, c5); - }).Parse("12345"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345").Value, Is.EqualTo((0, 5))); - Assert.That(parser.Map(m => m.ToString()).Parse("12345").Value, Is.EqualTo("12345")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456").Value, Is.EqualTo((0, 5))); - Assert.That(parser.Parse("12345").Value, Is.EqualTo(('1', '2', '3', '4', '5'))); - Assert.That(parser.Parse("123456").Value, Is.EqualTo(('1', '2', '3', '4', '5'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - - Assert.That(parser.Parse("123").Success, Is.False); - Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); - - Assert.That(parser.Parse("1234").Success, Is.False); - Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); - } - - [Test] - public void Seq6Test() - { - var parser = Seq(Any, Any, Any, Any, Any, Any); - - Assert.That(parser.Parse("123456").Success, Is.True); - Assert.That(parser.Parse("1234567").Success, Is.True); - parser.Do((c1, c2, c3, c4, c5, c6) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - Assert.That(c4, Is.EqualTo('4')); - Assert.That(c5, Is.EqualTo('5')); - Assert.That(c6, Is.EqualTo('6')); - return (c1, c2, c3, c4, c5, c6); - }).Parse("123456"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456").Value, Is.EqualTo((0, 6))); - Assert.That(parser.Map(m => m.ToString()).Parse("123456").Value, Is.EqualTo("123456")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234567").Value, Is.EqualTo((0, 6))); - Assert.That(parser.Parse("123456").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6'))); - Assert.That(parser.Parse("1234567").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - - Assert.That(parser.Parse("123").Success, Is.False); - Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); - - Assert.That(parser.Parse("1234").Success, Is.False); - Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); - - Assert.That(parser.Parse("12345").Success, Is.False); - Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); - } - - [Test] - public void Seq7Test() - { - var parser = Seq(Any, Any, Any, Any, Any, Any, Any); - - Assert.That(parser.Parse("1234567").Success, Is.True); - Assert.That(parser.Parse("12345678").Success, Is.True); - parser.Do((c1, c2, c3, c4, c5, c6, c7) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - Assert.That(c4, Is.EqualTo('4')); - Assert.That(c5, Is.EqualTo('5')); - Assert.That(c6, Is.EqualTo('6')); - Assert.That(c7, Is.EqualTo('7')); - return (c1, c2, c3, c4, c5, c6, c7); - }).Parse("1234567"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234567").Value, Is.EqualTo((0, 7))); - Assert.That(parser.Map(m => m.ToString()).Parse("1234567").Value, Is.EqualTo("1234567")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 7))); - Assert.That(parser.Parse("1234567").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7'))); - Assert.That(parser.Parse("12345678").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - - Assert.That(parser.Parse("123").Success, Is.False); - Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); - - Assert.That(parser.Parse("1234").Success, Is.False); - Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); - - Assert.That(parser.Parse("12345").Success, Is.False); - Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); - - Assert.That(parser.Parse("123456").Success, Is.False); - Assert.That(parser.Parse("123456").ErrorMessage, Is.EqualTo("(1:7) Expected any character")); - } - - [Test] - public void Seq8Test() - { - var parser = Seq(Any, Any, Any, Any, Any, Any, Any, Any); - - Assert.That(parser.Parse("12345678").Success, Is.True); - Assert.That(parser.Parse("123456789").Success, Is.True); - parser.Do((c1, c2, c3, c4, c5, c6, c7, c8) => - { - Assert.That(c1, Is.EqualTo('1')); - Assert.That(c2, Is.EqualTo('2')); - Assert.That(c3, Is.EqualTo('3')); - Assert.That(c4, Is.EqualTo('4')); - Assert.That(c5, Is.EqualTo('5')); - Assert.That(c6, Is.EqualTo('6')); - Assert.That(c7, Is.EqualTo('7')); - Assert.That(c8, Is.EqualTo('8')); - return (c1, c2, c3, c4, c5, c6, c7, c8); - }).Parse("12345678"); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 8))); - Assert.That(parser.Map(m => m.ToString()).Parse("12345678").Value, Is.EqualTo("12345678")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456789").Value, Is.EqualTo((0, 8))); - Assert.That(parser.Parse("12345678").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7', '8'))); - Assert.That(parser.Parse("123456789").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7', '8'))); - - Assert.That(parser.Parse("").Success, Is.False); - Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); - - Assert.That(parser.Parse("12").Success, Is.False); - Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); - - Assert.That(parser.Parse("123").Success, Is.False); - Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); - - Assert.That(parser.Parse("1234").Success, Is.False); - Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); - - Assert.That(parser.Parse("12345").Success, Is.False); - Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); - - Assert.That(parser.Parse("123456").Success, Is.False); - Assert.That(parser.Parse("123456").ErrorMessage, Is.EqualTo("(1:7) Expected any character")); - - Assert.That(parser.Parse("1234567").Success, Is.False); - Assert.That(parser.Parse("1234567").ErrorMessage, Is.EqualTo("(1:8) Expected any character")); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void SeqTest() + { + var parser = Seq( + Seq( + Seq(Any, Any), + Seq(Any, Any)), + Seq( + Seq(Any, Any), + Seq(Any, Any))); + + Assert.That(parser.Map(m => m.ToString()).Parse("12345678").Value, Is.EqualTo("12345678")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 8))); + } + + [Test] + public void Seq2Test() + { + var parser = Seq(Any, Any); + + Assert.That(parser.Parse("12").Success, Is.True); + Assert.That(parser.Parse("123").Success, Is.True); + parser.Do((c1, c2) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + return (c1, c2); + }).Parse("12"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 2))); + Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("12")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123").Value, Is.EqualTo((0, 2))); + Assert.That(parser.Parse("12").Value, Is.EqualTo(('1', '2'))); + Assert.That(parser.Parse("123").Value, Is.EqualTo(('1', '2'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + } + + [Test] + public void Seq3Test() + { + var parser = Seq(Any, Any, Any); + + Assert.That(parser.Parse("123").Success, Is.True); + Assert.That(parser.Parse("1234").Success, Is.True); + parser.Do((c1, c2, c3) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + return (c1, c2, c3); + }).Parse("123"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123").Value, Is.EqualTo((0, 3))); + Assert.That(parser.Map(m => m.ToString()).Parse("123").Value, Is.EqualTo("123")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234").Value, Is.EqualTo((0, 3))); + Assert.That(parser.Parse("123").Value, Is.EqualTo(('1', '2', '3'))); + Assert.That(parser.Parse("1234").Value, Is.EqualTo(('1', '2', '3'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + } + + [Test] + public void Seq4Test() + { + var parser = Seq(Any, Any, Any, Any); + + Assert.That(parser.Parse("1234").Success, Is.True); + Assert.That(parser.Parse("12345").Success, Is.True); + parser.Do((c1, c2, c3, c4) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + Assert.That(c4, Is.EqualTo('4')); + return (c1, c2, c3, c4); + }).Parse("1234"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234").Value, Is.EqualTo((0, 4))); + Assert.That(parser.Map(m => m.ToString()).Parse("1234").Value, Is.EqualTo("1234")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345").Value, Is.EqualTo((0, 4))); + Assert.That(parser.Parse("1234").Value, Is.EqualTo(('1', '2', '3', '4'))); + Assert.That(parser.Parse("12345").Value, Is.EqualTo(('1', '2', '3', '4'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + + Assert.That(parser.Parse("123").Success, Is.False); + Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); + } + + [Test] + public void Seq5Test() + { + var parser = Seq(Any, Any, Any, Any, Any); + + Assert.That(parser.Parse("12345").Success, Is.True); + Assert.That(parser.Parse("123456").Success, Is.True); + parser.Do((c1, c2, c3, c4, c5) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + Assert.That(c4, Is.EqualTo('4')); + Assert.That(c5, Is.EqualTo('5')); + return (c1, c2, c3, c4, c5); + }).Parse("12345"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345").Value, Is.EqualTo((0, 5))); + Assert.That(parser.Map(m => m.ToString()).Parse("12345").Value, Is.EqualTo("12345")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456").Value, Is.EqualTo((0, 5))); + Assert.That(parser.Parse("12345").Value, Is.EqualTo(('1', '2', '3', '4', '5'))); + Assert.That(parser.Parse("123456").Value, Is.EqualTo(('1', '2', '3', '4', '5'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + + Assert.That(parser.Parse("123").Success, Is.False); + Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); + + Assert.That(parser.Parse("1234").Success, Is.False); + Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); + } + + [Test] + public void Seq6Test() + { + var parser = Seq(Any, Any, Any, Any, Any, Any); + + Assert.That(parser.Parse("123456").Success, Is.True); + Assert.That(parser.Parse("1234567").Success, Is.True); + parser.Do((c1, c2, c3, c4, c5, c6) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + Assert.That(c4, Is.EqualTo('4')); + Assert.That(c5, Is.EqualTo('5')); + Assert.That(c6, Is.EqualTo('6')); + return (c1, c2, c3, c4, c5, c6); + }).Parse("123456"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456").Value, Is.EqualTo((0, 6))); + Assert.That(parser.Map(m => m.ToString()).Parse("123456").Value, Is.EqualTo("123456")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234567").Value, Is.EqualTo((0, 6))); + Assert.That(parser.Parse("123456").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6'))); + Assert.That(parser.Parse("1234567").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + + Assert.That(parser.Parse("123").Success, Is.False); + Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); + + Assert.That(parser.Parse("1234").Success, Is.False); + Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); + + Assert.That(parser.Parse("12345").Success, Is.False); + Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); + } + + [Test] + public void Seq7Test() + { + var parser = Seq(Any, Any, Any, Any, Any, Any, Any); + + Assert.That(parser.Parse("1234567").Success, Is.True); + Assert.That(parser.Parse("12345678").Success, Is.True); + parser.Do((c1, c2, c3, c4, c5, c6, c7) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + Assert.That(c4, Is.EqualTo('4')); + Assert.That(c5, Is.EqualTo('5')); + Assert.That(c6, Is.EqualTo('6')); + Assert.That(c7, Is.EqualTo('7')); + return (c1, c2, c3, c4, c5, c6, c7); + }).Parse("1234567"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("1234567").Value, Is.EqualTo((0, 7))); + Assert.That(parser.Map(m => m.ToString()).Parse("1234567").Value, Is.EqualTo("1234567")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 7))); + Assert.That(parser.Parse("1234567").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7'))); + Assert.That(parser.Parse("12345678").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + + Assert.That(parser.Parse("123").Success, Is.False); + Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); + + Assert.That(parser.Parse("1234").Success, Is.False); + Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); + + Assert.That(parser.Parse("12345").Success, Is.False); + Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); + + Assert.That(parser.Parse("123456").Success, Is.False); + Assert.That(parser.Parse("123456").ErrorMessage, Is.EqualTo("(1:7) Expected any character")); + } + + [Test] + public void Seq8Test() + { + var parser = Seq(Any, Any, Any, Any, Any, Any, Any, Any); + + Assert.That(parser.Parse("12345678").Success, Is.True); + Assert.That(parser.Parse("123456789").Success, Is.True); + parser.Do((c1, c2, c3, c4, c5, c6, c7, c8) => + { + Assert.That(c1, Is.EqualTo('1')); + Assert.That(c2, Is.EqualTo('2')); + Assert.That(c3, Is.EqualTo('3')); + Assert.That(c4, Is.EqualTo('4')); + Assert.That(c5, Is.EqualTo('5')); + Assert.That(c6, Is.EqualTo('6')); + Assert.That(c7, Is.EqualTo('7')); + Assert.That(c8, Is.EqualTo('8')); + return (c1, c2, c3, c4, c5, c6, c7, c8); + }).Parse("12345678"); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12345678").Value, Is.EqualTo((0, 8))); + Assert.That(parser.Map(m => m.ToString()).Parse("12345678").Value, Is.EqualTo("12345678")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123456789").Value, Is.EqualTo((0, 8))); + Assert.That(parser.Parse("12345678").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7', '8'))); + Assert.That(parser.Parse("123456789").Value, Is.EqualTo(('1', '2', '3', '4', '5', '6', '7', '8'))); + + Assert.That(parser.Parse("").Success, Is.False); + Assert.That(parser.Parse("").ErrorMessage, Is.EqualTo("(1:1) Expected any character")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("1").ErrorMessage, Is.EqualTo("(1:2) Expected any character")); + + Assert.That(parser.Parse("12").Success, Is.False); + Assert.That(parser.Parse("12").ErrorMessage, Is.EqualTo("(1:3) Expected any character")); + + Assert.That(parser.Parse("123").Success, Is.False); + Assert.That(parser.Parse("123").ErrorMessage, Is.EqualTo("(1:4) Expected any character")); + + Assert.That(parser.Parse("1234").Success, Is.False); + Assert.That(parser.Parse("1234").ErrorMessage, Is.EqualTo("(1:5) Expected any character")); + + Assert.That(parser.Parse("12345").Success, Is.False); + Assert.That(parser.Parse("12345").ErrorMessage, Is.EqualTo("(1:6) Expected any character")); + + Assert.That(parser.Parse("123456").Success, Is.False); + Assert.That(parser.Parse("123456").ErrorMessage, Is.EqualTo("(1:7) Expected any character")); + + Assert.That(parser.Parse("1234567").Success, Is.False); + Assert.That(parser.Parse("1234567").ErrorMessage, Is.EqualTo("(1:8) Expected any character")); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Set_Opt.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Set_Opt.cs index c1207a2..4537982 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Set_Opt.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Set_Opt.cs @@ -1,218 +1,218 @@ -using System.Globalization; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; -using System.Text; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void Set_Any() - { - var parser = Choice(Set("0-9"), Set("\0-\uFFFF")); - Assert.That(parser, Is.SameAs(Any)); - } - - [Test] - public void Set_UC() - { - var parser = Choice(L(GeneralUnicodeCategory.OtherLetter), L(GeneralUnicodeCategory.FinalQuotePunctuation)); - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+UnicodeCategoryParser`1[System.Char]")); - - IncludeTest(parser, c => char.GetUnicodeCategory(c) is UnicodeCategory.OtherLetter or UnicodeCategory.FinalQuotePunctuation); - } - - [Test] - public void Set_SingeChar() - { - var parser = Set("a"); - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+CharParser`1[System.Char]")); - } - - [Test] - public void Set_SingleRange() - { - var parser1 = Set("a-z"); - var parser2 = OneOf("0123456789"); - var parser3 = Choice(OneOf("0123456789"), L(UnicodeCategory.DecimalDigitNumber)); - var parser4 = Choice(Set("0-9"), L(UnicodeCategory.DecimalDigitNumber)); - - Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - Assert.That(parser4.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); - - IncludeTest( - OneOf("0123456789"), - c => c is >= '0' and <= '9'); - - IncludeTest( - Choice(OneOf("0123456789"), L(UnicodeCategory.DecimalDigitNumber)), - c => c is >= '0' and <= '9' || char.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber); - } - - [Test] - public void Set_MultipleRanges_OverallWidth_128() - { - var parser1 = Set("a-z0-9"); - var parser2 = Set("а-жА-Ж"); - var parser3 = Set("a-z0-9\u0085\u00A0"); - Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - - IncludeTest( - Set("a-z0-9\u0085\u00A0"), - c => c is >= '0' and <= '9' or >= 'a' and <= 'z' or '\u0085' or '\u00A0'); - - IncludeTest( - Set("а-жА-Ж"), - c => c is >= 'а' and <= 'ж' or >= 'А' and <= 'Ж'); - } - - [Test] - public void Set_MultipleRanges_OverallWidth_256() - { - var parser1 = Set("\0\u0085\u00A0a-z0-9"); - var parser2 = Set("\0\u0085\u00FFa-z0-9"); - var parser3 = Set("\u3040-\u309F\u3100-\u312F"); - - Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); - Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); - Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); - - IncludeTest( - Set("\0\u0085\u00A0\u00FFa-z0-9"), - c => c is >= 'a' and <= 'z' or >= '0' and <= '9' or '\0' or '\u0085' or '\u00A0' or '\u00FF'); - - IncludeTest( - Set("\u3040-\u309F\u3100-\u312F"), - c => c is >= '\u3040' and <= '\u309F' or >= '\u3100' and <= '\u312f'); - } - - [Test] - public void Set_MultipleRanges_OverallWidth_512() - { - var parser1 = Set("\0\u0085\u00A0\u0190a-z0-9"); - var parser2 = Set("\0\u0085\u00A0\u01FFa-z0-9"); - var parser3 = Set("\u3040-\u309F\u3130-\u318F"); - - Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); - Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); - Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); - - IncludeTest( - Set("\0\u0085\u00A0\u0190a-z0-9"), - c => c is >= 'a' and <= 'z' or >= '0' and <= '9' or '\0' or '\u0085' or '\u00A0' or '\u0190'); - - IncludeTest( - Set("\u3040-\u309F\u3130-\u318F"), - c => c is >= '\u3040' and <= '\u309F' or >= '\u3130' and <= '\u318f'); - } - - [Test] - public void Set_MultipleRanges_ContainsSearch() - { - var sb = new StringBuilder(); - for (var c = '\0'; c < 128*5; c = (char)(c + 5)) - sb.Append($"\\u{(int)c:x4}"); - - var parser = Set(sb.ToString()); - - Assert.That(parser.GetType().ToString(), - Avx2.IsSupported - ? Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+ContainsSearcher]") - : Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BinaryRangeSearcher]")); - IncludeTest(parser, c => c < 128*5 && c % 5 == 0); - } - - [Test] - public void Set_MultipleRanges_Simd256() - { - if (!Avx2.IsSupported) - return; - - var sb = new StringBuilder(); - var chars = new HashSet { char.MinValue }; - - sb.Append('\0'); - - for (var c = (char)1000; c < 1050; c = (char)(c + 5)) - { - sb.Append($"\\u{c+0:x4}"); - sb.Append($"\\u{c+1:x4}"); - sb.Append($"\\u{c+2:x4}"); - - chars.Add(c); - chars.Add((char)(c + 1)); - chars.Add((char)(c + 2)); - } - - var parser = Set(sb.ToString()); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); - IncludeTest(parser, chars.Contains); - } - - [Test] - public void Set_MultipleRanges_Simd128() - { - if (!Sse41.IsSupported && !AdvSimd.IsSupported) - return; - - var sb = new StringBuilder(); - var chars = new HashSet { char.MinValue }; - - sb.Append('\0'); - - for (var c = (char)1000; c < 1030; c = (char)(c + 5)) - { - sb.Append($"\\u{c+0:x4}"); - sb.Append($"\\u{c+1:x4}"); - sb.Append($"\\u{c+2:x4}"); - - chars.Add((char)(c + 0)); - chars.Add((char)(c + 1)); - chars.Add((char)(c + 2)); - } - - var parser = Set(sb.ToString()); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); - IncludeTest(parser, chars.Contains); - } - - [Test] - public void Set_MultipleRanges_BinarySearch() - { - var sb = new StringBuilder(); - for (var c = '\0'; c < 10240; c = (char)(c + 2)) - sb.Append($"\\u{(int)c:x4}"); - - var parser = Set(sb.ToString()); - - Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BinaryRangeSearcher]")); - IncludeTest(parser, c => c < 10240 && c % 2 == 0); - } - - private static void IncludeTest(Parser parser, Func included) - { - for (var c = 0; c <= 65535; c++) - { - var s = new string((char)c, 1); - - if (parser.TryParse(s, out var v)) - { - Assert.That(included((char)c), Is.True, $"Code: 0x{c:x4}"); - Assert.That(v, Is.EqualTo((char)c), $"Code: 0x{c:x4}"); - } - else - { - Assert.That(included((char)c), Is.False, $"Code: 0x{c:x4}"); - } - } - } -} +using System.Globalization; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using System.Text; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void Set_Any() + { + var parser = Choice(Set("0-9"), Set("\0-\uFFFF")); + Assert.That(parser, Is.SameAs(Any)); + } + + [Test] + public void Set_UC() + { + var parser = Choice(L(GeneralUnicodeCategory.OtherLetter), L(GeneralUnicodeCategory.FinalQuotePunctuation)); + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+UnicodeCategoryParser`1[System.Char]")); + + IncludeTest(parser, c => char.GetUnicodeCategory(c) is UnicodeCategory.OtherLetter or UnicodeCategory.FinalQuotePunctuation); + } + + [Test] + public void Set_SingeChar() + { + var parser = Set("a"); + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+CharParser`1[System.Char]")); + } + + [Test] + public void Set_SingleRange() + { + var parser1 = Set("a-z"); + var parser2 = OneOf("0123456789"); + var parser3 = Choice(OneOf("0123456789"), L(UnicodeCategory.DecimalDigitNumber)); + var parser4 = Choice(Set("0-9"), L(UnicodeCategory.DecimalDigitNumber)); + + Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + Assert.That(parser4.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+RangeSearcher]")); + + IncludeTest( + OneOf("0123456789"), + c => c is >= '0' and <= '9'); + + IncludeTest( + Choice(OneOf("0123456789"), L(UnicodeCategory.DecimalDigitNumber)), + c => c is >= '0' and <= '9' || char.GetUnicodeCategory(c) == UnicodeCategory.DecimalDigitNumber); + } + + [Test] + public void Set_MultipleRanges_OverallWidth_128() + { + var parser1 = Set("a-z0-9"); + var parser2 = Set("а-жА-Ж"); + var parser3 = Set("a-z0-9\u0085\u00A0"); + Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + + IncludeTest( + Set("a-z0-9\u0085\u00A0"), + c => c is >= '0' and <= '9' or >= 'a' and <= 'z' or '\u0085' or '\u00A0'); + + IncludeTest( + Set("а-жА-Ж"), + c => c is >= 'а' and <= 'ж' or >= 'А' and <= 'Ж'); + } + + [Test] + public void Set_MultipleRanges_OverallWidth_256() + { + var parser1 = Set("\0\u0085\u00A0a-z0-9"); + var parser2 = Set("\0\u0085\u00FFa-z0-9"); + var parser3 = Set("\u3040-\u309F\u3100-\u312F"); + + Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); + Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); + Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); + + IncludeTest( + Set("\0\u0085\u00A0\u00FFa-z0-9"), + c => c is >= 'a' and <= 'z' or >= '0' and <= '9' or '\0' or '\u0085' or '\u00A0' or '\u00FF'); + + IncludeTest( + Set("\u3040-\u309F\u3100-\u312F"), + c => c is >= '\u3040' and <= '\u309F' or >= '\u3100' and <= '\u312f'); + } + + [Test] + public void Set_MultipleRanges_OverallWidth_512() + { + var parser1 = Set("\0\u0085\u00A0\u0190a-z0-9"); + var parser2 = Set("\0\u0085\u00A0\u01FFa-z0-9"); + var parser3 = Set("\u3040-\u309F\u3130-\u318F"); + + Assert.That(parser1.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); + Assert.That(parser2.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); + Assert.That(parser3.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BitVectorSearcher`1[Ramstack.Parsing.Utilities.Block512Bit]]")); + + IncludeTest( + Set("\0\u0085\u00A0\u0190a-z0-9"), + c => c is >= 'a' and <= 'z' or >= '0' and <= '9' or '\0' or '\u0085' or '\u00A0' or '\u0190'); + + IncludeTest( + Set("\u3040-\u309F\u3130-\u318F"), + c => c is >= '\u3040' and <= '\u309F' or >= '\u3130' and <= '\u318f'); + } + + [Test] + public void Set_MultipleRanges_ContainsSearch() + { + var sb = new StringBuilder(); + for (var c = '\0'; c < 128*5; c = (char)(c + 5)) + sb.Append($"\\u{(int)c:x4}"); + + var parser = Set(sb.ToString()); + + Assert.That(parser.GetType().ToString(), + Avx2.IsSupported + ? Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+ContainsSearcher]") + : Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BinaryRangeSearcher]")); + IncludeTest(parser, c => c < 128*5 && c % 5 == 0); + } + + [Test] + public void Set_MultipleRanges_Simd256() + { + if (!Avx2.IsSupported) + return; + + var sb = new StringBuilder(); + var chars = new HashSet { char.MinValue }; + + sb.Append('\0'); + + for (var c = (char)1000; c < 1050; c = (char)(c + 5)) + { + sb.Append($"\\u{c+0:x4}"); + sb.Append($"\\u{c+1:x4}"); + sb.Append($"\\u{c+2:x4}"); + + chars.Add(c); + chars.Add((char)(c + 1)); + chars.Add((char)(c + 2)); + } + + var parser = Set(sb.ToString()); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block256Bit]]")); + IncludeTest(parser, chars.Contains); + } + + [Test] + public void Set_MultipleRanges_Simd128() + { + if (!Sse41.IsSupported && !AdvSimd.IsSupported) + return; + + var sb = new StringBuilder(); + var chars = new HashSet { char.MinValue }; + + sb.Append('\0'); + + for (var c = (char)1000; c < 1030; c = (char)(c + 5)) + { + sb.Append($"\\u{c+0:x4}"); + sb.Append($"\\u{c+1:x4}"); + sb.Append($"\\u{c+2:x4}"); + + chars.Add((char)(c + 0)); + chars.Add((char)(c + 1)); + chars.Add((char)(c + 2)); + } + + var parser = Set(sb.ToString()); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+SimdRangeSearcher`1[Ramstack.Parsing.Utilities.Block128Bit]]")); + IncludeTest(parser, chars.Contains); + } + + [Test] + public void Set_MultipleRanges_BinarySearch() + { + var sb = new StringBuilder(); + for (var c = '\0'; c < 10240; c = (char)(c + 2)) + sb.Append($"\\u{(int)c:x4}"); + + var parser = Set(sb.ToString()); + + Assert.That(parser.GetType().ToString(), Is.EqualTo("Ramstack.Parsing.Parser+RangeParser`2[System.Char,Ramstack.Parsing.Parser+BinaryRangeSearcher]")); + IncludeTest(parser, c => c < 10240 && c % 2 == 0); + } + + private static void IncludeTest(Parser parser, Func included) + { + for (var c = 0; c <= 65535; c++) + { + var s = new string((char)c, 1); + + if (parser.TryParse(s, out var v)) + { + Assert.That(included((char)c), Is.True, $"Code: 0x{c:x4}"); + Assert.That(v, Is.EqualTo((char)c), $"Code: 0x{c:x4}"); + } + else + { + Assert.That(included((char)c), Is.False, $"Code: 0x{c:x4}"); + } + } + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.String.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.String.cs index f5163db..e2a78a8 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.String.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.String.cs @@ -1,135 +1,135 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void StringTest() - { - Assert.That(L("Apple").Parse("Apple").Success, Is.True); - Assert.That(L("Apple").Parse("Apple").Value, Is.EqualTo("Apple")); - Assert.That(L("Apple").Parse("Apples").Value, Is.EqualTo("Apple")); - Assert.That(L("Apple").Map(m => (m.Index, m.Length)).Parse("Apple").Value, Is.EqualTo((0, 5))); - Assert.That(L("Apple").Map(m => m.ToString()).Parse("Apple").Value, Is.EqualTo("Apple")); - - Assert.That(L("Apple", StringComparison.OrdinalIgnoreCase).Parse("apple").Value, Is.EqualTo("Apple")); - - Assert.That(L("Apple").Parse("apple").Success, Is.False); - Assert.That(L("Apple").Parse("apple").ErrorMessage, Is.EqualTo("(1:1) Expected 'Apple'")); - Assert.That(L("Apple").Parse("Fruit").Success, Is.False); - Assert.That(L("Apple").Parse("").Success, Is.False); - } - - [Test] - public void OneOf_String_OverlappingWords() - { - var literals = new[] - { - "Sun", - "Sunset", - "Light", - "Lighthouse", - "Star", - "Starlight", - "Book", - "Bookstore", - "Home", - "Homeland", - "Sea", - "Seashore", - "1234", - "12345", - "Практика", - "Практикант" - }; - - for (var count = 2; count <= literals.Length; count++) - { - var values = literals[..count]; - - foreach (var value in values) - { - foreach (var comparison in Enum.GetValues()) - { - var lower = ((int)comparison & 1) == 1 - ? value.ToLower() - : value; - - var upper = ((int)comparison & 1) == 1 - ? value.ToUpper() - : value; - - var parser = OneOf(values, comparison); - - Assert.That(parser.Parse(value).Success, Is.True); - Assert.That(parser.Parse(lower).Success, Is.True); - Assert.That(parser.Parse(upper).Success, Is.True); - Assert.That(parser.Parse(value).Value, Is.EqualTo(value)); - Assert.That(parser.Parse(lower).Value, Is.EqualTo(value)); - Assert.That(parser.Parse(upper).Value, Is.EqualTo(value)); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(value).Value, Is.EqualTo((0, value.Length))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(lower).Value, Is.EqualTo((0, value.Length))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(upper).Value, Is.EqualTo((0, value.Length))); - } - } - } - } - - [Test] - public void OneOf_String() - { - var literals = new[] - { - "Sun", - "Rain", - "Lighthouse", - "Starlight", - "Bookstore", - "Home", - "Seashore", - "12345", - "Практика" - }; - - for (var count = 2; count <= literals.Length; count++) - { - var values = literals[..count]; - - foreach (var value in values) - { - foreach (var comparison in Enum.GetValues()) - { - var lower = ((int)comparison & 1) == 1 - ? value.ToLower() - : value; - - var upper = ((int)comparison & 1) == 1 - ? value.ToLower() - : value; - - var parser = OneOf(values, comparison); - - Assert.That(parser.Parse(value).Success, Is.True); - Assert.That(parser.Parse(lower).Success, Is.True); - Assert.That(parser.Parse(upper).Success, Is.True); - Assert.That(parser.Parse(value).Value, Is.EqualTo(value)); - Assert.That(parser.Parse(lower).Value, Is.EqualTo(value)); - Assert.That(parser.Parse(upper).Value, Is.EqualTo(value)); - - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(value).Value, Is.EqualTo((0, value.Length))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(lower).Value, Is.EqualTo((0, value.Length))); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(upper).Value, Is.EqualTo((0, value.Length))); - } - } - } - - foreach (var text in new[] { "None", "Light", "123", "Практик" }) - { - Assert.That( - OneOf(literals).Parse(text).ErrorMessage, - Is.EqualTo("(1:1) Expected 'Sun', 'Rain', 'Lighthouse', 'Starlight', 'Bookstore', 'Home', 'Seashore', '12345', or 'Практика'")); - } - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void StringTest() + { + Assert.That(L("Apple").Parse("Apple").Success, Is.True); + Assert.That(L("Apple").Parse("Apple").Value, Is.EqualTo("Apple")); + Assert.That(L("Apple").Parse("Apples").Value, Is.EqualTo("Apple")); + Assert.That(L("Apple").Map(m => (m.Index, m.Length)).Parse("Apple").Value, Is.EqualTo((0, 5))); + Assert.That(L("Apple").Map(m => m.ToString()).Parse("Apple").Value, Is.EqualTo("Apple")); + + Assert.That(L("Apple", StringComparison.OrdinalIgnoreCase).Parse("apple").Value, Is.EqualTo("Apple")); + + Assert.That(L("Apple").Parse("apple").Success, Is.False); + Assert.That(L("Apple").Parse("apple").ErrorMessage, Is.EqualTo("(1:1) Expected 'Apple'")); + Assert.That(L("Apple").Parse("Fruit").Success, Is.False); + Assert.That(L("Apple").Parse("").Success, Is.False); + } + + [Test] + public void OneOf_String_OverlappingWords() + { + var literals = new[] + { + "Sun", + "Sunset", + "Light", + "Lighthouse", + "Star", + "Starlight", + "Book", + "Bookstore", + "Home", + "Homeland", + "Sea", + "Seashore", + "1234", + "12345", + "Практика", + "Практикант" + }; + + for (var count = 2; count <= literals.Length; count++) + { + var values = literals[..count]; + + foreach (var value in values) + { + foreach (var comparison in Enum.GetValues()) + { + var lower = ((int)comparison & 1) == 1 + ? value.ToLower() + : value; + + var upper = ((int)comparison & 1) == 1 + ? value.ToUpper() + : value; + + var parser = OneOf(values, comparison); + + Assert.That(parser.Parse(value).Success, Is.True); + Assert.That(parser.Parse(lower).Success, Is.True); + Assert.That(parser.Parse(upper).Success, Is.True); + Assert.That(parser.Parse(value).Value, Is.EqualTo(value)); + Assert.That(parser.Parse(lower).Value, Is.EqualTo(value)); + Assert.That(parser.Parse(upper).Value, Is.EqualTo(value)); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(value).Value, Is.EqualTo((0, value.Length))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(lower).Value, Is.EqualTo((0, value.Length))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(upper).Value, Is.EqualTo((0, value.Length))); + } + } + } + } + + [Test] + public void OneOf_String() + { + var literals = new[] + { + "Sun", + "Rain", + "Lighthouse", + "Starlight", + "Bookstore", + "Home", + "Seashore", + "12345", + "Практика" + }; + + for (var count = 2; count <= literals.Length; count++) + { + var values = literals[..count]; + + foreach (var value in values) + { + foreach (var comparison in Enum.GetValues()) + { + var lower = ((int)comparison & 1) == 1 + ? value.ToLower() + : value; + + var upper = ((int)comparison & 1) == 1 + ? value.ToLower() + : value; + + var parser = OneOf(values, comparison); + + Assert.That(parser.Parse(value).Success, Is.True); + Assert.That(parser.Parse(lower).Success, Is.True); + Assert.That(parser.Parse(upper).Success, Is.True); + Assert.That(parser.Parse(value).Value, Is.EqualTo(value)); + Assert.That(parser.Parse(lower).Value, Is.EqualTo(value)); + Assert.That(parser.Parse(upper).Value, Is.EqualTo(value)); + + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(value).Value, Is.EqualTo((0, value.Length))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(lower).Value, Is.EqualTo((0, value.Length))); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(upper).Value, Is.EqualTo((0, value.Length))); + } + } + } + + foreach (var text in new[] { "None", "Light", "123", "Практик" }) + { + Assert.That( + OneOf(literals).Parse(text).ErrorMessage, + Is.EqualTo("(1:1) Expected 'Sun', 'Rain', 'Lighthouse', 'Starlight', 'Bookstore', 'Home', 'Seashore', '12345', or 'Практика'")); + } + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Then.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Then.cs index 1d266e1..d7d86d2 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Then.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Then.cs @@ -1,19 +1,19 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void ThenTest() - { - var parser = Any.Then(Any); - - Assert.That(parser.Parse("12").Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((1, 1))); - Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("2")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void ThenTest() + { + var parser = Any.Then(Any); + + Assert.That(parser.Parse("12").Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((1, 1))); + Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("2")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs index e8f1f69..76371bf 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.ThenIgnore.cs @@ -1,19 +1,19 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void ThenIgnoreTest() - { - var parser = Any.ThenIgnore(Any); - - Assert.That(parser.Parse("12").Success, Is.True); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 1))); - Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("1")); - - Assert.That(parser.Parse("1").Success, Is.False); - Assert.That(parser.Parse("").Success, Is.False); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void ThenIgnoreTest() + { + var parser = Any.ThenIgnore(Any); + + Assert.That(parser.Parse("12").Success, Is.True); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("12").Value, Is.EqualTo((0, 1))); + Assert.That(parser.Map(m => m.ToString()).Parse("12").Value, Is.EqualTo("1")); + + Assert.That(parser.Parse("1").Success, Is.False); + Assert.That(parser.Parse("").Success, Is.False); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.UnicodeCategory.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.UnicodeCategory.cs index a567329..b3f4a30 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.UnicodeCategory.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.UnicodeCategory.cs @@ -1,45 +1,45 @@ -using System.Globalization; - -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void UnicodeCategoryTest() - { - var parser = L(UnicodeCategory.DecimalDigitNumber); - - for (var c = '0'; c <= '9'; c++) - { - var s = c.ToString(); - Assert.That(parser.Parse(s).Success, Is.True); - Assert.That(parser.Parse(s).Value, Is.EqualTo(c)); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); - } - - Assert.That(parser.Parse("a").Success, Is.False); - Assert.That(parser.Parse("*").Success, Is.False); - } - - // [Test] - // public void UnicodeCategoriesTest() - // { - // var parser = OneOf( - // UnicodeCategory.DecimalDigitNumber, - // UnicodeCategory.LowercaseLetter, - // UnicodeCategory.UppercaseLetter); - // - // foreach (var c in "0123456789abcdefghABCDEFGH") - // { - // var s = c.ToString(); - // Assert.That(parser.Parse(s).Success, Is.True); - // Assert.That(parser.Parse(s).Value, Is.EqualTo(c)); - // Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); - // } - // - // Assert.That(parser.Parse(" ").Success, Is.False); - // Assert.That(parser.Parse("*").Success, Is.False); - // } -} +using System.Globalization; + +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void UnicodeCategoryTest() + { + var parser = L(UnicodeCategory.DecimalDigitNumber); + + for (var c = '0'; c <= '9'; c++) + { + var s = c.ToString(); + Assert.That(parser.Parse(s).Success, Is.True); + Assert.That(parser.Parse(s).Value, Is.EqualTo(c)); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); + } + + Assert.That(parser.Parse("a").Success, Is.False); + Assert.That(parser.Parse("*").Success, Is.False); + } + + // [Test] + // public void UnicodeCategoriesTest() + // { + // var parser = OneOf( + // UnicodeCategory.DecimalDigitNumber, + // UnicodeCategory.LowercaseLetter, + // UnicodeCategory.UppercaseLetter); + // + // foreach (var c in "0123456789abcdefghABCDEFGH") + // { + // var s = c.ToString(); + // Assert.That(parser.Parse(s).Success, Is.True); + // Assert.That(parser.Parse(s).Value, Is.EqualTo(c)); + // Assert.That(parser.Map(m => (m.Index, m.Length)).Parse(s).Value, Is.EqualTo((0, 1))); + // } + // + // Assert.That(parser.Parse(" ").Success, Is.False); + // Assert.That(parser.Parse("*").Success, Is.False); + // } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.Until.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.Until.cs index 515c793..36a7680 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.Until.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.Until.cs @@ -1,20 +1,20 @@ -using static Ramstack.Parsing.Parser; - -namespace Ramstack.Parsing; - -partial class ParsersTests -{ - [Test] - public void UntilTest() - { - var parser = Choice( - Set("0-9"), - Set("a-zA-Z") - ).Until(Seq(L(','), S)); - - Assert.That(parser.Parse("123,").Success, Is.True); - Assert.That(parser.Parse("abc,").Success, Is.True); - Assert.That(parser.Parse("abc,").Value, Is.EquivalentTo("abc")); - Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123,abc").Value, Is.EqualTo((0, 3))); - } -} +using static Ramstack.Parsing.Parser; + +namespace Ramstack.Parsing; + +partial class ParsersTests +{ + [Test] + public void UntilTest() + { + var parser = Choice( + Set("0-9"), + Set("a-zA-Z") + ).Until(Seq(L(','), S)); + + Assert.That(parser.Parse("123,").Success, Is.True); + Assert.That(parser.Parse("abc,").Success, Is.True); + Assert.That(parser.Parse("abc,").Value, Is.EquivalentTo("abc")); + Assert.That(parser.Map(m => (m.Index, m.Length)).Parse("123,abc").Value, Is.EqualTo((0, 3))); + } +} diff --git a/tests/Ramstack.Parsing.Tests/ParsersTests.cs b/tests/Ramstack.Parsing.Tests/ParsersTests.cs index 2130949..50dbd77 100644 --- a/tests/Ramstack.Parsing.Tests/ParsersTests.cs +++ b/tests/Ramstack.Parsing.Tests/ParsersTests.cs @@ -1,20 +1,20 @@ -namespace Ramstack.Parsing; - -[TestFixture] -public partial class ParsersTests -{ - // Bound parser invocations so a missing progress check fails instead of hanging the test run. - private sealed class BoundedParser(Parser parser) : Parser - { - public int Attempts { get; private set; } - - public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) - { - if (++Attempts > 16) - throw new InvalidOperationException( - "The repetition did not stop after matching empty input."); - - return parser.TryParse(ref context, out value); - } - } -} +namespace Ramstack.Parsing; + +[TestFixture] +public partial class ParsersTests +{ + // Bound parser invocations so a missing progress check fails instead of hanging the test run. + private sealed class BoundedParser(Parser parser) : Parser + { + public int Attempts { get; private set; } + + public override bool TryParse(ref ParseContext context, [NotNullWhen(true)] out T? value) + { + if (++Attempts > 16) + throw new InvalidOperationException( + "The repetition did not stop after matching empty input."); + + return parser.TryParse(ref context, out value); + } + } +} diff --git a/tests/Ramstack.Parsing.Tests/Ramstack.Parsing.Tests.csproj b/tests/Ramstack.Parsing.Tests/Ramstack.Parsing.Tests.csproj index 9786469..a53edf6 100644 --- a/tests/Ramstack.Parsing.Tests/Ramstack.Parsing.Tests.csproj +++ b/tests/Ramstack.Parsing.Tests/Ramstack.Parsing.Tests.csproj @@ -1,35 +1,35 @@ - - - - net6.0;net7.0;net8.0;net9.0 - Ramstack.Parsing - enable - enable - latest - false - - - - TEST_INTERNALS - - - - - - - - - - - - - - - - - - - - - - + + + + net6.0;net7.0;net8.0;net9.0 + Ramstack.Parsing + enable + enable + latest + false + + + + TEST_INTERNALS + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/ComplexTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/ComplexTests.cs index 0589f2d..c65a2f1 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/ComplexTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/ComplexTests.cs @@ -1,83 +1,83 @@ -using System.Globalization; - -namespace Ramstack.Parsing.Scenarios; - -using static Parser; - -[TestFixture] -public class ComplexTests -{ - [TestCase("-2147483648")] - [TestCase("2147483647")] - [TestCase("+2147483647")] - [TestCase("10")] - [TestCase("10")] - [TestCase("13")] - [TestCase("1024")] - [TestCase("-10")] - [TestCase("-0")] - [TestCase("+0")] - [TestCase("+1075")] - [TestCase("", "(1:1) Expected [+-] or [0-9]")] - [TestCase("text", "(1:1) Expected [+-] or [0-9]")] - [TestCase("+", "(1:2) Expected [0-9]")] - [TestCase("-", "(1:2) Expected [0-9]")] - [TestCase("~10", "(1:1) Expected [+-] or [0-9]")] - [TestCase("+text", "(1:2) Expected [0-9]")] - [TestCase("-text", "(1:2) Expected [0-9]")] - [TestCase("10 ", "(1:3) Expected [0-9] or end of input")] - [TestCase(" 10", "(1:1) Expected [+-] or [0-9]")] - [TestCase("12345678+0", "(1:9) Expected [0-9] or end of input")] - public void ParseDecimalNumberTest(string expression, string error = "") - { - // [+-]? ['0'..'9']+ EOF - - var parser = Seq( - Set("+-").Optional(), - Set("0-9").OneOrMore(), - Eof).Map(s => int.Parse(s)); - - if (int.TryParse(expression, out var value) && !string.IsNullOrEmpty(error)) - value = 0; - - ParserTest(parser, expression, string.IsNullOrEmpty(error), value, error); - } - - [Test] - public void ParseFixedFloatTest() - { - // [0-9]+ ('.' [0-9]+)? EOL - - var digit = Set("0-9"); - var value = Seq( - digit.OneOrMore(), - Seq(L('.'), digit.OneOrMore()).Optional(), - Eof).Map(s => double.Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); - - Assert.That(value.Parse("1").Value, Is.EqualTo( 1.00)); - Assert.That(value.Parse("10.72").Value, Is.EqualTo(10.72)); - Assert.That(value.Parse("1.75").Value, Is.EqualTo( 1.75)); - Assert.That(value.Parse("1.00").Value, Is.EqualTo( 1.00)); - - Assert.That(value.Parse("1.").Success, Is.False); - Assert.That(value.Parse("1 d").Success, Is.False); - Assert.That(value.Parse("1.0d").Success, Is.False); - Assert.That(value.Parse("1.0 d").Success, Is.False); - Assert.That(value.Parse("1. d").Success, Is.False); - Assert.That(value.Parse("1.d").Success, Is.False); - Assert.That(value.Parse(".1").Success, Is.False); - Assert.That(value.Parse("").Success, Is.False); - Assert.That(value.Parse(" ").Success, Is.False); - Assert.That(value.Parse(".").Success, Is.False); - Assert.That(value.Parse("c").Success, Is.False); - } - - private static void ParserTest(Parser parser, string expression, bool success, T value, string error) - { - var result = parser.Parse(expression); - - Assert.That(result.Success, Is.EqualTo(success), $"#1: {expression}"); - Assert.That(result.Value, Is.EqualTo(success ? value : default), $"#2: {expression}"); - Assert.That(result.ToString(), Is.EqualTo(success ? value!.ToString() : error), $"#3: {expression}"); - } -} +using System.Globalization; + +namespace Ramstack.Parsing.Scenarios; + +using static Parser; + +[TestFixture] +public class ComplexTests +{ + [TestCase("-2147483648")] + [TestCase("2147483647")] + [TestCase("+2147483647")] + [TestCase("10")] + [TestCase("10")] + [TestCase("13")] + [TestCase("1024")] + [TestCase("-10")] + [TestCase("-0")] + [TestCase("+0")] + [TestCase("+1075")] + [TestCase("", "(1:1) Expected [+-] or [0-9]")] + [TestCase("text", "(1:1) Expected [+-] or [0-9]")] + [TestCase("+", "(1:2) Expected [0-9]")] + [TestCase("-", "(1:2) Expected [0-9]")] + [TestCase("~10", "(1:1) Expected [+-] or [0-9]")] + [TestCase("+text", "(1:2) Expected [0-9]")] + [TestCase("-text", "(1:2) Expected [0-9]")] + [TestCase("10 ", "(1:3) Expected [0-9] or end of input")] + [TestCase(" 10", "(1:1) Expected [+-] or [0-9]")] + [TestCase("12345678+0", "(1:9) Expected [0-9] or end of input")] + public void ParseDecimalNumberTest(string expression, string error = "") + { + // [+-]? ['0'..'9']+ EOF + + var parser = Seq( + Set("+-").Optional(), + Set("0-9").OneOrMore(), + Eof).Map(s => int.Parse(s)); + + if (int.TryParse(expression, out var value) && !string.IsNullOrEmpty(error)) + value = 0; + + ParserTest(parser, expression, string.IsNullOrEmpty(error), value, error); + } + + [Test] + public void ParseFixedFloatTest() + { + // [0-9]+ ('.' [0-9]+)? EOL + + var digit = Set("0-9"); + var value = Seq( + digit.OneOrMore(), + Seq(L('.'), digit.OneOrMore()).Optional(), + Eof).Map(s => double.Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); + + Assert.That(value.Parse("1").Value, Is.EqualTo( 1.00)); + Assert.That(value.Parse("10.72").Value, Is.EqualTo(10.72)); + Assert.That(value.Parse("1.75").Value, Is.EqualTo( 1.75)); + Assert.That(value.Parse("1.00").Value, Is.EqualTo( 1.00)); + + Assert.That(value.Parse("1.").Success, Is.False); + Assert.That(value.Parse("1 d").Success, Is.False); + Assert.That(value.Parse("1.0d").Success, Is.False); + Assert.That(value.Parse("1.0 d").Success, Is.False); + Assert.That(value.Parse("1. d").Success, Is.False); + Assert.That(value.Parse("1.d").Success, Is.False); + Assert.That(value.Parse(".1").Success, Is.False); + Assert.That(value.Parse("").Success, Is.False); + Assert.That(value.Parse(" ").Success, Is.False); + Assert.That(value.Parse(".").Success, Is.False); + Assert.That(value.Parse("c").Success, Is.False); + } + + private static void ParserTest(Parser parser, string expression, bool success, T value, string error) + { + var result = parser.Parse(expression); + + Assert.That(result.Success, Is.EqualTo(success), $"#1: {expression}"); + Assert.That(result.Value, Is.EqualTo(success ? value : default), $"#2: {expression}"); + Assert.That(result.ToString(), Is.EqualTo(success ? value!.ToString() : error), $"#3: {expression}"); + } +} diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs index 8d4cb26..4f1c48f 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/JsonTests.cs @@ -1,160 +1,160 @@ -using System.Text.Json; - -using Samples.Json; - -namespace Ramstack.Parsing.Scenarios; - -[TestFixture] -public class JsonTests -{ - [Test] - public void JsonParseTest() - { - #if NET7_0_OR_GREATER - var s1 = JsonSerializer.Serialize(JsonParser.Parser.Parse(Json).Value); - var s2 = JsonSerializer.Serialize(JsonSerializer.Deserialize(Json)); - Assert.That(s1, Is.EqualTo(s2)); - #endif - } - - private const string Json = - """ - [ - { - "_id": "5420ea1c9d34f914b83bc28a", - "index": 0.1, - "guid": "88ccc7c0-e2dd-4523-8066-84c58f0dfd5d", - "isActive": false, - "balance": "$1,553.84", - "picture": "https://placehold.it/32x32", - "age": 26.1, - "eyeColor": "green", - "name": "Goodwin Cherry", - "gender": "male", - "company": "DIGIPRINT", - "email": "goodwincherry@digiprint.com", - "phone": "+1 (809) 545-2897", - "address": "894 Montauk Avenue, Wyoming, Virgin Islands, 7416", - "about": "Id aute esse amet tempor excepteur in ex aute velit irure. Proident mollit minim fugiat ex non anim Lorem mollit est ullamco consectetur dolor officia adipisicing. Voluptate ut et non quis nisi. Quis occaecat commodo excepteur et deserunt fugiat sit occaecat. Deserunt incididunt laboris nisi sint irure nostrud sint amet et id. Ut pariatur in eiusmod sit excepteur.\r\n", - "registered": "2014-06-14T20:34:23-12:00", - "latitude": -89.19685, - "longitude": 32.263679, - "tags": [ - "aliqua", - "esse", - "non", - "officia", - "enim", - "nostrud", - "consectetur" - ], - "friends": [ - { - "id": 1.1, - "name": "Saundra Lucas" - }, - { - "id": 1.2, - "name": "Rachelle Hancock" - }, - { - "id": 1.3, - "name": "Sharp Wilkerson" - } - ], - "greeting": "Hello, Goodwin Cherry! You have 9 unread messages.", - "favoriteFruit": "banana" - }, - { - "_id": "5420ea1c2ce68275fc87b664", - "index": 1.1, - "guid": "63e815df-7102-42ca-96ae-adf7915dbd1f", - "isActive": false, - "balance": "$2,184.56", - "picture": "https://placehold.it/32x32", - "age": 23.1, - "eyeColor": "green", - "name": "Perez Zamora", - "gender": "male", - "company": "SHADEASE", - "email": "perezzamora@shadease.com", - "phone": "+1 (843) 562-2745", - "address": "902 Noel Avenue, Goldfield, Massachusetts, 5555", - "about": "Labore sunt qui consequat anim veniam voluptate aliqua. Tempor ullamco est culpa anim incididunt ipsum culpa veniam et ut ad excepteur commodo Lorem. Laboris duis do amet culpa sunt in velit incididunt nisi ex dolor. Lorem do sint veniam sint pariatur qui reprehenderit. Nisi in cupidatat eu irure esse et ipsum Lorem.\r\n", - "registered": "2014-06-11T11:33:57-12:00", - "latitude": 52.963756, - "longitude": -97.251673, - "tags": [ - "commodo", - "do", - "ullamco", - "duis", - "aliqua", - "adipisicing", - "aliquip" - ], - "friends": [ - { - "id": 1.1, - "name": "Strickland Mack" - }, - { - "id": 1.2, - "name": "Allison Oneill" - }, - { - "id": 1.3, - "name": "Hampton Lawson" - } - ], - "greeting": "Hello, Perez Zamora! You have 4 unread messages.", - "favoriteFruit": "banana" - }, - { - "_id": "5420ea1c101c26a29fc0476d", - "index": 2.1, - "guid": "362d0d30-2aa3-4a14-b292-908288c05e1f", - "isActive": false, - "balance": "$2,429.42", - "picture": "https://placehold.it/32x32", - "age": 32.1, - "eyeColor": "blue", - "name": "Jenifer Thomas", - "gender": "female", - "company": "OPTIQUE", - "email": "jeniferthomas@optique.com", - "phone": "+1 (882) 462-3944", - "address": "820 Hausman Street, Yardville, Utah, 3474", - "about": "Eiusmod voluptate voluptate culpa est adipisicing officia nisi id irure voluptate irure do dolore. Laboris ipsum irure qui ut in. In ipsum nulla adipisicing eu reprehenderit deserunt excepteur culpa ea in nisi ut.\r\n", - "registered": "2014-09-09T05:57:09-12:00", - "latitude": 18.683324, - "longitude": -63.517571, - "tags": [ - "enim", - "cupidatat", - "velit", - "officia", - "irure", - "mollit", - "culpa" - ], - "friends": [ - { - "id": 1.1, - "name": "Ilene Stein" - }, - { - "id": 1.2, - "name": "Albert Jacobs" - }, - { - "id": 1.3, - "name": "Flores Malone" - } - ], - "greeting": "Hello, Jenifer Thomas! You have 8 unread messages.", - "favoriteFruit": "strawberry" - } - ] - """; -} +using System.Text.Json; + +using Samples.Json; + +namespace Ramstack.Parsing.Scenarios; + +[TestFixture] +public class JsonTests +{ + [Test] + public void JsonParseTest() + { + #if NET7_0_OR_GREATER + var s1 = JsonSerializer.Serialize(JsonParser.Parser.Parse(Json).Value); + var s2 = JsonSerializer.Serialize(JsonSerializer.Deserialize(Json)); + Assert.That(s1, Is.EqualTo(s2)); + #endif + } + + private const string Json = + """ + [ + { + "_id": "5420ea1c9d34f914b83bc28a", + "index": 0.1, + "guid": "88ccc7c0-e2dd-4523-8066-84c58f0dfd5d", + "isActive": false, + "balance": "$1,553.84", + "picture": "https://placehold.it/32x32", + "age": 26.1, + "eyeColor": "green", + "name": "Goodwin Cherry", + "gender": "male", + "company": "DIGIPRINT", + "email": "goodwincherry@digiprint.com", + "phone": "+1 (809) 545-2897", + "address": "894 Montauk Avenue, Wyoming, Virgin Islands, 7416", + "about": "Id aute esse amet tempor excepteur in ex aute velit irure. Proident mollit minim fugiat ex non anim Lorem mollit est ullamco consectetur dolor officia adipisicing. Voluptate ut et non quis nisi. Quis occaecat commodo excepteur et deserunt fugiat sit occaecat. Deserunt incididunt laboris nisi sint irure nostrud sint amet et id. Ut pariatur in eiusmod sit excepteur.\r\n", + "registered": "2014-06-14T20:34:23-12:00", + "latitude": -89.19685, + "longitude": 32.263679, + "tags": [ + "aliqua", + "esse", + "non", + "officia", + "enim", + "nostrud", + "consectetur" + ], + "friends": [ + { + "id": 1.1, + "name": "Saundra Lucas" + }, + { + "id": 1.2, + "name": "Rachelle Hancock" + }, + { + "id": 1.3, + "name": "Sharp Wilkerson" + } + ], + "greeting": "Hello, Goodwin Cherry! You have 9 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5420ea1c2ce68275fc87b664", + "index": 1.1, + "guid": "63e815df-7102-42ca-96ae-adf7915dbd1f", + "isActive": false, + "balance": "$2,184.56", + "picture": "https://placehold.it/32x32", + "age": 23.1, + "eyeColor": "green", + "name": "Perez Zamora", + "gender": "male", + "company": "SHADEASE", + "email": "perezzamora@shadease.com", + "phone": "+1 (843) 562-2745", + "address": "902 Noel Avenue, Goldfield, Massachusetts, 5555", + "about": "Labore sunt qui consequat anim veniam voluptate aliqua. Tempor ullamco est culpa anim incididunt ipsum culpa veniam et ut ad excepteur commodo Lorem. Laboris duis do amet culpa sunt in velit incididunt nisi ex dolor. Lorem do sint veniam sint pariatur qui reprehenderit. Nisi in cupidatat eu irure esse et ipsum Lorem.\r\n", + "registered": "2014-06-11T11:33:57-12:00", + "latitude": 52.963756, + "longitude": -97.251673, + "tags": [ + "commodo", + "do", + "ullamco", + "duis", + "aliqua", + "adipisicing", + "aliquip" + ], + "friends": [ + { + "id": 1.1, + "name": "Strickland Mack" + }, + { + "id": 1.2, + "name": "Allison Oneill" + }, + { + "id": 1.3, + "name": "Hampton Lawson" + } + ], + "greeting": "Hello, Perez Zamora! You have 4 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5420ea1c101c26a29fc0476d", + "index": 2.1, + "guid": "362d0d30-2aa3-4a14-b292-908288c05e1f", + "isActive": false, + "balance": "$2,429.42", + "picture": "https://placehold.it/32x32", + "age": 32.1, + "eyeColor": "blue", + "name": "Jenifer Thomas", + "gender": "female", + "company": "OPTIQUE", + "email": "jeniferthomas@optique.com", + "phone": "+1 (882) 462-3944", + "address": "820 Hausman Street, Yardville, Utah, 3474", + "about": "Eiusmod voluptate voluptate culpa est adipisicing officia nisi id irure voluptate irure do dolore. Laboris ipsum irure qui ut in. In ipsum nulla adipisicing eu reprehenderit deserunt excepteur culpa ea in nisi ut.\r\n", + "registered": "2014-09-09T05:57:09-12:00", + "latitude": 18.683324, + "longitude": -63.517571, + "tags": [ + "enim", + "cupidatat", + "velit", + "officia", + "irure", + "mollit", + "culpa" + ], + "friends": [ + { + "id": 1.1, + "name": "Ilene Stein" + }, + { + "id": 1.2, + "name": "Albert Jacobs" + }, + { + "id": 1.3, + "name": "Flores Malone" + } + ], + "greeting": "Hello, Jenifer Thomas! You have 8 unread messages.", + "favoriteFruit": "strawberry" + } + ] + """; +} diff --git a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs index 69118c6..f58de71 100644 --- a/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs +++ b/tests/Ramstack.Parsing.Tests/Scenarios/SimpleCalcTests.cs @@ -1,116 +1,116 @@ -using System.Globalization; - -namespace Ramstack.Parsing.Scenarios; - -using static Parser; - -[TestFixture] -public class SimpleCalcTests -{ - [TestCase("1", 1.0)] - [TestCase("(1.00)", 1.0)] - [TestCase("1 ", 1.0)] - [TestCase(" 1 ", 1.0)] - [TestCase("1.0 ", 1.0)] - [TestCase("1 + 9", 10d)] - [TestCase("(1 + 9)", 10d)] - [TestCase("2*(1 + 9)", 20d)] - [TestCase("(1 + 9)*2", 20d)] - [TestCase("(1 + 9) * 3", 30d)] - [TestCase("1 + 9*2", 19d)] - [TestCase("1 + 9*2 + 1", 20d)] - [TestCase("1 + 1 - 1 + 2 - 2", 1.0)] - [TestCase("3*5/5*5/5", 3.0)] - [TestCase("3* 5/5 *5/ 3", 5.0)] - [TestCase("3 * 5 / 5 * 5 / 3", 5.0)] - [TestCase("(1.2+2.8)*(4+2)/(2*4)", 3.0)] - - [TestCase("1.2+", 0, "(1:5) Expected [0-9] or '('")] - [TestCase("(1", 0, "(1:3) Expected [0-9], '.', [*/], [+-], or ')'")] - [TestCase("1+", 0, "(1:3) Expected [0-9] or '('")] - [TestCase("1)", 0, "(1:2) Expected [0-9], '.', [*/], [+-], or end of input")] - [TestCase("(1+)", 0, "(1:4) Expected [0-9] or '('")] - [TestCase("1+)", 0, "(1:3) Expected [0-9] or '('")] - [TestCase("1+1,2", 0, "(1:4) Expected [0-9], '.', [*/], [+-], or end of input")] - [TestCase("( )", 0, "(1:3) Expected [0-9] or '('")] - [TestCase("()", 0, "(1:2) Expected [0-9] or '('")] - public void SimpleCalcTest(string text, double number, string error = "") - { - // Expr : S Sum EOF - // Sum : Product ([+-] S Product)* - // Product : Primary ([*/] S Primary)* - // Primary : Parenthesis / Value - // Parenthesis : '(' S Sum ')' S - // Value : Number S - - var sum = Deferred(); - - var digit = - Set("0-9"); - - var value = - Seq( - digit.OneOrMore(), - Seq(L('.'), digit.OneOrMore()).Optional() - ).ThenIgnore(S).Map(m => - double.Parse(m, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); - - var parenthesis = - sum.Between( - Seq(L('('), S), - Seq(L(')'), S)); - - var primary = - Choice(value, parenthesis); - - var product = - Seq( - primary, - Seq( - OneOf("*/"), - S, - primary - ).Many() - ).Do(Multiply); - - sum.Parser = - Seq( - product, - Seq( - OneOf("+-"), - S, - product - ).Many() - ).Do(Add); - - var expression = - sum.Between(S, Eof); - - static double Multiply(double v, List<(char, Unit, double)> results) - { - foreach (var (op, _, d) in results) - v = op == '*' ? v * d : v / d; - - return v; - } - - static double Add(double v, List<(char, Unit, double)> results) - { - foreach (var (op, _, d) in results) - v = op == '+' ? v + d : v - d; - - return v; - } - - ParserTest(expression, text, string.IsNullOrEmpty(error), number, error); - } - - private static void ParserTest(Parser parser, string expression, bool success, T value, string error) - { - var result = parser.Parse(expression); - - Assert.That(result.Success, Is.EqualTo(success), $"#1: {expression}"); - Assert.That(result.Value, Is.EqualTo(success ? value : default), $"#2: {expression}"); - Assert.That(result.ToString(), Is.EqualTo(success ? value!.ToString() : error), $"#3: {expression}"); - } -} +using System.Globalization; + +namespace Ramstack.Parsing.Scenarios; + +using static Parser; + +[TestFixture] +public class SimpleCalcTests +{ + [TestCase("1", 1.0)] + [TestCase("(1.00)", 1.0)] + [TestCase("1 ", 1.0)] + [TestCase(" 1 ", 1.0)] + [TestCase("1.0 ", 1.0)] + [TestCase("1 + 9", 10d)] + [TestCase("(1 + 9)", 10d)] + [TestCase("2*(1 + 9)", 20d)] + [TestCase("(1 + 9)*2", 20d)] + [TestCase("(1 + 9) * 3", 30d)] + [TestCase("1 + 9*2", 19d)] + [TestCase("1 + 9*2 + 1", 20d)] + [TestCase("1 + 1 - 1 + 2 - 2", 1.0)] + [TestCase("3*5/5*5/5", 3.0)] + [TestCase("3* 5/5 *5/ 3", 5.0)] + [TestCase("3 * 5 / 5 * 5 / 3", 5.0)] + [TestCase("(1.2+2.8)*(4+2)/(2*4)", 3.0)] + + [TestCase("1.2+", 0, "(1:5) Expected [0-9] or '('")] + [TestCase("(1", 0, "(1:3) Expected [0-9], '.', [*/], [+-], or ')'")] + [TestCase("1+", 0, "(1:3) Expected [0-9] or '('")] + [TestCase("1)", 0, "(1:2) Expected [0-9], '.', [*/], [+-], or end of input")] + [TestCase("(1+)", 0, "(1:4) Expected [0-9] or '('")] + [TestCase("1+)", 0, "(1:3) Expected [0-9] or '('")] + [TestCase("1+1,2", 0, "(1:4) Expected [0-9], '.', [*/], [+-], or end of input")] + [TestCase("( )", 0, "(1:3) Expected [0-9] or '('")] + [TestCase("()", 0, "(1:2) Expected [0-9] or '('")] + public void SimpleCalcTest(string text, double number, string error = "") + { + // Expr : S Sum EOF + // Sum : Product ([+-] S Product)* + // Product : Primary ([*/] S Primary)* + // Primary : Parenthesis / Value + // Parenthesis : '(' S Sum ')' S + // Value : Number S + + var sum = Deferred(); + + var digit = + Set("0-9"); + + var value = + Seq( + digit.OneOrMore(), + Seq(L('.'), digit.OneOrMore()).Optional() + ).ThenIgnore(S).Map(m => + double.Parse(m, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture)); + + var parenthesis = + sum.Between( + Seq(L('('), S), + Seq(L(')'), S)); + + var primary = + Choice(value, parenthesis); + + var product = + Seq( + primary, + Seq( + OneOf("*/"), + S, + primary + ).Many() + ).Do(Multiply); + + sum.Parser = + Seq( + product, + Seq( + OneOf("+-"), + S, + product + ).Many() + ).Do(Add); + + var expression = + sum.Between(S, Eof); + + static double Multiply(double v, List<(char, Unit, double)> results) + { + foreach (var (op, _, d) in results) + v = op == '*' ? v * d : v / d; + + return v; + } + + static double Add(double v, List<(char, Unit, double)> results) + { + foreach (var (op, _, d) in results) + v = op == '+' ? v + d : v - d; + + return v; + } + + ParserTest(expression, text, string.IsNullOrEmpty(error), number, error); + } + + private static void ParserTest(Parser parser, string expression, bool success, T value, string error) + { + var result = parser.Parse(expression); + + Assert.That(result.Success, Is.EqualTo(success), $"#1: {expression}"); + Assert.That(result.Value, Is.EqualTo(success ? value : default), $"#2: {expression}"); + Assert.That(result.ToString(), Is.EqualTo(success ? value!.ToString() : error), $"#3: {expression}"); + } +} diff --git a/tests/Ramstack.Parsing.Tests/SimdConfigurationTests.cs b/tests/Ramstack.Parsing.Tests/SimdConfigurationTests.cs index 5306034..8876468 100644 --- a/tests/Ramstack.Parsing.Tests/SimdConfigurationTests.cs +++ b/tests/Ramstack.Parsing.Tests/SimdConfigurationTests.cs @@ -1,29 +1,29 @@ -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics.Arm; -using System.Runtime.Intrinsics.X86; - -namespace Ramstack.Parsing; - -[TestFixture] -public class SimdConfigurationTests -{ - [Test] - public void VerifySimdConfiguration() - { - if (Environment.GetEnvironmentVariable("DOTNET_EnableHWIntrinsic") == "0") - { - Assert.That(Sse2.IsSupported, Is.False); - Assert.That(Sse41.IsSupported, Is.False); - Assert.That(Avx2.IsSupported, Is.False); - Assert.That(AdvSimd.Arm64.IsSupported, Is.False); - Assert.That(AdvSimd.IsSupported, Is.False); - } - - if (RuntimeInformation.ProcessArchitecture == Architecture.X64 && Environment.GetEnvironmentVariable("DOTNET_EnableAVX2") == "0") - { - Assert.That(Sse2.IsSupported, Is.True); - Assert.That(Sse41.IsSupported, Is.True); - Assert.That(Avx2.IsSupported, Is.False); - } - } -} +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ramstack.Parsing; + +[TestFixture] +public class SimdConfigurationTests +{ + [Test] + public void VerifySimdConfiguration() + { + if (Environment.GetEnvironmentVariable("DOTNET_EnableHWIntrinsic") == "0") + { + Assert.That(Sse2.IsSupported, Is.False); + Assert.That(Sse41.IsSupported, Is.False); + Assert.That(Avx2.IsSupported, Is.False); + Assert.That(AdvSimd.Arm64.IsSupported, Is.False); + Assert.That(AdvSimd.IsSupported, Is.False); + } + + if (RuntimeInformation.ProcessArchitecture == Architecture.X64 && Environment.GetEnvironmentVariable("DOTNET_EnableAVX2") == "0") + { + Assert.That(Sse2.IsSupported, Is.True); + Assert.That(Sse41.IsSupported, Is.True); + Assert.That(Avx2.IsSupported, Is.False); + } + } +} diff --git a/tests/Ramstack.Parsing.Tests/Utilities/CharClassParserTests.cs b/tests/Ramstack.Parsing.Tests/Utilities/CharClassParserTests.cs index c7d5c29..e7785d7 100644 --- a/tests/Ramstack.Parsing.Tests/Utilities/CharClassParserTests.cs +++ b/tests/Ramstack.Parsing.Tests/Utilities/CharClassParserTests.cs @@ -1,97 +1,97 @@ -#if TEST_INTERNALS - -namespace Ramstack.Parsing.Utilities; - -[TestFixture] -public class CharClassParserTests -{ - [TestCase(@"\0", @"'\0'")] - [TestCase(@"\a", @"'\a'")] - [TestCase(@"\b", @"'\b'")] - [TestCase(@"\e", @"'\e'")] - [TestCase(@"\f", @"'\f'")] - [TestCase(@"\t", @"'\t'")] - [TestCase(@"\n", @"'\n'")] - [TestCase(@"\v", @"'\v'")] - [TestCase(@"\r", @"'\r'")] - [TestCase(@"\\", @"'\\'")] - [TestCase(@"\-", @"'-'")] - - [TestCase("-", "'-'")] - [TestCase("9", "'9'")] - - [TestCase("*+-", "[*+-]")] - [TestCase("0*+-", "[*+\\-0]")] - - - [TestCase("0-12-34-56-78-9", "[0-9]")] - [TestCase("0123456789", "[0-9]")] - [TestCase("6-72-30-18-94-5", "[0-9]")] - [TestCase("0-234-5678-9", "[0-9]")] - [TestCase("0-24-68-9", "[0-24-689]")] - [TestCase("01", "[01]")] - - [TestCase(@"\u0030\u0031\u0032\u0033", "[0-3]")] - - [TestCase(@"\p{L}\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}", @"[\p{L}]")] - [TestCase(@"\p{M}\p{Mn}\p{Mc}\p{Me}", @"[\p{M}]")] - [TestCase(@"\p{N}\p{Nd}\p{Nl}\p{No}", @"[\p{N}]")] - [TestCase(@"\p{Z}\p{Zs}\p{Zl}\p{Zp}", @"[\p{Z}]")] - [TestCase(@"\p{P}\p{Pc}\p{Pd}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}", @"[\p{P}]")] - [TestCase(@"\p{S}\p{Sm}\p{Sc}\p{Sk}\p{So}", @"[\p{S}]")] - [TestCase(@"\p{C}\p{Cc}\p{Cf}\p{Cs}\p{Co}\p{Cn}", @"[\p{C}]")] - - [TestCase(@"\P{L}", @"[\P{L}]")] - [TestCase(@"\P{M}", @"[\P{M}]")] - [TestCase(@"\P{N}", @"[\P{N}]")] - [TestCase(@"\P{Z}", @"[\P{Z}]")] - [TestCase(@"\P{P}", @"[\P{P}]")] - [TestCase(@"\P{S}", @"[\P{S}]")] - [TestCase(@"\P{C}", @"[\P{C}]")] - - [TestCase(@"\p{M}\p{N}\p{Z}\p{C}\p{P}\p{S}", @"[\P{L}]")] - [TestCase(@"\p{L}\p{N}\p{Z}\p{C}\p{P}\p{S}", @"[\P{M}]")] - [TestCase(@"\p{L}\p{M}\p{Z}\p{C}\p{P}\p{S}", @"[\P{N}]")] - [TestCase(@"\p{L}\p{M}\p{N}\p{C}\p{P}\p{S}", @"[\P{Z}]")] - [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{C}\p{S}", @"[\P{P}]")] - [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{C}\p{P}", @"[\P{S}]")] - [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{P}\p{S}", @"[\P{C}]")] - - [TestCase(@"a-z\p{L}", @"[a-z\p{L}]")] - [TestCase(@"\p{L}a-z", @"[a-z\p{L}]")] - [TestCase(@"\p{Lu}\p{Ll}\p{Lt}\p{Nd}", @"[\p{Lu}\p{Ll}\p{Lt}\p{Nd}]")] - - [TestCase(@"\w", @"[\p{L}\p{Mn}\p{Nd}\p{Pc}]")] - [TestCase(@"\d", @"[\p{Nd}]")] - [TestCase(@"\s", @"[\t-\r \u0085\p{Z}]")] - [TestCase(@"\W", @"[\p{Z}\p{C}\p{S}\p{Mc}\p{Me}\p{Nl}\p{No}\p{Pd}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]")] - [TestCase(@"\D", @"[\P{N}\p{Nl}\p{No}]")] - [TestCase(@"\S", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] - - [TestCase(@"\p{L}\p{M}\p{Z}\p{C}\p{P}\p{S}\p{Nl}\p{No}", @"[\P{N}\p{Nl}\p{No}]")] - - [TestCase(@"\0-\b\u000e-\u001f!-\u0084\p{L}\p{M}\p{N}\p{C}\p{P}\p{S}", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] - [TestCase(@"\0-\b\u000e-\u001f!-\u0084\P{Z}", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] - public void ParseTest(string set, string expected) - { - var @class = CharClassParser.Parse(set); - Assert.That(@class.ToPrintable(), Is.EqualTo(expected)); - } - - [TestCase("2-1", "Invalid pattern '2-1'. Range in reverse order.")] - [TestCase("\\p{Q}", "Invalid pattern '\\p{Q}'. Unknown property 'Q'.")] - [TestCase("\\p{Lu", "Invalid pattern '\\p{Lu'.")] - [TestCase("\\T", "Invalid pattern '\\T'. Unrecognized escape sequence.")] - [TestCase("\\ur234", "Invalid pattern '\\ur234'. Invalid Unicode escape sequence.")] - [TestCase("\\u1r34", "Invalid pattern '\\u1r34'. Invalid Unicode escape sequence.")] - [TestCase("\\u12r4", "Invalid pattern '\\u12r4'. Invalid Unicode escape sequence.")] - [TestCase("\\u123r", "Invalid pattern '\\u123r'. Invalid Unicode escape sequence.")] - public void ParseTest_Invalid(string set, string message) - { - Assert.That( - () => CharClassParser.Parse(set), - Throws.ArgumentException.With.Message.EqualTo(message)); - } -} - -#endif +#if TEST_INTERNALS + +namespace Ramstack.Parsing.Utilities; + +[TestFixture] +public class CharClassParserTests +{ + [TestCase(@"\0", @"'\0'")] + [TestCase(@"\a", @"'\a'")] + [TestCase(@"\b", @"'\b'")] + [TestCase(@"\e", @"'\e'")] + [TestCase(@"\f", @"'\f'")] + [TestCase(@"\t", @"'\t'")] + [TestCase(@"\n", @"'\n'")] + [TestCase(@"\v", @"'\v'")] + [TestCase(@"\r", @"'\r'")] + [TestCase(@"\\", @"'\\'")] + [TestCase(@"\-", @"'-'")] + + [TestCase("-", "'-'")] + [TestCase("9", "'9'")] + + [TestCase("*+-", "[*+-]")] + [TestCase("0*+-", "[*+\\-0]")] + + + [TestCase("0-12-34-56-78-9", "[0-9]")] + [TestCase("0123456789", "[0-9]")] + [TestCase("6-72-30-18-94-5", "[0-9]")] + [TestCase("0-234-5678-9", "[0-9]")] + [TestCase("0-24-68-9", "[0-24-689]")] + [TestCase("01", "[01]")] + + [TestCase(@"\u0030\u0031\u0032\u0033", "[0-3]")] + + [TestCase(@"\p{L}\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}", @"[\p{L}]")] + [TestCase(@"\p{M}\p{Mn}\p{Mc}\p{Me}", @"[\p{M}]")] + [TestCase(@"\p{N}\p{Nd}\p{Nl}\p{No}", @"[\p{N}]")] + [TestCase(@"\p{Z}\p{Zs}\p{Zl}\p{Zp}", @"[\p{Z}]")] + [TestCase(@"\p{P}\p{Pc}\p{Pd}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}", @"[\p{P}]")] + [TestCase(@"\p{S}\p{Sm}\p{Sc}\p{Sk}\p{So}", @"[\p{S}]")] + [TestCase(@"\p{C}\p{Cc}\p{Cf}\p{Cs}\p{Co}\p{Cn}", @"[\p{C}]")] + + [TestCase(@"\P{L}", @"[\P{L}]")] + [TestCase(@"\P{M}", @"[\P{M}]")] + [TestCase(@"\P{N}", @"[\P{N}]")] + [TestCase(@"\P{Z}", @"[\P{Z}]")] + [TestCase(@"\P{P}", @"[\P{P}]")] + [TestCase(@"\P{S}", @"[\P{S}]")] + [TestCase(@"\P{C}", @"[\P{C}]")] + + [TestCase(@"\p{M}\p{N}\p{Z}\p{C}\p{P}\p{S}", @"[\P{L}]")] + [TestCase(@"\p{L}\p{N}\p{Z}\p{C}\p{P}\p{S}", @"[\P{M}]")] + [TestCase(@"\p{L}\p{M}\p{Z}\p{C}\p{P}\p{S}", @"[\P{N}]")] + [TestCase(@"\p{L}\p{M}\p{N}\p{C}\p{P}\p{S}", @"[\P{Z}]")] + [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{C}\p{S}", @"[\P{P}]")] + [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{C}\p{P}", @"[\P{S}]")] + [TestCase(@"\p{L}\p{M}\p{N}\p{Z}\p{P}\p{S}", @"[\P{C}]")] + + [TestCase(@"a-z\p{L}", @"[a-z\p{L}]")] + [TestCase(@"\p{L}a-z", @"[a-z\p{L}]")] + [TestCase(@"\p{Lu}\p{Ll}\p{Lt}\p{Nd}", @"[\p{Lu}\p{Ll}\p{Lt}\p{Nd}]")] + + [TestCase(@"\w", @"[\p{L}\p{Mn}\p{Nd}\p{Pc}]")] + [TestCase(@"\d", @"[\p{Nd}]")] + [TestCase(@"\s", @"[\t-\r \u0085\p{Z}]")] + [TestCase(@"\W", @"[\p{Z}\p{C}\p{S}\p{Mc}\p{Me}\p{Nl}\p{No}\p{Pd}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]")] + [TestCase(@"\D", @"[\P{N}\p{Nl}\p{No}]")] + [TestCase(@"\S", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] + + [TestCase(@"\p{L}\p{M}\p{Z}\p{C}\p{P}\p{S}\p{Nl}\p{No}", @"[\P{N}\p{Nl}\p{No}]")] + + [TestCase(@"\0-\b\u000e-\u001f!-\u0084\p{L}\p{M}\p{N}\p{C}\p{P}\p{S}", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] + [TestCase(@"\0-\b\u000e-\u001f!-\u0084\P{Z}", @"[\0-\b\u000e-\u001f!-\u0084\P{Z}]")] + public void ParseTest(string set, string expected) + { + var @class = CharClassParser.Parse(set); + Assert.That(@class.ToPrintable(), Is.EqualTo(expected)); + } + + [TestCase("2-1", "Invalid pattern '2-1'. Range in reverse order.")] + [TestCase("\\p{Q}", "Invalid pattern '\\p{Q}'. Unknown property 'Q'.")] + [TestCase("\\p{Lu", "Invalid pattern '\\p{Lu'.")] + [TestCase("\\T", "Invalid pattern '\\T'. Unrecognized escape sequence.")] + [TestCase("\\ur234", "Invalid pattern '\\ur234'. Invalid Unicode escape sequence.")] + [TestCase("\\u1r34", "Invalid pattern '\\u1r34'. Invalid Unicode escape sequence.")] + [TestCase("\\u12r4", "Invalid pattern '\\u12r4'. Invalid Unicode escape sequence.")] + [TestCase("\\u123r", "Invalid pattern '\\u123r'. Invalid Unicode escape sequence.")] + public void ParseTest_Invalid(string set, string message) + { + Assert.That( + () => CharClassParser.Parse(set), + Throws.ArgumentException.With.Message.EqualTo(message)); + } +} + +#endif diff --git a/tests/Ramstack.Parsing.Tests/Utilities/StringBufferTests.cs b/tests/Ramstack.Parsing.Tests/Utilities/StringBufferTests.cs index 86010b6..9bb96a2 100644 --- a/tests/Ramstack.Parsing.Tests/Utilities/StringBufferTests.cs +++ b/tests/Ramstack.Parsing.Tests/Utilities/StringBufferTests.cs @@ -1,95 +1,95 @@ -#if TEST_INTERNALS - -namespace Ramstack.Parsing.Utilities; - -[TestFixture] -public class StringBufferTests -{ - [Test] - public void Ctor() - { - var sb = new StringBuffer(); - - Assert.That(sb.ToString(), Is.EqualTo("")); - Assert.That(sb.Length, Is.EqualTo(0)); - } - - [Test] - public void Ctor_Capacity() - { - var sb = new StringBuffer(10); - - Assert.That(sb.Length, Is.EqualTo(0)); - Assert.That(sb.ToString(), Is.EqualTo("")); - } - - [TestCase(-1)] - [TestCase(-10)] - [TestCase(int.MinValue)] - [TestCase(-int.MaxValue)] - public void Ctor_InvalidCapacity(int capacity) => - Assert.Throws(() => _ = new StringBuffer(capacity)); - - [Test] - public void Append_Char() - { - for (var count = 1; count <= 256; count++) - { - var sb = new StringBuffer(); - var expected = ""; - - for (var n = 1; n <= count; n++) - { - sb.Append('a'); - expected += 'a'; - - Assert.That(sb.Length, Is.EqualTo(n)); - } - - Assert.That(sb.ToString(), Is.EqualTo(expected)); - } - } - - [TestCase("")] - [TestCase("1")] - [TestCase("12")] - [TestCase("123456789")] - public void Append_ReadOnlySpan(string text) - { - var sb = new StringBuffer(); - sb.Append(text); - - Assert.That(sb.Length, Is.EqualTo(text.Length)); - Assert.That(sb.ToString(), Is.EqualTo(text)); - } - - [Test] - public void Append_LargeValue() - { - var a1 = new string('a', 50); - var a2 = new string('b', 1000); - - var sb = new StringBuffer(); - - sb.Append(a1); - Assert.That(sb.Length, Is.EqualTo(a1.Length)); - - sb.Append(a2); - Assert.That(sb.Length, Is.EqualTo(a1.Length + a2.Length)); - - Assert.That(sb.ToString(), Is.EqualTo(a1 + a2)); - } - - [Test] - public void FailOnLargeString() - { - Assert.Throws(() => - { - var sb = new StringBuffer(); - sb.Append(new char[2_000_000_000]); - sb.Append(new char[500_000_000]); - }); - } -} - -#endif +#if TEST_INTERNALS + +namespace Ramstack.Parsing.Utilities; + +[TestFixture] +public class StringBufferTests +{ + [Test] + public void Ctor() + { + var sb = new StringBuffer(); + + Assert.That(sb.ToString(), Is.EqualTo("")); + Assert.That(sb.Length, Is.EqualTo(0)); + } + + [Test] + public void Ctor_Capacity() + { + var sb = new StringBuffer(10); + + Assert.That(sb.Length, Is.EqualTo(0)); + Assert.That(sb.ToString(), Is.EqualTo("")); + } + + [TestCase(-1)] + [TestCase(-10)] + [TestCase(int.MinValue)] + [TestCase(-int.MaxValue)] + public void Ctor_InvalidCapacity(int capacity) => + Assert.Throws(() => _ = new StringBuffer(capacity)); + + [Test] + public void Append_Char() + { + for (var count = 1; count <= 256; count++) + { + var sb = new StringBuffer(); + var expected = ""; + + for (var n = 1; n <= count; n++) + { + sb.Append('a'); + expected += 'a'; + + Assert.That(sb.Length, Is.EqualTo(n)); + } + + Assert.That(sb.ToString(), Is.EqualTo(expected)); + } + } + + [TestCase("")] + [TestCase("1")] + [TestCase("12")] + [TestCase("123456789")] + public void Append_ReadOnlySpan(string text) + { + var sb = new StringBuffer(); + sb.Append(text); + + Assert.That(sb.Length, Is.EqualTo(text.Length)); + Assert.That(sb.ToString(), Is.EqualTo(text)); + } + + [Test] + public void Append_LargeValue() + { + var a1 = new string('a', 50); + var a2 = new string('b', 1000); + + var sb = new StringBuffer(); + + sb.Append(a1); + Assert.That(sb.Length, Is.EqualTo(a1.Length)); + + sb.Append(a2); + Assert.That(sb.Length, Is.EqualTo(a1.Length + a2.Length)); + + Assert.That(sb.ToString(), Is.EqualTo(a1 + a2)); + } + + [Test] + public void FailOnLargeString() + { + Assert.Throws(() => + { + var sb = new StringBuffer(); + sb.Append(new char[2_000_000_000]); + sb.Append(new char[500_000_000]); + }); + } +} + +#endif