Import nodejs_20.13.1+dfsg.orig-ada.tar.xz
authorJérémy Lal <kapouer@melix.org>
Wed, 15 May 2024 11:52:04 +0000 (13:52 +0200)
committerJérémy Lal <kapouer@melix.org>
Wed, 15 May 2024 11:52:04 +0000 (13:52 +0200)
[dgit import orig nodejs_20.13.1+dfsg.orig-ada.tar.xz]

168 files changed:
.clang-format [new file with mode: 0644]
.editorconfig [new file with mode: 0644]
.github/ISSUE_TEMPLATE/1-bug-report.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/2-feature-request.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/dependabot.yml [new file with mode: 0644]
.github/workflows/alpine.yml [new file with mode: 0644]
.github/workflows/cifuzz.yml [new file with mode: 0644]
.github/workflows/codeql.yml [new file with mode: 0644]
.github/workflows/dependency-review.yml [new file with mode: 0644]
.github/workflows/documentation.yml [new file with mode: 0644]
.github/workflows/emscripten.yml [new file with mode: 0644]
.github/workflows/lint_and_format_check.yml [new file with mode: 0644]
.github/workflows/macos_install.yml [new file with mode: 0644]
.github/workflows/release-script-tests.yml [new file with mode: 0644]
.github/workflows/release_create.yml [new file with mode: 0644]
.github/workflows/release_prepare.yml [new file with mode: 0644]
.github/workflows/scorecard.yml [new file with mode: 0644]
.github/workflows/ubuntu-release.yml [new file with mode: 0644]
.github/workflows/ubuntu-s390x.yml [new file with mode: 0644]
.github/workflows/ubuntu-sanitized.yml [new file with mode: 0644]
.github/workflows/ubuntu-undef.yml [new file with mode: 0644]
.github/workflows/ubuntu.yml [new file with mode: 0644]
.github/workflows/ubuntu_install.yml [new file with mode: 0644]
.github/workflows/ubuntu_old.yml [new file with mode: 0644]
.github/workflows/ubuntu_pedantic.yml [new file with mode: 0644]
.github/workflows/visual_studio.yml [new file with mode: 0644]
.github/workflows/visual_studio_clang.yml [new file with mode: 0644]
.github/workflows/wpt-updater.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.python-version [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
Dockerfile [new file with mode: 0644]
LICENSE-APACHE [new file with mode: 0644]
LICENSE-MIT [new file with mode: 0644]
README.md [new file with mode: 0644]
SECURITY.md [new file with mode: 0644]
benchmarks/CMakeLists.txt [new file with mode: 0644]
benchmarks/bbc_bench.cpp [new file with mode: 0644]
benchmarks/bench.cpp [new file with mode: 0644]
benchmarks/bench_search_params.cpp [new file with mode: 0644]
benchmarks/benchmark_header.h [new file with mode: 0644]
benchmarks/benchmark_template.cpp [new file with mode: 0644]
benchmarks/competitors/servo-url/Cargo.lock [new file with mode: 0644]
benchmarks/competitors/servo-url/Cargo.toml [new file with mode: 0644]
benchmarks/competitors/servo-url/README.md [new file with mode: 0644]
benchmarks/competitors/servo-url/cbindgen.toml [new file with mode: 0644]
benchmarks/competitors/servo-url/lib.rs [new file with mode: 0644]
benchmarks/competitors/servo-url/servo_url.h [new file with mode: 0644]
benchmarks/model_bench.cpp [new file with mode: 0644]
benchmarks/percent_encode.cpp [new file with mode: 0644]
benchmarks/performancecounters/apple_arm_events.h [new file with mode: 0644]
benchmarks/performancecounters/event_counter.h [new file with mode: 0644]
benchmarks/performancecounters/linux-perf-events.h [new file with mode: 0644]
benchmarks/wpt_bench.cpp [new file with mode: 0644]
clang-format-ignore.txt [new file with mode: 0644]
cmake/CPM.cmake [new file with mode: 0644]
cmake/ada-config.cmake.in [new file with mode: 0644]
cmake/ada-flags.cmake [new file with mode: 0644]
cmake/add-cpp-test.cmake [new file with mode: 0644]
cmake/clang-format.cmake [new file with mode: 0644]
cmake/codecoverage.cmake [new file with mode: 0644]
docs/RELEASE.md [new file with mode: 0644]
docs/cli.md [new file with mode: 0644]
docs/doxygen/footer.html [new file with mode: 0644]
docs/doxygen/header.html [new file with mode: 0644]
doxygen [new file with mode: 0644]
fuzz/build.sh [new file with mode: 0755]
fuzz/parse.cc [new file with mode: 0644]
include/ada.h [new file with mode: 0644]
include/ada/ada_idna.h [new file with mode: 0644]
include/ada/ada_version.h [new file with mode: 0644]
include/ada/character_sets-inl.h [new file with mode: 0644]
include/ada/character_sets.h [new file with mode: 0644]
include/ada/checkers-inl.h [new file with mode: 0644]
include/ada/checkers.h [new file with mode: 0644]
include/ada/common_defs.h [new file with mode: 0644]
include/ada/encoding_type.h [new file with mode: 0644]
include/ada/expected.h [new file with mode: 0644]
include/ada/helpers.h [new file with mode: 0644]
include/ada/implementation.h [new file with mode: 0644]
include/ada/log.h [new file with mode: 0644]
include/ada/parser.h [new file with mode: 0644]
include/ada/scheme-inl.h [new file with mode: 0644]
include/ada/scheme.h [new file with mode: 0644]
include/ada/serializers.h [new file with mode: 0644]
include/ada/state.h [new file with mode: 0644]
include/ada/unicode-inl.h [new file with mode: 0644]
include/ada/unicode.h [new file with mode: 0644]
include/ada/url-inl.h [new file with mode: 0644]
include/ada/url.h [new file with mode: 0644]
include/ada/url_aggregator-inl.h [new file with mode: 0644]
include/ada/url_aggregator.h [new file with mode: 0644]
include/ada/url_base-inl.h [new file with mode: 0644]
include/ada/url_base.h [new file with mode: 0644]
include/ada/url_components.h [new file with mode: 0644]
include/ada/url_search_params-inl.h [new file with mode: 0644]
include/ada/url_search_params.h [new file with mode: 0644]
include/ada_c.h [new file with mode: 0644]
pyproject.toml [new file with mode: 0644]
singleheader/CMakeLists.txt [new file with mode: 0644]
singleheader/README.md [new file with mode: 0644]
singleheader/amalgamate.py [new file with mode: 0755]
singleheader/demo.c [new file with mode: 0644]
singleheader/demo.cpp [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/ada.cpp [new file with mode: 0644]
src/ada_c.cpp [new file with mode: 0644]
src/ada_idna.cpp [new file with mode: 0644]
src/checkers.cpp [new file with mode: 0644]
src/helpers.cpp [new file with mode: 0644]
src/implementation.cpp [new file with mode: 0644]
src/parser.cpp [new file with mode: 0644]
src/serializers.cpp [new file with mode: 0644]
src/unicode.cpp [new file with mode: 0644]
src/url-getters.cpp [new file with mode: 0644]
src/url-setters.cpp [new file with mode: 0644]
src/url.cpp [new file with mode: 0644]
src/url_aggregator.cpp [new file with mode: 0644]
src/url_components.cpp [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/ada_c.cpp [new file with mode: 0644]
tests/basic_fuzzer.cpp [new file with mode: 0644]
tests/basic_tests.cpp [new file with mode: 0644]
tests/from_file_tests.cpp [new file with mode: 0644]
tests/installation/CMakeLists.txt [new file with mode: 0644]
tests/url_components.cpp [new file with mode: 0644]
tests/url_search_params.cpp [new file with mode: 0644]
tests/wasm/CMakeLists.txt [new file with mode: 0644]
tests/wasm/test.js.in [new file with mode: 0644]
tests/wasm/wasm.cpp [new file with mode: 0644]
tests/wpt/CMakeLists.txt [new file with mode: 0644]
tests/wpt/IdnaTestV2.json [new file with mode: 0644]
tests/wpt/ada_extra_setters_tests.json [new file with mode: 0644]
tests/wpt/ada_extra_urltestdata.json [new file with mode: 0644]
tests/wpt/ada_long_urltestdata.json [new file with mode: 0644]
tests/wpt/percent-encoding.json [new file with mode: 0644]
tests/wpt/setters_tests.json [new file with mode: 0644]
tests/wpt/toascii.json [new file with mode: 0644]
tests/wpt/urltestdata.json [new file with mode: 0644]
tests/wpt/verifydnslength_tests.json [new file with mode: 0644]
tests/wpt_tests.cpp [new file with mode: 0644]
tools/CMakeLists.txt [new file with mode: 0644]
tools/cli/CMakeLists.txt [new file with mode: 0644]
tools/cli/adaparse.cpp [new file with mode: 0644]
tools/cli/benchmark_adaparse.sh [new file with mode: 0644]
tools/cli/benchmark_write_to_file.sh [new file with mode: 0644]
tools/cli/line_iterator.h [new file with mode: 0644]
tools/lint_and_format.py [new file with mode: 0755]
tools/prepare-doxygen.sh [new file with mode: 0755]
tools/release/__init__.py [new file with mode: 0644]
tools/release/create_release.py [new file with mode: 0755]
tools/release/lib/__init__.py [new file with mode: 0644]
tools/release/lib/release.py [new file with mode: 0644]
tools/release/lib/tests/__init__.py [new file with mode: 0644]
tools/release/lib/tests/samples/ada_version_h.txt [new file with mode: 0644]
tools/release/lib/tests/samples/ada_version_h_expected.txt [new file with mode: 0644]
tools/release/lib/tests/samples/cmakelists.txt [new file with mode: 0644]
tools/release/lib/tests/samples/cmakelists_expected.txt [new file with mode: 0644]
tools/release/lib/tests/samples/doxygen.txt [new file with mode: 0644]
tools/release/lib/tests/samples/doxygen_expected.txt [new file with mode: 0644]
tools/release/lib/tests/test_release.py [new file with mode: 0644]
tools/release/lib/tests/test_update_versions.py [new file with mode: 0644]
tools/release/lib/versions.py [new file with mode: 0644]
tools/release/requirements.txt [new file with mode: 0644]
tools/release/update_versions.py [new file with mode: 0755]
tools/run-clangcldocker.sh [new file with mode: 0755]
tools/update-wpt.sh [new file with mode: 0755]

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..1acba5a
--- /dev/null
@@ -0,0 +1,2 @@
+BasedOnStyle: Google
+SortIncludes: false
diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..0b3779e
--- /dev/null
@@ -0,0 +1,5 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.yml b/.github/ISSUE_TEMPLATE/1-bug-report.yml
new file mode 100644 (file)
index 0000000..c11dae7
--- /dev/null
@@ -0,0 +1,40 @@
+name: 🐛 Bug report
+description: Create a report to help us improve
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thank you for reporting an issue.
+
+        Please fill in as much of the following form as you're able.
+  - type: input
+    attributes:
+      label: Version
+      description: Which Ada version are you referring to?
+  - type: input
+    attributes:
+      label: Platform
+      description: |
+        UNIX: output of `uname -a`
+        Windows: output of `"$([Environment]::OSVersion.VersionString) $(('x86', 'x64')[[Environment]::Is64BitOperatingSystem])"` in PowerShell console
+  - type: textarea
+    attributes:
+      label: What steps will reproduce the bug?
+      description: Enter details about your bug, preferably a simple code snippet that can be run directly without installing third-party dependencies.
+  - type: textarea
+    attributes:
+      label: How often does it reproduce? Is there a required condition?
+  - type: textarea
+    attributes:
+      label: What is the expected behavior?
+      description: If possible please provide textual output instead of screenshots.
+  - type: textarea
+    attributes:
+      label: What do you see instead?
+      description: If possible please provide textual output instead of screenshots.
+    validations:
+      required: true
+  - type: textarea
+    attributes:
+      label: Additional information
+      description: Tell us anything else you think we should know.
diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.yml b/.github/ISSUE_TEMPLATE/2-feature-request.yml
new file mode 100644 (file)
index 0000000..f7cae8c
--- /dev/null
@@ -0,0 +1,23 @@
+name: 🚀 Feature request
+description: Suggest an idea for this project
+labels: [feature request]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thank you for suggesting an idea to make Node.js better.
+
+        Please fill in as much of the following form as you're able.
+  - type: textarea
+    attributes:
+      label: What is the problem this feature will solve?
+    validations:
+      required: true
+  - type: textarea
+    attributes:
+      label: What is the feature you are proposing to solve the problem?
+    validations:
+      required: true
+  - type: textarea
+    attributes:
+      label: What alternatives have you considered?
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..3159b47
--- /dev/null
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+  - name: Looking for documentation?
+    url: https://ada-url.github.io/ada
+    about: Please navigate to our documentation website.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644 (file)
index 0000000..1cfc22e
--- /dev/null
@@ -0,0 +1,23 @@
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: monthly
+
+  - package-ecosystem: docker
+    directory: /
+    schedule:
+      interval: monthly
+
+  - package-ecosystem: cargo
+    directory: /benchmarks/competitors/servo-url
+    schedule:
+      interval: monthly
+
+  - package-ecosystem: pip
+    directory: /tools/release
+    schedule:
+      interval: monthly
\ No newline at end of file
diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml
new file mode 100644 (file)
index 0000000..29d058c
--- /dev/null
@@ -0,0 +1,45 @@
+name: Alpine Linux
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.5.2
+      - name: start docker
+        run: |
+          docker run -w /src -dit --name alpine -v $PWD:/src alpine:latest
+          echo 'docker exec alpine "$@";' > ./alpine.sh
+          chmod +x ./alpine.sh
+      - name: install packages
+        run: |
+          ./alpine.sh apk update
+          ./alpine.sh apk add build-base cmake g++ linux-headers git bash icu-dev
+      - name: cmake
+        run: |
+          ./alpine.sh cmake -DADA_BENCHMARKS=ON -B build_for_alpine
+      - name: build
+        run: |
+          ./alpine.sh cmake --build build_for_alpine
+      - name: test
+        run: |
+          ./alpine.sh bash -c "cd build_for_alpine && ctest  ."
\ No newline at end of file
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml
new file mode 100644 (file)
index 0000000..2dcece0
--- /dev/null
@@ -0,0 +1,41 @@
+name: CIFuzz
+
+on:
+  pull_request:
+    branches:
+      - main
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions: read-all
+
+jobs:
+  Fuzzing:
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        sanitizer: [address, undefined, memory]
+    steps:
+      - name: Build Fuzzers (${{ matrix.sanitizer }})
+        id: build
+        uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@c9485cb75e3e39a122264a45ce667d3b57188675 # master
+        with:
+         oss-fuzz-project-name: 'ada-url'
+         language: c++
+         sanitizer: ${{ matrix.sanitizer }}
+      - name: Run Fuzzers (${{ matrix.sanitizer }})
+        uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@c9485cb75e3e39a122264a45ce667d3b57188675 # master
+        with:
+         oss-fuzz-project-name: 'ada-url'
+         language: c++
+         fuzz-seconds: 300
+         sanitizer: ${{ matrix.sanitizer }}
+      - name: Upload Crash
+        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
+        if: steps.build.outcome == 'success'
+        with:
+          name: ${{ matrix.sanitizer }}-artifacts
+          path: ./out/artifacts
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644 (file)
index 0000000..280ed75
--- /dev/null
@@ -0,0 +1,47 @@
+name: "CodeQL"
+
+on:
+  schedule:
+    - cron: '0 0 * * 1'
+
+permissions:
+  contents: read
+  security-events: write
+  pull-requests: read
+  actions: read
+
+jobs:
+  analyze:
+    name: Analyze
+    
+    runs-on: ubuntu-latest
+    
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'cpp', 'python' ]
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+
+      # Initializes the CodeQL tools for scanning.
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.2.5
+        with:
+          languages: ${{ matrix.language }}
+
+      # Autobuild attempts to build any compiled languages  (C/C++, C#, Go, or Java).
+      # If this step fails, then you should remove it and run the build manually (see below)
+      - name: Autobuild
+        uses: github/codeql-action/autobuild@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.2.5
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.2.5
+        with:
+          category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644 (file)
index 0000000..332890b
--- /dev/null
@@ -0,0 +1,14 @@
+name: 'Dependency Review'
+on: [pull_request]
+
+permissions:
+  contents: read
+
+jobs:
+  dependency-review:
+    runs-on: ubuntu-latest
+    steps:
+      - name: 'Checkout Repository'
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: 'Dependency Review'
+        uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # v4.2.5
\ No newline at end of file
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644 (file)
index 0000000..5ed64cd
--- /dev/null
@@ -0,0 +1,35 @@
+name: Doxygen GitHub Pages
+
+on:
+  push:
+    branches:
+      - main
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+    permissions:
+      contents: write
+      pages: write
+      id-token: write
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Install theme
+        run: ./tools/prepare-doxygen.sh
+      - uses: mattnotmitt/doxygen-action@e0c8cd4cd05e28b88e723b25b30188ecf2505b40 # edge
+        with:
+          doxyfile-path: './doxygen'
+      - name: Deploy to GitHub Pages
+        uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3.9.3
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: docs/html
diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml
new file mode 100644 (file)
index 0000000..301dace
--- /dev/null
@@ -0,0 +1,39 @@
+name: emscripten
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+      - uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14
+      - name: Verify
+        run: emcc -v
+      - name: Checkout
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Configure
+        run: emcmake  cmake -B buildwasm -D ADA_TOOLS=OFF
+      - name: Build
+        run: cmake --build buildwasm
+      - name: Test
+        run: ctest --test-dir buildwasm
diff --git a/.github/workflows/lint_and_format_check.yml b/.github/workflows/lint_and_format_check.yml
new file mode 100644 (file)
index 0000000..825bd89
--- /dev/null
@@ -0,0 +1,44 @@
+name: Lint and format
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  lint-and-format:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+
+      - name: Install clang-format
+        run: |
+          sudo apt update && sudo apt install clang-format-15 -y
+          sudo ln -sf /usr/bin/clang-format-15 /usr/bin/clang-format
+
+      - name: Build with Lint and Format Check
+        run: |
+          cmake -B build && cmake --build build
+        env:
+          CXX: clang++-14
+          LINT_AND_FORMAT_CHECK: true
+
+      - uses: chartboost/ruff-action@e18ae971ccee1b2d7bbef113930f00c670b78da4 # v1.0.0
+        name: Lint with Ruff
+        with:
+          version: 0.0.263
diff --git a/.github/workflows/macos_install.yml b/.github/workflows/macos_install.yml
new file mode 100644 (file)
index 0000000..8b5729d
--- /dev/null
@@ -0,0 +1,44 @@
+name: macos (Installation)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: macos-latest
+    strategy:
+      matrix:
+        include:
+          - {shared: ON}
+          - {shared: OFF}
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
+      - name: Prepare
+        run: cmake -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX:PATH=destination -B build
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Install
+        run: cmake --install build
+      - name: Prepare test package
+        run: cmake -DCMAKE_INSTALL_PREFIX:PATH=../../destination -S tests/installation -B buildbabyada
+      - name: Build test package
+        run: cmake --build buildbabyada
+      - name: Run example
+        run: ./buildbabyada/main
diff --git a/.github/workflows/release-script-tests.yml b/.github/workflows/release-script-tests.yml
new file mode 100644 (file)
index 0000000..4365272
--- /dev/null
@@ -0,0 +1,40 @@
+name: Release Script Tests
+
+on:
+  # workflow_call is used to indicate that a workflow can be called by another workflow. 
+  workflow_call:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+jobs:
+  release-script-test:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./tools/release
+
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+
+      - name: Prepare Python
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+        with:
+          cache: 'pip' # caching pip dependencies
+
+      - name: Install dependencies
+        run: pip install -r requirements.txt
+
+      - name: Run tests
+        run: pytest -v
diff --git a/.github/workflows/release_create.yml b/.github/workflows/release_create.yml
new file mode 100644 (file)
index 0000000..07b3b21
--- /dev/null
@@ -0,0 +1,61 @@
+name: Release Create 
+
+on:
+  pull_request:
+    types: [closed]
+
+env:
+  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+jobs:
+  check-release-conditions:
+    runs-on: ubuntu-latest
+    if: |
+      github.event.pull_request.merged == true && 
+      github.event.pull_request.base.ref == 'main' && 
+      startsWith(github.event.pull_request.head.ref, 'release/v') && 
+      startsWith(github.event.pull_request.user.login, 'github-actions')
+  
+    steps:
+      - name: Check release conditions
+        run: | 
+          echo "All conditions have been met!"
+          
+  release-script-test:
+    needs: check-release-conditions
+    uses: ./.github/workflows/release-script-tests.yml
+
+  create-release:
+    permissions:
+      contents: write
+    needs: release-script-test
+    runs-on: ubuntu-latest
+    if: ${{ needs.release-script-test.result == 'success' }}
+      
+    env:
+      NEXT_RELEASE_TAG: ${{ github.event.pull_request.head.ref }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+
+      - name: Prepare Python
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+        with:
+          cache: 'pip' # caching pip dependencies
+      
+      - name: Install dependencies
+        run: pip install -r ./tools/release/requirements.txt
+
+      - name: Extract Tag from branch name
+        run: |
+          NEXT_RELEASE_TAG=$(echo $NEXT_RELEASE_TAG | sed 's/^release\///')
+          echo "NEXT_RELEASE_TAG=${NEXT_RELEASE_TAG}" >> $GITHUB_ENV
+
+      - name: Target release Tag
+        run: echo "New tag $NEXT_RELEASE_TAG"
+
+      - name: Amalgamation
+        run: ./singleheader/amalgamate.py
+      
+      - name: "Create release"
+        run: ./tools/release/create_release.py
diff --git a/.github/workflows/release_prepare.yml b/.github/workflows/release_prepare.yml
new file mode 100644 (file)
index 0000000..8253e84
--- /dev/null
@@ -0,0 +1,57 @@
+name: Release Prepare 
+
+on:
+  workflow_dispatch:
+    inputs:
+      tag:
+        type: string
+        required: true
+        description: "Tag for the next release. Ex.: v5.0.0"
+
+env:
+  NEXT_RELEASE_TAG: ${{ github.event.inputs.tag }}
+  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+jobs:
+  release-script-test:
+    uses: ./.github/workflows/release-script-tests.yml
+
+  prepare-release-and-pull-request:
+    permissions:
+      contents: write
+      pull-requests: write
+    needs: release-script-test
+    runs-on: ubuntu-latest
+    if: ${{ needs.release-script-test.result == 'success' }}
+    env:
+      CXX: clang++-14
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+
+      - name: Prepare Python
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
+        with:
+          cache: 'pip' # caching pip dependencies
+
+      - name: Install dependencies
+        run: pip install -r ./tools/release/requirements.txt
+        
+      - name: Update source code versions
+        run: ./tools/release/update_versions.py
+
+      - name: Ada Build
+        run: cmake -B build && cmake --build build
+      - name: Ada Test
+        run: ctest --output-on-failure --test-dir build
+
+      - name: Create PR with code updates for new release
+        uses: peter-evans/create-pull-request@f3a21bf3404eae73a97f65817ab35f351a1a63fe #v5.0.0
+        with:
+          commit-message: "chore: release ${{ env.NEXT_RELEASE_TAG }}"
+          branch: "release/${{ env.NEXT_RELEASE_TAG }}"
+          title: "chore: release ${{ env.NEXT_RELEASE_TAG }}"
+          token: ${{ env.GITHUB_TOKEN }}
+          body: |
+            This pull PR updates the source code version to ${{ env.NEXT_RELEASE_TAG }}
+          delete-branch: true
+          reviewers: "lemire,anonrig"
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644 (file)
index 0000000..3bf589b
--- /dev/null
@@ -0,0 +1,70 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecard supply-chain security
+on:
+  # For Branch-Protection check. Only the default branch is supported. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+  branch_protection_rule:
+  # To guarantee Maintained check is occasionally updated. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+  schedule:
+    - cron: '0 0 * * 1'
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+  analysis:
+    name: Scorecard analysis
+    runs-on: ubuntu-latest
+    permissions:
+      # Needed to upload the results to code-scanning dashboard.
+      security-events: write
+      # Needed to publish results and get a badge (see publish_results below).
+      id-token: write
+      # Uncomment the permissions below if installing in a private repository.
+      # contents: read
+      # actions: read
+
+    steps:
+      - name: "Checkout code"
+        uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+        with:
+          persist-credentials: false
+
+      - name: "Run analysis"
+        uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
+        with:
+          results_file: results.sarif
+          results_format: sarif
+          # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+          # - you want to enable the Branch-Protection check on a *public* repository, or
+          # - you are installing Scorecard on a *private* repository
+          # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
+          # repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+          # Public repositories:
+          #   - Publish results to OpenSSF REST API for easy access by consumers
+          #   - Allows the repository to include the Scorecard badge.
+          #   - See https://github.com/ossf/scorecard-action#publishing-results.
+          # For private repositories:
+          #   - `publish_results` will always be set to `false`, regardless
+          #     of the value entered here.
+          publish_results: true
+
+      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+      # format to the repository Actions tab.
+      - name: "Upload artifact"
+        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
+        with:
+          name: SARIF file
+          path: results.sarif
+          retention-days: 5
+
+      # Upload the results to GitHub's code scanning dashboard.
+      - name: "Upload to code-scanning"
+        uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # v2.13.4
+        with:
+          sarif_file: results.sarif
\ No newline at end of file
diff --git a/.github/workflows/ubuntu-release.yml b/.github/workflows/ubuntu-release.yml
new file mode 100644 (file)
index 0000000..16a67c4
--- /dev/null
@@ -0,0 +1,40 @@
+name: Ubuntu 22.04 (Release build)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-release-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        cxx: [g++-12, clang++-14]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -G Ninja -B build
+        env:
+          CXX: ${{matrix.cxx}}
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/ubuntu-s390x.yml b/.github/workflows/ubuntu-s390x.yml
new file mode 100644 (file)
index 0000000..be08e63
--- /dev/null
@@ -0,0 +1,43 @@
+name: Ubuntu s390x (GCC 11)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - uses: uraimo/run-on-arch-action@517085f0367c8256bcfa753e3e13e1550af09954 # v2.7.1
+        name: Test
+        id: runcmd
+        with:
+          arch: s390x
+          distro: ubuntu_latest
+          githubToken: ${{ github.token }}
+          install: |
+            apt-get update -q -y
+            apt-get install -y cmake make g++ git
+            apt-get install -y ninja-build
+          run: |
+            cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -B build
+            rm -r -f dependencies
+            cmake --build build -j=2
+            ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/ubuntu-sanitized.yml b/.github/workflows/ubuntu-sanitized.yml
new file mode 100644 (file)
index 0000000..dcd98e4
--- /dev/null
@@ -0,0 +1,40 @@
+name: Ubuntu 22.04 (GCC 12 SANITIZED)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        shared: [ON, OFF]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -DADA_SANITIZE=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build
+        env:
+          CXX: g++-12
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/ubuntu-undef.yml b/.github/workflows/ubuntu-undef.yml
new file mode 100644 (file)
index 0000000..1f582ea
--- /dev/null
@@ -0,0 +1,40 @@
+name: Ubuntu 22.04 (GCC 12 SANITIZE UNDEFINED)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        shared: [ON, OFF]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -D ADA_SANITIZE_UNDEFINED=ON -DADA_DEVELOPMENT_CHECKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build
+        env:
+          CXX: g++-12
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
new file mode 100644 (file)
index 0000000..ae11509
--- /dev/null
@@ -0,0 +1,43 @@
+name: Ubuntu 22.04
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        shared: [ON, OFF]
+        cxx: [g++-12, clang++-14]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -D ADA_BENCHMARKS=ON -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build
+        env:
+          CXX: ${{matrix.cxx}}
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
+      - name: Run default benchmark
+        run: cd build && benchmarks/bench
\ No newline at end of file
diff --git a/.github/workflows/ubuntu_install.yml b/.github/workflows/ubuntu_install.yml
new file mode 100644 (file)
index 0000000..f4acb58
--- /dev/null
@@ -0,0 +1,46 @@
+name: Ubuntu 22.04 (Installation)
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        include:
+          - {shared: ON}
+          - {shared: OFF}
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -G Ninja -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX:PATH=destination -B build
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Install
+        run: cmake --install build
+      - name: Prepare test package
+        run: cmake -DCMAKE_INSTALL_PREFIX:PATH=../../destination -S tests/installation -B buildbabyada
+      - name: Build test package
+        run: cmake --build buildbabyada
+      - name: Run example
+        run: ./buildbabyada/main
diff --git a/.github/workflows/ubuntu_old.yml b/.github/workflows/ubuntu_old.yml
new file mode 100644 (file)
index 0000000..cddebab
--- /dev/null
@@ -0,0 +1,41 @@
+name: Ubuntu 20.04
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        shared: [ON, OFF]
+        cxx: [g++-9, clang++-10]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build
+        env:
+          CXX: ${{matrix.cxx}}
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/ubuntu_pedantic.yml b/.github/workflows/ubuntu_pedantic.yml
new file mode 100644 (file)
index 0000000..92fc3b9
--- /dev/null
@@ -0,0 +1,41 @@
+name: Ubuntu 22.04 (GCC 12) Fails On Compiler Warnings
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ubuntu-build:
+    runs-on: ubuntu-22.04
+    strategy:
+      matrix:
+        shared: [ON, OFF]
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Setup Ninja
+        run: sudo apt-get install ninja-build
+      - name: Prepare
+        run: cmake -DBUILD_SHARED_LIBS=${{matrix.shared}} -G Ninja -B build
+        env:
+          CXX: g++-12
+          CXXFLAGS: -Werror
+      - name: Build
+        run: cmake --build build -j=2
+      - name: Test
+        run: ctest --output-on-failure --test-dir build
diff --git a/.github/workflows/visual_studio.yml b/.github/workflows/visual_studio.yml
new file mode 100644 (file)
index 0000000..631ab99
--- /dev/null
@@ -0,0 +1,45 @@
+name: VS17-CI
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ci:
+    name: windows-vs17
+    runs-on: windows-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {gen: Visual Studio 17 2022, arch: x64, devchecks: OFF, shared: OFF, config: Release}
+          - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON, shared: OFF, config: Debug}
+          - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON, shared: ON, config: Debug}
+          - {gen: Visual Studio 17 2022, arch: Win32, devchecks: ON, shared: OFF, config: Debug}
+          - {gen: Visual Studio 17 2022, arch: Win32, devchecks: ON, shared: ON, config: Debug}
+    steps:
+    - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+    - name: Configure
+      run: |
+        cmake -DADA_DEVELOPMENT_CHECKS="${{matrix.devchecks}}" -G "${{matrix.gen}}" -A ${{matrix.arch}} -DBUILD_SHARED_LIBS=${{matrix.shared}} -B build
+    - name: Build
+      run: cmake --build build --config "${{matrix.config}}" --verbose
+    - name: Run  tests
+      working-directory: build
+      run: ctest -C "${{matrix.config}}"  --output-on-failure
diff --git a/.github/workflows/visual_studio_clang.yml b/.github/workflows/visual_studio_clang.yml
new file mode 100644 (file)
index 0000000..b0a21a5
--- /dev/null
@@ -0,0 +1,46 @@
+name: VS17-clang-CI
+
+on:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+  push:
+    branches:
+      - main
+    paths-ignore:
+      - '**.md'
+      - 'docs/**'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  ci:
+    name: windows-vs17
+    runs-on: windows-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        include:
+          - {gen: Visual Studio 17 2022, arch: x64, devchecks: ON}
+    steps:
+    - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+    - name: Configure
+      run: |
+        cmake -DADA_DEVELOPMENT_CHECKS="${{matrix.devchecks}}" -G "${{matrix.gen}}" -A ${{matrix.arch}} -T ClangCL -B build
+    - name: Build Debug
+      run: cmake --build build --config Debug --verbose
+    - name: Run Debug tests
+      working-directory: build
+      run: ctest -C Debug  --output-on-failure
+    - name: Build Release
+      run: cmake --build build --config Release --verbose
+    - name: Run Release tests
+      working-directory: build
+      run: ctest -C Release  --output-on-failure
diff --git a/.github/workflows/wpt-updater.yml b/.github/workflows/wpt-updater.yml
new file mode 100644 (file)
index 0000000..53d7bf3
--- /dev/null
@@ -0,0 +1,40 @@
+name: Update WPT
+
+on:
+  schedule:
+    - cron: '0 0 * * *'
+
+env:
+  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+concurrency:
+  group: wpt-updater
+  cancel-in-progress: true
+
+permissions:
+  contents: read
+
+jobs:
+  issue:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v3.6.0
+      - name: Fetch tests
+        run: tools/update-wpt.sh
+      - name: Open pull request
+        uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 #v5.0.1
+        with:
+          token: ${{ secrets.GH_PAT }}
+          commit-message: "test: update web platform tests"
+          branch: "automatic-update-wpt"
+          title: "Update web platform tests"
+          body: |
+            This is an automated pull request for updating the WPT.
+            - [Web Platform Tests](https://github.com/web-platform-tests/wpt/tree/master/url)
+            - [Commit History](https://github.com/web-platform-tests/wpt/commits/master/url/resources)
+            cc @anonrig @lemire
+          team-reviewers: core
+          delete-branch: true
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..bb1f23e
--- /dev/null
@@ -0,0 +1,27 @@
+# common build directory
+build
+*-build-*
+
+# Python cache
+__pycache__
+venv
+
+cmake-build-debug
+
+.cache
+docs/html
+docs/theme
+
+# Generated using only the Github workflow
+benchmark_result.json
+
+singleheader/ada.h
+singleheader/ada_c.h
+singleheader/ada.cpp
+singleheader/singleheader.zip
+
+benchmarks/competitors/servo-url/debug
+benchmarks/competitors/servo-url/target
+
+#ignore VScode
+.vscode/
\ No newline at end of file
diff --git a/.python-version b/.python-version
new file mode 100644 (file)
index 0000000..e4fba21
--- /dev/null
@@ -0,0 +1 @@
+3.12
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..278c23a
--- /dev/null
@@ -0,0 +1,183 @@
+cmake_minimum_required(VERSION 3.16)
+
+project(ada
+  DESCRIPTION "Fast spec-compliant URL parser"
+  LANGUAGES C CXX
+  VERSION 2.7.8
+)
+set(CMAKE_CXX_STANDARD 17)
+set(ADA_LIB_VERSION "2.7.8" CACHE STRING "ada library version")
+set(ADA_LIB_SOVERSION "2" CACHE STRING "ada library soversion")
+
+include(GNUInstallDirs)
+
+include (cmake/clang-format.cmake)
+
+include(CTest)
+include(cmake/ada-flags.cmake)
+
+set(ADA_SOURCE_DIR src)
+
+add_subdirectory(src)
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/scripts/cmake)
+
+option(ADA_BENCHMARKS "Build benchmarks" OFF)
+option(ADA_TESTING "Build tests" ${BUILD_TESTING})
+
+# There are cases where when embedding ada as a dependency for other CMake 
+# projects as submodules or subdirectories (via FetchContent) can lead to
+# errors due to CPM, so this is here to support disabling all the testing
+# and tooling for ada if one only wishes to use the ada library.
+if(ADA_TESTING OR ADA_BENCHMARKS OR ADA_TOOLS)
+  include(cmake/CPM.cmake)
+  # CPM requires git as an implicit dependency
+  find_package(Git QUIET)
+  # We use googletest in the tests
+  if(Git_FOUND AND ADA_TESTING)
+    CPMAddPackage(
+      NAME GTest
+      GITHUB_REPOSITORY google/googletest
+      VERSION 1.14.0
+      OPTIONS  "BUILD_GMOCK OFF" "INSTALL_GTEST OFF"
+    )
+  endif()
+  # We use simdjson in both the benchmarks and tests
+  if(Git_FOUND AND (ADA_TESTING OR ADA_BENCHMARKS))
+    CPMAddPackage("gh:simdjson/simdjson@3.3.0")
+  endif()
+  # We use Google Benchmark, but it does not build under several 32-bit systems.
+  if(Git_FOUND AND ADA_BENCHMARKS AND (CMAKE_SIZEOF_VOID_P EQUAL 8))
+    CPMAddPackage(
+      NAME benchmark
+      GITHUB_REPOSITORY google/benchmark
+      GIT_TAG f91b6b4
+      OPTIONS "BENCHMARK_ENABLE_TESTING OFF"
+              "BENCHMARK_ENABLE_INSTALL OFF"
+              "BENCHMARK_ENABLE_WERROR OFF"
+  
+    )
+  endif()
+  
+  if (ADA_TESTING AND NOT EMSCRIPTEN)
+    if(Git_FOUND)
+      message(STATUS "The tests are enabled.")
+      add_subdirectory(tests)
+    else()
+      message(STATUS "The tests are disabled because git was not found.")
+    endif()
+  else()
+    if(is_top_project)
+      message(STATUS "The tests are disabled.")
+    endif()
+  endif(ADA_TESTING AND NOT EMSCRIPTEN)
+  
+  If(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
+    if(Git_FOUND)
+      message(STATUS "Ada benchmarks enabled.")
+      add_subdirectory(benchmarks)
+    else()
+      message(STATUS "The benchmarks are disabled because git was not found.")
+    endif()
+  else(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
+    if(is_top_project)
+      message(STATUS "Ada benchmarks disabled. Set ADA_BENCHMARKS=ON to enable them.")
+    endif()
+  endif(ADA_BENCHMARKS AND NOT EMSCRIPTEN)
+  
+  if (ADA_TESTING AND EMSCRIPTEN)
+    add_subdirectory(tests/wasm)
+  endif(ADA_TESTING AND EMSCRIPTEN)
+endif()
+
+
+add_library(ada::ada ALIAS ada)
+
+set_target_properties(
+  ada PROPERTIES
+  VERSION "${ADA_LIB_VERSION}"
+  SOVERSION "${ADA_LIB_SOVERSION}"
+  WINDOWS_EXPORT_ALL_SYMBOLS YES
+)
+
+include(CMakePackageConfigHelpers)
+include(GNUInstallDirs)
+
+if(NOT ADA_COVERAGE AND NOT EMSCRIPTEN)
+  add_subdirectory(singleheader)
+endif()
+
+if(ADA_TOOLS)
+  if(Git_FOUND)
+    add_subdirectory(tools)
+  else()
+    message(STATUS "The tools are disabled because git was not found.")
+  endif()
+endif()
+
+install(
+  FILES include/ada.h include/ada_c.h
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+  COMPONENT ada_development
+)
+
+install(
+  DIRECTORY include/ada
+  DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+  COMPONENT ada_development
+)
+
+install(
+  TARGETS ada
+  EXPORT ada_targets
+  RUNTIME COMPONENT ada_runtime
+  LIBRARY COMPONENT ada_runtime
+  NAMELINK_COMPONENT ada_development
+  ARCHIVE COMPONENT ada_development
+  INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+)
+
+configure_file(cmake/ada-config.cmake.in ada-config.cmake @ONLY)
+
+write_basic_package_version_file(
+  ada-config-version.cmake
+  COMPATIBILITY SameMinorVersion
+)
+
+set(
+  ADA_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/ada"
+  CACHE STRING "CMake package config location relative to the install prefix"
+)
+mark_as_advanced(ADA_INSTALL_CMAKEDIR)
+
+install(
+  FILES
+  "${PROJECT_BINARY_DIR}/ada-config.cmake"
+  "${PROJECT_BINARY_DIR}/ada-config-version.cmake"
+  DESTINATION "${ADA_INSTALL_CMAKEDIR}"
+  COMPONENT ada_development
+)
+
+install(
+  EXPORT ada_targets
+  NAMESPACE ada::
+  DESTINATION "${ADA_INSTALL_CMAKEDIR}"
+  COMPONENT ada_development
+)
+
+install(
+  EXPORT ada_targets
+  NAMESPACE ada::
+  DESTINATION "${ADA_INSTALL_CMAKEDIR}"
+  COMPONENT example_development
+)
+
+if(is_top_project)
+  set(CPACK_PACKAGE_VENDOR "Ada Authors")
+  set(CPACK_PACKAGE_CONTACT "yagiz@nizipli.com")
+  set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE-MIT")
+  set(CPACK_RPM_PACKAGE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE-MIT")
+  set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.md")
+  set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
+  include(CPack)
+endif()
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..4b1957e
--- /dev/null
@@ -0,0 +1,12 @@
+FROM debian:12-slim@sha256:7802002798b0e351323ed2357ae6dc5a8c4d0a05a57e7f4d8f97136151d3d603
+
+RUN apt-get update && apt-get install -y \
+    apt-transport-https \
+    gcc \
+    clang \
+    clang-tools \
+    cmake
+
+WORKDIR /repo
+
+CMD ["bash", "-c", "cmake -B build && cmake --build build && cd build && ctest --output-on-failure"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..1204b0a
--- /dev/null
@@ -0,0 +1,201 @@
+              Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2023 Yagiz Nizipli and Daniel Lemire
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644 (file)
index 0000000..bd2abac
--- /dev/null
@@ -0,0 +1,18 @@
+Copyright 2023 Yagiz Nizipli and Daniel Lemire
+
+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
new file mode 100644 (file)
index 0000000..24a8465
--- /dev/null
+++ b/README.md
@@ -0,0 +1,325 @@
+# Ada
+[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ada-url/ada/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ada-url/ada)
+[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7085/badge)](https://bestpractices.coreinfrastructure.org/projects/7085)
+[![Ubuntu 22.04](https://github.com/ada-url/ada/actions/workflows/ubuntu.yml/badge.svg)](https://github.com/ada-url/ada/actions/workflows/ubuntu.yml)
+[![VS17-CI](https://github.com/ada-url/ada/actions/workflows/visual_studio.yml/badge.svg)](https://github.com/ada-url/ada/actions/workflows/visual_studio.yml)
+[![VS17-clang-CI](https://github.com/ada-url/ada/actions/workflows/visual_studio_clang.yml/badge.svg)](https://github.com/ada-url/ada/actions/workflows/visual_studio_clang.yml)
+[![Ubuntu s390x (GCC 11)](https://github.com/ada-url/ada/actions/workflows/ubuntu-s390x.yml/badge.svg)](https://github.com/ada-url/ada/actions/workflows/ubuntu-s390x.yml)
+
+Ada is a fast and spec-compliant URL parser written in C++.
+Specification for URL parser can be found from the
+[WHATWG](https://url.spec.whatwg.org/#url-parsing) website.
+
+The Ada library passes the full range of tests from the specification,
+across a wide range of platforms (e.g., Windows, Linux, macOS). It fully
+supports the relevant [Unicode Technical Standard](https://www.unicode.org/reports/tr46/#ToUnicode).
+
+A common use of a URL parser is to take a URL string and normalize it.
+The WHATWG URL specification has been adopted by most browsers.  Other tools, such as curl and many
+standard libraries, follow the RFC 3986. The following table illustrates possible differences in practice
+(encoding of the host, encoding of the path):
+
+| string source | string value |
+|:--------------|:--------------|
+| input string | https://www.7‑Eleven.com/Home/Privacy/Montréal |
+| ada's normalized string | https://www.xn--7eleven-506c.com/Home/Privacy/Montr%C3%A9al |
+| curl 7.87 | (returns the original unchanged) |
+
+### Requirements
+
+The project is otherwise self-contained and it has no dependency.
+A recent C++ compiler supporting C++17. We test GCC 9 or better, LLVM 10 or better and Microsoft Visual Studio 2022.
+
+## Ada is fast.
+
+On a benchmark where we need to validate and normalize [thousands URLs found
+on popular websites](https://github.com/ada-url/url-various-datasets/tree/main/top100),
+we find that ada can be several times faster than popular competitors (system: Apple MacBook 2022
+with LLVM 14).
+
+
+```
+      ada ▏  188 ns/URL ███▏
+servo url ▏  664 ns/URL ███████████▎
+     CURL ▏ 1471 ns/URL █████████████████████████
+```
+
+Ada has improved the performance of the popular JavaScript environment Node.js:
+
+> Since Node.js 18, a new URL parser dependency was added to Node.js — Ada. This addition bumped the Node.js performance when parsing URLs to a new level. Some results could reach up to an improvement of **400%**. ([State of Node.js Performance 2023](https://blog.rafaelgss.dev/state-of-nodejs-performance-2023))
+
+The Ada library is used by important systems besides Node.js such as Redpanda and Cloudflare Workers.
+
+
+
+[![the ada library](http://img.youtube.com/vi/tQ-6OWRDsZg/0.jpg)](https://www.youtube.com/watch?v=tQ-6OWRDsZg)<br />
+
+## Quick Start
+
+
+
+Linux or macOS users might follow the following instructions if they have a recent C++ compiler installed and a standard utility (`wget`)
+
+
+1. Pull the library in a directory
+   ```
+   wget https://github.com/ada-url/ada/releases/download/v2.6.10/ada.cpp
+   wget https://github.com/ada-url/ada/releases/download/v2.6.10/ada.h
+   ```
+2. Create a new file named `demo.cpp` with this content:
+   ```C++
+    #include "ada.cpp"
+    #include "ada.h"
+    #include <iostream>
+
+    int main(int, char *[]) {
+      auto url = ada::parse<ada::url>("https://www.google.com");
+      if (!url) {
+        std::cout << "failure" << std::endl;
+        return EXIT_FAILURE;
+      }
+      url->set_protocol("http");
+      std::cout << url->get_protocol() << std::endl;
+      std::cout << url->get_host() << std::endl;
+      return EXIT_SUCCESS;
+    }
+   ```
+2. Compile
+   ```
+   c++ -std=c++17 -o demo demo.cpp
+   ```
+3. `./demo`
+
+   ```
+   http:
+   www.google.com
+   ```
+
+## Bindings of Ada
+
+We provide clients for different programming languages through our C API.
+
+- [Rust](https://github.com/ada-url/rust): Rust bindings for Ada
+- [Go](https://github.com/ada-url/goada): Go bindings for Ada
+- [Python](https://github.com/ada-url/python): Python bindings for Ada
+- [R](https://github.com/schochastics/adaR): R wrapper for Ada
+
+## Usage
+
+Ada supports two types of URL instances, `ada::url` and `ada::url_aggregator`. The usage is
+the same in either case: we have an parsing function template `ada::parse` which can return
+either a result of type `ada::result<ada::url>` or of type `ada::result<ada::url_aggregator>`
+depending on your needs. The `ada::url_aggregator` class is smaller and it is backed by a precomputed
+serialized URL string. The `ada::url` class is made of several separate strings for the various
+components (path, host, and so forth).
+
+### Parsing & Validation
+
+- Parse and validate a URL from an ASCII or UTF-8 string
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+if (url) { /* URL is valid */ }
+```
+
+After calling 'parse', you *must* check that the result is valid before
+accessing it when you are not sure that it will succeed. The following
+code is unsafe:
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("some bad url");
+url->get_href();
+```
+
+You should do...
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("some bad url");
+if(url) {
+  // next line is now safe:
+  url->get_href();
+} else {
+  // report a parsing failure
+}
+```
+
+For simplicity, in the examples below, we skip the check because
+we know that parsing succeeds.
+
+### Examples
+
+- Get/Update credentials
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_username("username");
+url->set_password("password");
+// ada->get_href() will return "https://username:password@www.google.com/"
+```
+
+- Get/Update Protocol
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_protocol("wss");
+// url->get_protocol() will return "wss:"
+// url->get_href() will return "wss://www.google.com/"
+```
+
+- Get/Update host
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_host("github.com");
+// url->get_host() will return "github.com"
+// you can use `url.set_hostname` depending on your usage.
+```
+
+- Get/Update port
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_port("8080");
+// url->get_port() will return "8080"
+```
+
+- Get/Update pathname
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_pathname("/my-super-long-path")
+// url->get_pathname() will return "/my-super-long-path"
+```
+
+- Get/Update search/query
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_search("target=self");
+// url->get_search() will return "?target=self"
+```
+
+- Get/Update hash/fragment
+
+```cpp
+ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>("https://www.google.com");
+url->set_hash("is-this-the-real-life");
+// url->get_hash() will return "#is-this-the-real-life"
+```
+For more information about command-line options, please refer to the [CLI documentation](docs/cli.md).
+
+- URL search params
+
+```cpp
+ada::url_search_params search_params("a=b&c=d&e=f");
+search_params.append("g=h");
+
+search_params.get("g");  // will return "h"
+
+auto keys = search_params.get_keys();
+while (keys.has_next()) {
+  auto key = keys.next();  // "a", "c", "e", "g"
+}
+```
+
+### C wrapper
+
+See the file `include/ada_c.h` for our C interface. We expect ASCII or UTF-8 strings.
+
+```C
+#include "ada_c.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+static void ada_print(ada_string string) {
+  printf("%.*s\n", (int)string.length, string.data);
+}
+
+int main(int c, char *arg[] ) {
+  ada_url url = ada_parse("https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists");
+  if(!ada_is_valid(url)) { puts("failure"); return EXIT_FAILURE; }
+  ada_print(ada_get_href(url)); // prints https://username:password@host:8080/pathname?query=true#hash-exists
+  ada_print(ada_get_protocol(url)); // prints https:
+  ada_print(ada_get_username(url)); // prints username
+  ada_set_href(url, "https://www.yagiz.co");
+  if(!ada_is_valid(url)) { puts("failure"); return EXIT_FAILURE; }
+  ada_set_hash(url, "new-hash");
+  ada_set_hostname(url, "new-host");
+  ada_set_host(url, "changed-host:9090");
+  ada_set_pathname(url, "new-pathname");
+  ada_set_search(url, "new-search");
+  ada_set_protocol(url, "wss");
+  ada_print(ada_get_href(url)); // will print wss://changed-host:9090/new-pathname?new-search#new-hash
+
+  // Manipulating search params
+  ada_string search = ada_get_search(url);
+  ada_url_search_params search_params =
+      ada_parse_search_params(search.data, search.length);
+  ada_search_params_append(search_params, "a", 1, "b", 1);
+  ada_owned_string result = ada_search_params_to_string(search_params);
+  ada_set_search(url, result.data, result.length);
+  ada_free_owned_string(result);
+  ada_free_search_params(search_params);
+
+  ada_free(url);
+  return EXIT_SUCCESS;
+}
+```
+
+When linking against the ada library from C++, be minding that ada requires access to the standard
+C++ library. E.g., you may link with the C++ compiler.
+
+E.g., if you grab our single-header C++ files (`ada.cpp` and `ada.h`), as well as the C header (`ada_c.h`),
+you can often compile a C program (`demo.c`) as follows under Linux/macOS systems:
+
+```
+c++ -c ada.cpp -std=c++17
+cc -c demo.c
+c++ demo.o ada.o -o cdemo
+./cdemo
+```
+
+### CMake dependency
+
+See the file `tests/installation/CMakeLists.txt` for an example of how you might use ada from your own
+CMake project, after having installed ada on your system.
+
+## Installation
+
+### Homebrew
+
+Ada is available through [Homebrew](https://formulae.brew.sh/formula/ada-url#default).
+You can install Ada using `brew install ada-url`.
+
+## Contributing
+
+### Building
+
+Ada uses cmake as a build system. It's recommended you to run the following commands to build it locally.
+
+- **Build**: `cmake -B build && cmake --build build`
+- **Test**: `ctest --output-on-failure --test-dir build`
+
+Windows users need additional flags to specify the build configuration, e.g. `--config Release`.
+
+The project can also be built via docker using default docker file of repository with following commands.
+
+`docker build -t ada-builder . && docker run --rm -it -v ${PWD}:/repo ada-builder`
+
+### Amalgamation
+
+You may amalgamate all source files into only two files (`ada.h` and `ada.cpp`) by typing executing the Python
+3 script `singleheader/amalgamate.py`. By default, the files are created in the `singleheader` directory.
+
+### License
+
+This code is made available under the Apache License 2.0 as well as the MIT license.
+
+Our tests include third-party code and data. The benchmarking code includes third-party code: it is provided for research purposes only and not part of the library.
+
+### Further reading
+
+
+* Yagiz Nizipli, Daniel Lemire, [Parsing Millions of URLs per Second](https://doi.org/10.1002/spe.3296), Software: Practice and Experience 54(5) May 2024.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644 (file)
index 0000000..255f509
--- /dev/null
@@ -0,0 +1,8 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+Please use the following contact information for reporting a vulnerability:
+
+- [Daniel Lemire](https://github.com/lemire) - daniel@lemire.me
+- [Yagiz Nizipli](https://github.com/anonrig) - yagiz@nizipli.com
diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt
new file mode 100644 (file)
index 0000000..886ce52
--- /dev/null
@@ -0,0 +1,300 @@
+# bench_search_params
+add_executable(bench_search_params bench_search_params.cpp)
+target_link_libraries(bench_search_params PRIVATE ada)
+
+# Bench
+add_executable(wpt_bench wpt_bench.cpp)
+target_link_libraries(wpt_bench PRIVATE ada)
+target_link_libraries(wpt_bench PRIVATE simdjson)
+target_include_directories(wpt_bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+target_include_directories(wpt_bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
+
+# Bench
+add_executable(bench bench.cpp)
+target_link_libraries(bench PRIVATE ada)
+target_include_directories(bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+target_include_directories(bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
+
+# Benchdata
+CPMAddPackage("gh:ada-url/url-dataset#9749b92c13e970e70409948fa862461191504ccc")
+add_executable(benchdata bench.cpp)
+target_link_libraries(benchdata PRIVATE ada)
+target_include_directories(benchdata PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+target_include_directories(benchdata PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
+target_compile_definitions(benchdata PRIVATE ADA_URL_FILE="${url-dataset_SOURCE_DIR}/out.txt")
+
+
+# BBC Bench
+add_executable(bbc_bench bbc_bench.cpp)
+target_link_libraries(bbc_bench PRIVATE ada)
+target_include_directories(bbc_bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+target_include_directories(bbc_bench PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
+
+# Percent Encode
+add_executable(percent_encode percent_encode.cpp)
+target_link_libraries(percent_encode PRIVATE ada)
+target_include_directories(percent_encode PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+target_include_directories(percent_encode PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/benchmarks>")
+if(MSVC AND BUILD_SHARED_LIBS)
+  # Copy the ada dll into the directory
+  add_custom_command(TARGET percent_encode POST_BUILD        # Adds a post-build event
+    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake -E copy_if_different..."
+        "$<TARGET_FILE:ada>"      # <--this is in-file
+        "$<TARGET_FILE_DIR:percent_encode>")                 # <--this is out-file path
+endif()
+
+if(CMAKE_SYSTEM_NAME MATCHES "Linux")
+    # The model_bench program requires accurate/low-overhead performance counters.
+    # We only have such support under Linux.
+    add_executable(model_bench model_bench.cpp)
+    target_link_libraries(model_bench PRIVATE ada)
+    target_compile_definitions(model_bench PRIVATE ADA_URL_FILE="${url-dataset_SOURCE_DIR}/out.txt")
+endif()
+
+target_link_libraries(wpt_bench PRIVATE benchmark::benchmark)
+target_link_libraries(bench PRIVATE benchmark::benchmark)
+target_link_libraries(benchdata PRIVATE benchmark::benchmark)
+target_link_libraries(bbc_bench PRIVATE benchmark::benchmark)
+target_link_libraries(percent_encode PRIVATE benchmark::benchmark)
+target_link_libraries(bench_search_params PRIVATE benchmark::benchmark)
+
+option(ADA_COMPETITION "Whether to install various competitors." OFF)
+
+# We only build url_whatwg if ICU is found, so we need to make
+# finding ICU easy.
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+  message(STATUS "Apple system detected.")
+  # People who run macOS often use brew.
+  if(EXISTS /opt/homebrew/opt/icu4c)
+    message(STATUS "icu is provided by homebrew at /opt/homebrew/opt/icu4c.")
+    ## This is a bit awkward, but it is a lot better than asking the
+    ## user to figure that out.
+    list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/icu4c/include")
+    list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/opt/icu4c/lib")
+  elseif(EXISTS /usr/local/opt/icu4c)
+    message(STATUS "icu is provided by homebrew at /usr/local/opt/icu4c.")
+    list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/icu4c/include")
+    list(APPEND CMAKE_LIBRARY_PATH "/usr/local/opt/icu4c/lib")
+  endif()
+endif()
+
+find_package(ICU COMPONENTS uc i18n)
+### If the user does not have ICU, let us help them with instructions:
+if(NOT ICU_FOUND)
+  if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+      if(EXISTS /opt/homebrew)
+        message(STATUS "Under macOS, you may install ICU with brew, using 'brew install icu4c'.")
+      else()
+        message(STATUS "Under macOS, you should install brew (see https://brew.sh) and then icu4c ('brew install icu4c').")
+      endif()
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+      message(STATUS "Under Linux, you may be able to install ICU with a command such as 'apt-get install libicu-dev'." )
+  endif()
+endif(NOT ICU_FOUND)
+
+if(ICU_FOUND)
+    CPMAddPackage(
+      NAME url_whatwg
+      GITHUB_REPOSITORY rmisev/url_whatwg
+      GIT_TAG 72bcabf
+      OPTIONS  "URL_BUILD_TESTS OFF" "URL_USE_LIBS OFF"
+    )
+    add_library(url_whatwg_lib STATIC "${url_whatwg_SOURCE_DIR}/src/url.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url_idna.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url_ip.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url_percent_encode.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url_search_params.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url_utf.cpp"
+      "${url_whatwg_SOURCE_DIR}/src/url.cpp")
+    target_include_directories(url_whatwg_lib PUBLIC "${url_whatwg_SOURCE_DIR}/include")
+    target_link_libraries(url_whatwg_lib PRIVATE ICU::uc ICU::i18n)
+
+
+    target_link_libraries(bench PRIVATE url_whatwg_lib)
+    target_link_libraries(benchdata PRIVATE url_whatwg_lib)
+    target_link_libraries(bbc_bench PRIVATE url_whatwg_lib)
+    target_link_libraries(wpt_bench PRIVATE url_whatwg_lib)
+
+    target_include_directories(bench PUBLIC "${url_whatwg_SOURCE_DIR}")
+    target_include_directories(benchdata PUBLIC "${url_whatwg_SOURCE_DIR}")
+    target_include_directories(bbc_bench PUBLIC "${url_whatwg_SOURCE_DIR}")
+    target_include_directories(wpt_bench PUBLIC "${url_whatwg_SOURCE_DIR}")
+
+    target_compile_definitions(bench PRIVATE ADA_url_whatwg_ENABLED=1)
+    target_compile_definitions(benchdata PRIVATE ADA_url_whatwg_ENABLED=1)
+    target_compile_definitions(bbc_bench PRIVATE ADA_url_whatwg_ENABLED=1)
+    target_compile_definitions(wpt_bench PRIVATE ADA_url_whatwg_ENABLED=1)
+
+endif(ICU_FOUND)
+
+if(ADA_COMPETITION)
+    # URI Parser
+    CPMAddPackage(
+      NAME uriparser
+      GITHUB_REPOSITORY uriparser/uriparser
+      GIT_TAG 634b678
+      OPTIONS "URIPARSER_BUILD_TESTS OFF" "URIPARSER_BUILD_DOCS OFF"
+    )
+    target_link_libraries(bench PRIVATE uriparser)
+    target_link_libraries(bbc_bench PRIVATE uriparser)
+    # URL Parser
+    CPMAddPackage(
+      NAME urlparser
+      GITHUB_REPOSITORY netmindms/urlparser
+      GIT_TAG 69c09ed
+    )
+    add_library(urlparser STATIC "${urlparser_SOURCE_DIR}/src/EdUrlParser.cpp")
+    target_include_directories(urlparser PUBLIC "${urlparser_SOURCE_DIR}/src")
+    target_link_libraries(bench PRIVATE urlparser)
+    target_link_libraries(bbc_bench PRIVATE urlparser)
+
+    # HTTP Parser
+    CPMAddPackage(
+      NAME httpparser
+      GITHUB_REPOSITORY nodejs/http-parser
+      VERSION 2.9.4
+    )
+    add_library(httpparser STATIC "${httpparser_SOURCE_DIR}/http_parser.c")
+    set_source_files_properties("${httpparser_SOURCE_DIR}/http_parser.c" PROPERTIES LANGUAGE C)
+    target_include_directories(httpparser PUBLIC "${httpparser_SOURCE_DIR}")
+    target_link_libraries(bench PRIVATE httpparser)
+    target_link_libraries(bbc_bench PRIVATE httpparser)
+
+
+    target_compile_definitions(bench PRIVATE ADA_VARIOUS_COMPETITION_ENABLED=1)
+    target_compile_definitions(bbc_bench PRIVATE ADA_VARIOUS_COMPETITION_ENABLED=1)
+endif(ADA_COMPETITION)
+
+# CURL
+find_package(CURL)
+if(CURL_FOUND)
+    message(STATUS "curl version " ${CURL_VERSION_STRING})
+    if (CURL_VERSION_STRING VERSION_LESS "7.62.0")
+        message(STATUS "curl is too old, we need version 7.62.0 or better")
+    else()
+        include_directories(${CURL_INCLUDE_DIRS})
+        if(NOT CURL_LIBRARIES)
+            target_link_libraries(bench PRIVATE CURL::libcurl)
+            target_link_libraries(benchdata PRIVATE CURL::libcurl)
+            target_link_libraries(bbc_bench PRIVATE CURL::libcurl)
+        else()
+            target_link_libraries(bench PRIVATE ${CURL_LIBRARIES})
+            target_link_libraries(benchdata PRIVATE ${CURL_LIBRARIES})
+            target_link_libraries(bbc_bench PRIVATE ${CURL_LIBRARIES})
+        endif()
+        target_compile_definitions(bench PRIVATE ADA_CURL_ENABLED=1)
+        target_compile_definitions(benchdata PRIVATE ADA_CURL_ENABLED=1)
+        target_compile_definitions(bbc_bench PRIVATE ADA_CURL_ENABLED=1)
+    endif()
+else(CURL_FOUND)
+    message(STATUS "Curl not found! Please install the curl library.")
+endif(CURL_FOUND)
+
+option(ADA_BOOST_URL "Whether to install boost URL." OFF)
+
+message(STATUS "Compiler is " ${CMAKE_CXX_COMPILER_ID})
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+message(STATUS "Compiler version " ${CMAKE_CXX_COMPILER_VERSION})
+
+if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9)
+message(STATUS "Compiler is too old, disabling boost url.")
+SET(ADA_BOOST_URL OFF CACHE BOOL "Whether to install boost URL." FORCE)
+endif()
+endif()
+
+# Boost
+if(ADA_BOOST_URL)
+find_package(
+    Boost 1.80
+    COMPONENTS system
+)
+endif(ADA_BOOST_URL)
+
+if(Boost_FOUND)
+    CPMAddPackage(
+      NAME boost_url
+      GITHUB_REPOSITORY  boostorg/url
+      GIT_TAG boost-1.81.0
+    )
+    add_library(boost_url INTERFACE)
+    target_include_directories(boost_url INTERFACE
+           "${boost_url_SOURCE_DIR}/include")
+
+    target_link_libraries(bench PRIVATE Boost::system)
+    target_link_libraries(bench PRIVATE boost_url)
+    target_compile_definitions(bench PRIVATE ADA_BOOST_ENABLED=1)
+
+    target_link_libraries(benchdata PRIVATE Boost::system)
+    target_link_libraries(benchdata PRIVATE boost_url)
+    target_compile_definitions(benchdata PRIVATE ADA_BOOST_ENABLED=1)
+
+    target_link_libraries(bbc_bench PRIVATE Boost::system)
+    target_link_libraries(bbc_bench PRIVATE boost_url)
+    target_compile_definitions(bbc_bench PRIVATE ADA_BOOST_ENABLED=1)
+else(Boost_FOUND)
+if(ADA_BOOST_URL)
+    message(STATUS "Boost 1.80 or better was not found, please install it for benchmarking purposes.")
+endif(ADA_BOOST_URL)
+endif(Boost_FOUND)
+
+# Zuri
+find_package(ZURI QUIET)
+if(ZURI_FOUND)
+       message(STATUS "Zuri found")
+       target_link_libraries(bench PRIVATE zuri)
+       target_link_libraries(benchdata PRIVATE zuri)
+       target_link_libraries(bbc_bench PRIVATE zuri)
+       target_compile_definitions(bench PRIVATE ADA_ZURI_ENABLED=1)
+       target_compile_definitions(benchdata PRIVATE ADA_ZURI_ENABLED=1)
+       target_compile_definitions(bbc_bench PRIVATE ADA_ZURI_ENABLED=1)
+else(ZURI_FOUND)
+    message(STATUS "Zuri not found! Please install to include in benchmark.")
+endif(ZURI_FOUND)
+
+
+# We want the check whether Rust is available before trying to build a crate.
+CPMAddPackage(
+  NAME corrosion
+  GITHUB_REPOSITORY corrosion-rs/corrosion
+  VERSION 0.4.4
+  DOWNLOAD_ONLY ON
+  OPTIONS "Rust_FIND_QUIETLY OFF"
+)
+include("${corrosion_SOURCE_DIR}/cmake/FindRust.cmake")
+
+
+if(RUST_FOUND)
+  message(STATUS "Rust found: " ${Rust_VERSION} )
+  add_subdirectory("${corrosion_SOURCE_DIR}" "${PROJECT_BINARY_DIR}/_deps/corrosion" EXCLUDE_FROM_ALL)
+  # Important: we want to build in release mode!
+  corrosion_import_crate(MANIFEST_PATH "competitors/servo-url/Cargo.toml" NO_LINKER_OVERRIDE PROFILE release)
+
+  target_link_libraries(bench PRIVATE servo-url)
+  target_compile_definitions(bench PRIVATE ADA_RUST_VERSION="${Rust_VERSION}")
+
+  target_link_libraries(benchdata PRIVATE servo-url)
+  target_compile_definitions(benchdata PRIVATE ADA_RUST_VERSION="${Rust_VERSION}")
+
+  target_link_libraries(bbc_bench PRIVATE servo-url)
+  target_compile_definitions(bbc_bench PRIVATE ADA_RUST_VERSION="${Rust_VERSION}")
+
+  target_link_libraries(percent_encode PRIVATE servo-url)
+  target_compile_definitions(percent_encode PRIVATE ADA_RUST_VERSION="${Rust_VERSION}")
+
+  target_link_libraries(wpt_bench PRIVATE servo-url)
+  target_compile_definitions(wpt_bench PRIVATE ADA_RUST_VERSION="${Rust_VERSION}")
+else()
+  message(STATUS "Rust/Cargo is unavailable." )
+  message(STATUS "We will not benchmark servo-url." )
+  if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    message(STATUS "Under macOS, you may be able to install rust with")
+    message(STATUS "curl https://sh.rustup.rs -sSf | sh")
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    message(STATUS "Under Linux, you may be able to install rust with a command such as")
+    message(STATUS "apt-get install cargo" )
+    message(STATUS "or" )
+    message(STATUS "curl https://sh.rustup.rs -sSf | sh")
+  endif()
+endif()
diff --git a/benchmarks/bbc_bench.cpp b/benchmarks/bbc_bench.cpp
new file mode 100644 (file)
index 0000000..907d491
--- /dev/null
@@ -0,0 +1,36 @@
+#include "benchmark_header.h"
+
+/**
+ * Realistic URL examples collected from the BBC homepage.
+ */
+std::string url_examples[] = {
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/"
+    "polyfills.js",
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/"
+    "css/orbit-v5-ltr.min.css",
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/"
+    "require.min.js",
+    "https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Rg.woff2",
+    "https://nav.files.bbci.co.uk/searchbox/c8bfe8595e453f2b9483fda4074e9d15/"
+    "css/box.css",
+    "https://static.files.bbci.co.uk/cookies/d3bb303e79f041fec95388e04f84e716/"
+    "cookie-banner/cookie-library.bundle.js",
+    "https://static.files.bbci.co.uk/account/id-cta/597/style/id-cta.css",
+    "https://gn-web-assets.api.bbc.com/wwhp/"
+    "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/responsive/css/"
+    "old-ie.min.css",
+    "https://gn-web-assets.api.bbc.com/wwhp/"
+    "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/modules/vendor/"
+    "bower/modernizr/modernizr.js"};
+
+void init_data(const char* v = nullptr) {}
+
+double url_examples_bytes = []() -> double {
+  size_t bytes{0};
+  for (std::string& url_string : url_examples) {
+    bytes += url_string.size();
+  }
+  return double(bytes);
+}();
+
+#include "benchmark_template.cpp"
diff --git a/benchmarks/bench.cpp b/benchmarks/bench.cpp
new file mode 100644 (file)
index 0000000..8986c93
--- /dev/null
@@ -0,0 +1,72 @@
+#include "benchmark_header.h"
+
+/**
+ * Realistic URL examples collected on the actual web.
+ */
+std::string url_examples_default[] = {
+    "https://www.google.com/"
+    "webhp?hl=en&amp;ictx=2&amp;sa=X&amp;ved=0ahUKEwil_"
+    "oSxzJj8AhVtEFkFHTHnCGQQPQgI",
+    "https://support.google.com/websearch/"
+    "?p=ws_results_help&amp;hl=en-CA&amp;fg=1",
+    "https://en.wikipedia.org/wiki/Dog#Roles_with_humans",
+    "https://www.tiktok.com/@aguyandagolden/video/7133277734310038830",
+    "https://business.twitter.com/en/help/troubleshooting/"
+    "how-twitter-ads-work.html?ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_"
+    "medium=web&utm_campaign=ao&utm_content=adsinfo",
+    "https://images-na.ssl-images-amazon.com/images/I/"
+    "41Gc3C8UysL.css?AUIClients/AmazonGatewayAuiAssets",
+    "https://www.reddit.com/?after=t3_zvz1ze",
+    "https://www.reddit.com/login/?dest=https%3A%2F%2Fwww.reddit.com%2F",
+    "postgresql://other:9818274x1!!@localhost:5432/"
+    "otherdb?connect_timeout=10&application_name=myapp",
+    "http://192.168.1.1",             // ipv4
+    "http://[2606:4700:4700::1111]",  // ipv6
+};
+
+std::vector<std::string> url_examples;
+
+double url_examples_bytes = []() -> double {
+  size_t bytes{0};
+  for (std::string& url_string : url_examples) {
+    bytes += url_string.size();
+  }
+  return double(bytes);
+}();
+
+#ifdef ADA_URL_FILE
+const char* default_file = ADA_URL_FILE;
+#else
+const char* default_file = nullptr;
+#endif
+
+size_t init_data(const char* input = default_file) {
+  // compute the number of bytes.
+  auto compute = []() -> double {
+    size_t bytes{0};
+    for (std::string& url_string : url_examples) {
+      bytes += url_string.size();
+    }
+    return double(bytes);
+  };
+  if (input == nullptr) {
+    for (const std::string& s : url_examples_default) {
+      url_examples.emplace_back(s);
+    }
+    url_examples_bytes = compute();
+    return url_examples.size();
+  }
+
+  if (!file_exists(input)) {
+    std::cout << "File not found !" << input << std::endl;
+    for (const std::string& s : url_examples_default) {
+      url_examples.emplace_back(s);
+    }
+  } else {
+    std::cout << "Loading " << input << std::endl;
+    url_examples = split_string(read_file(input));
+  }
+  url_examples_bytes = compute();
+  return url_examples.size();
+}
+#include "benchmark_template.cpp"
diff --git a/benchmarks/bench_search_params.cpp b/benchmarks/bench_search_params.cpp
new file mode 100644 (file)
index 0000000..26cf53e
--- /dev/null
@@ -0,0 +1,343 @@
+#include "benchmark_header.h"
+
+/**
+ * Realistic URL examples collected from Indeed.com, see
+ * https://github.com/ada-url/ada/pull/459#issuecomment-1624187633
+ */
+std::string url_examples_default[] = {
+    "https://secure.indeed.com/"
+    "auth?continue=https%3A%2F%2Fm5.apply.indeed.com%2Fbeta%2Findeedapply%"
+    "2Fresumeapply%3FdraftId%3Dd2f89678-c675-4dd6-8776-c7de2df808cc-Y21o%"
+    "26draftDc%3Dcmh%26postUrl%3Dhttp%253A%252F%252Fmuffit%252Fprocess-"
+    "indeedapply%26jk%3D4ce8c8f85737012d%26mob%3D0%26referer%3Dhttps%253A%252F%"
+    "252Fwww.indeed.com%252F%26formParent%3D%26hl%3Den_US%26jobTitle%"
+    "3DEmbedded%2BSoftware%2BEngineer%26questions%3Diq%253A%252F%"
+    "252F5a5f158dfd632ec505eb%253Fv%253D1%26twoPaneVjAllocId%3D%"
+    "26onappliedstatus%3D_updateIndeedApplyStatus%26preload%3D0%26autoString%"
+    "3Dnone%26iip%3D1%26recentsearchquery%3D%257B%2522what%2522%253A%"
+    "2522software%2Bengineer%2522%252C%2522where%2522%253A%2522austin%252C%"
+    "2Btx%2522%257D%26isCreateIAJobApiSuccess%3Dfalse%26onclose%"
+    "3DindeedApplyHandleModalClose%26onContinueClick%"
+    "3DindeedApplyHandleModalClose%26jobUrl%3Dhttps%253A%252F%252Fwww.indeed."
+    "com%252Fviewjob%253Fjk%253D4ce8c8f85737012d%26onready%3D_onButtonReady%"
+    "26onapplied%3DindeedApplyHandleApply%26href%3Dhttps%253A%252F%252Fwww."
+    "indeed.com%252Fviewjob%253Fjk%253D4ce8c8f85737012d%2526from%253Dmobhp_"
+    "jobfeed_auto%2526tk%253D1h4m9jbiui7lq801%2526viewtype%253Dembedded%"
+    "2526advn%253D2919294681304046%2526adid%253D409899006%2526xkcb%253DSoCq-_"
+    "M3NWbCoeUCiZ0LbzkdCdPP%2526topwindowlocation%253D%25252F%26coverletter%"
+    "3DOPTIONAL%26resume%3Drequired%26twoPaneAllocId%3D%26jobMeta%3D%257B%"
+    "2526quot%253Bvtk%2526quot%253B%253A%2526quot%253B1h4m9jddo28q3001%"
+    "2526quot%253B%252C%2526quot%253Btk%2526quot%253B%253A%2526quot%"
+    "253B1h4m9jbiui7lq801%2526quot%253B%257D%26src%3Didd%26ms%3D1688670424981%"
+    "26jobCompany%3DSigmaSense%252C%2BLLC%26onclick%"
+    "3DindeedApplyHandleButtonClick%26pingbackUrl%3Dhttps%253A%252F%252Fgdc."
+    "indeed.com%252Fconv%252ForgIndApp%253Fco%253DUS%2526vjtk%"
+    "253D1h4m9jddo28q3001%2526jk%253D4ce8c8f85737012d%2526mvj%253D0%2526tk%"
+    "253D1h4m9jbiui7lq801%2526trk.origin%253Djobsearch%2526sj%253D1%2526vjfrom%"
+    "253Dmobhp_jobfeed_auto%2526advn%253D2919294681304046%2526adid%"
+    "253D409899006%2526ad%253D-6NYlbfkN0BLmp7eN89U-"
+    "imdIS3k1HPy83nFSQVS0CyWSe3vCO57TwIlXkEWIh-"
+    "pJhJKr5e0ECbg2AnsbYecK2l6IQRkcmJAo04wMd0HwXw9frAU8JSwJ1mjwcEN4QeCXiILN_"
+    "wIA4Wr_ywZCGdozVPXXsoaJzqbyZBeGNAHJQuiHvWOxPzh1LKLSr_"
+    "pFbOxn1NmCOkmvvMW36P569CcM6K7a7vOkj32OJUAg8NT_"
+    "oipaaUGwXpvKlH6ebfTW6B3WWuJtZ9tsQNwH330zZOVkF1mhjr837W2e-OaEjikG0Nrqh-"
+    "9DFBdDUmSLosfcp0hGtARFGYWfp7xU-897-fsivVLte1sPZhzSqWn9P_"
+    "D9hHnfmG2LZnTVBp3Jx6QcGng4-U5K8v9KFx7XN9GjcqQum735VDirUpQ61ZT-"
+    "WOT5Ilm1xI3nNocOcUQJELhqt6WiAgSIyvTKw7SAfCj2fzp0DshQHzxqVdhe-"
+    "iJ9apJI0JWZa195l_ZNFYvu8-rusj79RaBev9_"
+    "LPbejUXOZON2MDA37bFHRZsyWNXOCCKl0tswubGZku70sD7HVHm5aYYINKdL_"
+    "uKogRuW4r7C99AU69eZMUJF78gl%2526xkcb%253DSoCq-_M3NWbCoeUCiZ0LbzkdCdPP%"
+    "2526astse%253Dad9474a7b6ec862d%2526assa%253D8360%26co%3DUS%26advNum%"
+    "3D2919294681304046%26noButtonUI%3Dtrue%26iaUid%3D1h4m9je9qjcbf800%26spn%"
+    "3D1%26jobId%3D5a5f158dfd632ec505eb%26isITA%3D0%26apiToken%"
+    "3Daa102235a5ccb18bd3668c0e14aa3ea7e2503cfac2a7a9bf3d6549899e125af4%"
+    "26jobLocation%3DAustin%252C%2BTX%2B78758%26twoPaneGroup%3D-1%"
+    "26indeedcsrftoken%3D7bG1QaY6YSlr3rfgMbu9YRVPyk1v2TF0%26phone%3DOPTIONAL%"
+    "26jobApplies%3D-1%26twoPaneVjGroup%3D-1%26returnToJobSearchUrl%3Dhttp%"
+    "253A%252F%252Fwww.indeed.com%252F%26indeedApplyableJobApiURI%3D&cfb=2&obo="
+    "http%3A%2F%2Fwww.indeed.com%2F&hl=en_US&from=indapply-login-SmartApply&"
+    "branding=indeed-apply",
+    //
+    "https://secure.indeed.com/"
+    "auth?continue=https%3A%2F%2Fm5.apply.indeed.com%2Fbeta%2Findeedapply%"
+    "2Fresumeapply%3FdraftId%3Dcd45b794-ede7-48a2-a143-6023319e90a4-Y21o%"
+    "26draftDc%3Dcmh%26postUrl%3Dhttps%253A%252F%252Fapply.workable.com%"
+    "252Fapi%252Fv1%252Fjobboards%252Findeed%252Fjobs%252FEC33BF8806%252Fapply%"
+    "26jk%3D0ffb6f7ed64d3bae%26mob%3D0%26referer%3Dhttps%253A%252F%252Fwww."
+    "indeed.com%252F%26formParent%3D%26hl%3Den_US%26jobTitle%3DEmbedded%"
+    "2BSoftware%2BEngineer%26questions%3Dhttps%253A%252F%252Fapply.workable."
+    "com%252Fapi%252Fv1%252Fjobboards%252Findeed%252Fjobs%252FEC33BF8806%"
+    "252Fquestions%26twoPaneVjAllocId%3D%26onappliedstatus%3D_"
+    "updateIndeedApplyStatus%26preload%3D0%26autoString%3Dnone%26iip%3D1%"
+    "26recentsearchquery%3D%257B%2522what%2522%253A%2522software%2Bengineer%"
+    "2522%252C%2522where%2522%253A%2522austin%252C%2Btx%2522%257D%"
+    "26isCreateIAJobApiSuccess%3Dfalse%26onclose%3DindeedApplyHandleModalClose%"
+    "26onContinueClick%3DindeedApplyHandleModalClose%26jobUrl%3Dhttps%253A%"
+    "252F%252Fwww.indeed.com%252Fviewjob%253Fjk%253D0ffb6f7ed64d3bae%26onready%"
+    "3D_onButtonReady%26onapplied%3DindeedApplyHandleApply%26href%3Dhttps%253A%"
+    "252F%252Fwww.indeed.com%252Fviewjob%253Fjk%253D0ffb6f7ed64d3bae%2526from%"
+    "253Dhp%2526tk%253D1h4m9jbiui7lq801%2526viewtype%253Dembedded%2526advn%"
+    "253D2169897021852324%2526adid%253D412530207%2526xkcb%253DSoDv-_"
+    "M3NWbCoe0CiZ0LbzkdCdPP%2526topwindowlocation%253D%25252F%26coverletter%3D%"
+    "26twoPaneAllocId%3D%26src%3Didd%26ms%3D1688670502027%26jobCompany%3DShift%"
+    "2BRobotics%26onclick%3DindeedApplyHandleButtonClick%26pingbackUrl%3Dhttps%"
+    "253A%252F%252Fgdc.indeed.com%252Fconv%252ForgIndApp%253Fco%253DUS%"
+    "2526vjtk%253D1h4m9ltcgii2t800%2526jk%253D0ffb6f7ed64d3bae%2526mvj%253D0%"
+    "2526tk%253D1h4m9jbiui7lq801%2526trk.origin%253Djobsearch%2526sj%253D1%"
+    "2526vjfrom%253Dhp%2526advn%253D2169897021852324%2526adid%253D412530207%"
+    "2526ad%253D-6NYlbfkN0ADTLHW1lVcttxG1n9WEfcRI1-"
+    "ixIWqaQXrnishWQ6BGJjne4HH5OGRzbL9TFjFzxuxk65rhcUupJlJ21QkpPLqd89n0B4cMJw-"
+    "xmaYdF9-dzypunDDP4jQEuuhT-tpejJCNc8jlBI6FGBAtkAXuipq96Z-"
+    "vOtd24jCWqboqknQBia2fKh5sYbqLv3E7C6vlBmxO2FH4-qm1_"
+    "vkeeUq1lsktOtkKCFK2RSR5V5xbkBHcu0hkuZAShjpg2ro3F4e9VbP5_"
+    "tC3BKSqdL9un4SibeC59V880-mAhOnU_"
+    "yhuURbniZCCFxjEH66D3euJEOSBZDVnpK0jsbAbxwAnx9dtEdC_"
+    "HG3BG2PgUf9uwPA8SgdtHuhTAkToYjDBF1l5ENrF3WSXIMTCANToEbE3FpgMwNgOkTDf_"
+    "4E0Zf-vZ5LjmNY_8q8gL9SwhL6dAsnb-iH5Nm9OGEI32LTlhl9KtszAFZ99UGlzmRjo_"
+    "iD7ienJa3zd_Ebh_NZWkb_4pEKal6--pSAPlVPbC6azvhPiBzQgMhzpUS9Z-7YYhU%25253D%"
+    "2526xkcb%253DSoDv-_M3NWbCoe0CiZ0LbzkdCdPP%2526astse%253Dc630be9cfe791df9%"
+    "2526assa%253D240%26co%3DUS%26advNum%3D2169897021852324%26noButtonUI%"
+    "3Dtrue%26iaUid%3D1h4m9lujpkblm800%26spn%3D1%26jobId%3D5F6DD26C1B%26isITA%"
+    "3D0%26apiToken%"
+    "3D3a51613a4d8b9799d352130065868b0c34bce36cee7f4dffa3ed16b0c7936634%"
+    "26jobLocation%3DAustin%252C%2BTexas%252C%2BUnited%2BStates%26twoPaneGroup%"
+    "3D-1%26indeedcsrftoken%3D7bG1QaY6YSlr3rfgMbu9YRVPyk1v2TF0%26phone%"
+    "3Doptional%26jobApplies%3D-1%26twoPaneVjGroup%3D-1%26returnToJobSearchUrl%"
+    "3Dhttp%253A%252F%252Fwww.indeed.com%252F%26indeedApplyableJobApiURI%3D&"
+    "cfb=2&obo=http%3A%2F%2Fwww.indeed.com%2F&hl=en_US&from=indapply-login-"
+    "SmartApply&branding=indeed-apply",
+    //
+    "https://secure.indeed.com/"
+    "auth?hl=en_US&co=US&continue=https%3A%2F%2Fwww.indeed.com%"
+    "2Fthirdpartysignin%3Fjk%3D67557c870d9debaf%26from%3Dhp%26from%3Djsfe-"
+    "3pintercept-viewjob%26tk%3D1h4m9jbiui7lq801%26viewtype%3Dembedded%26advn%"
+    "3D8187210054516026%26adid%3D378267801%26ad%3D-6NYlbfkN0CfpH2aSe_"
+    "yWN7pjV6WFrWU4hEZi9Btn9eCdDUBIhjK5M5mY81rEexvugfeSup1QuHOvw9d5hvgsJ79xiL2b"
+    "Cis9Y8r23bY8qvwxN3cXtMQH5eaPpn4zk1QcFRVOjQFg-"
+    "0YX6StKUcjnJroSlWw3vVqor9zKJ4mUJ-Ksql7DBTYyyZGXojbnMo-"
+    "neBlW1zDoHnAAl1ZZZa38U8p1jl35T8o9uwhvY3mVw2XDdmKpKawVuyFfiNGl3_"
+    "jyLBWarAGLeTBHVsVlBONBK8GK4zH1pVL31V4M43uQUjWUhjRqH4lnq92jt7uCHE97bhKm2hMo"
+    "6dpJ6I-"
+    "1REKDf9gE0gloVW3r2lBI2TpIWbePg2zuBg4CnvYaRAm7elrbL8hYuiPYtB3hjTkldS_IYH3-"
+    "NgunawHQ-"
+    "LwIxAO35DyDhaY1DrGuFWaTQj6f1JlddpnImKhUaKP3jgV0q9uKoQxvyyFhLOlLGDxfMsVecGZ"
+    "B4lwuUK0TE74Qix1iR26X1QtEguPk8yp8DQZ-AfOqT_"
+    "S7A0PtcI2eI0sLM1y3BHB3p0KdpYJUsDv02t7UYO_gNEmMOmcsr5gLsmE-cu52BF_"
+    "n2lEDE3kKpIKqMu91dFTmI25H393tb-"
+    "PfCUfVAVaUveXuO2hjWSctjtFCo9RPl6ix3ilDs1QgKt08BtT4IUb5I24JlxIJXNvkHhkH75vw"
+    "PH9SHKr5XfuN32rOCTUr9JWLmVEcQ4x5A0pHUXQRyz8OxdfsifIibHB8SpDYTtyY50lSL4sAe3"
+    "M4PDq0d54xfqWuSQqhGqo0lE944k8JjiQue8M1cIcqpssOOqE8SIi-"
+    "hDdv1KG0G1kQuLBIYMzzrGCJ6WDZm_KbLiyK0wTrPf2cWfHIyU1JI1pdWKbK6fop_"
+    "kuNd3OBEAl00YETNwOrg4HrZdK8NXEkG_QWXA-A0nYxFWz58uoHND5rkyVDO0o%26xkcb%"
+    "3DSoBZ-_M3NWbCoZUCiZ0LbzkdCdPP%26topwindowlocation%3D%252F%253Fadvn%"
+    "253D2169897021852324%2526vjk%253D0ffb6f7ed64d3bae%26vjtk%"
+    "3D1h4m9npiq21a4002&from=jsfe-3pintercept-viewjob&branding=third-party-"
+    "applies",
+    //
+    "https://secure.indeed.com/"
+    "auth?continue=https%3A%2F%2Fm5.apply.indeed.com%2Fbeta%2Findeedapply%"
+    "2Fresumeapply%3FdraftId%3Dde4f06da-7b31-465c-96d2-80f791a85bf7-Y21o%"
+    "26draftDc%3Dcmh%26postUrl%3Dhttp%253A%252F%252Fmuffit%252Fprocess-"
+    "indeedapply%26jk%3D7590bdb1fe928d49%26mob%3D0%26referer%3Dhttps%253A%252F%"
+    "252Fwww.indeed.com%252F%253Fvjk%253D4ce8c8f85737012d%2526advn%"
+    "253D2919294681304046%26formParent%3D%26hl%3Den_US%26jobTitle%3DSenior%"
+    "2BSoftware%2BDeveloper%2B%2528onsite%2529%26questions%3Diq%253A%252F%"
+    "252F0efc2325f6b4a2c5bc27%253Fv%253D1%26twoPaneVjAllocId%3D%"
+    "26onappliedstatus%3D_updateIndeedApplyStatus%26preload%3D0%26autoString%"
+    "3Dnone%26iip%3D1%26recentsearchquery%3D%257B%2522what%2522%253A%"
+    "2522software%2Bengineer%2522%252C%2522where%2522%253A%2522austin%252C%"
+    "2Btx%2522%257D%26isCreateIAJobApiSuccess%3Dfalse%26onclose%"
+    "3DindeedApplyHandleModalClose%26onContinueClick%"
+    "3DindeedApplyHandleModalClose%26jobUrl%3Dhttps%253A%252F%252Fwww.indeed."
+    "com%252Fviewjob%253Fjk%253D7590bdb1fe928d49%26onready%3D_onButtonReady%"
+    "26onapplied%3DindeedApplyHandleApply%26href%3Dhttps%253A%252F%252Fwww."
+    "indeed.com%252Fviewjob%253Fjk%253D7590bdb1fe928d49%2526from%253Dhp%2526tk%"
+    "253D1h4m9jbiui7lq801%2526viewtype%253Dembedded%2526advn%"
+    "253D5522285726153717%2526adid%253D414206073%2526xkcb%253DSoDt-_"
+    "M3NWbCoZUCiZ0KbzkdCdPP%2526topwindowlocation%253D%25252F%25253Fvjk%"
+    "25253D4ce8c8f85737012d%252526advn%25253D2919294681304046%26coverletter%"
+    "3DOPTIONAL%26resume%3Drequired%26twoPaneAllocId%3D%26jobMeta%3D%257B%"
+    "2526quot%253Bvtk%2526quot%253B%253A%2526quot%253B1h4m9oh7mirks800%"
+    "2526quot%253B%252C%2526quot%253Btk%2526quot%253B%253A%2526quot%"
+    "253B1h4m9jbiui7lq801%2526quot%253B%257D%26src%3Didd%26ms%3D1688670587917%"
+    "26jobCompany%3DCitizens%2BInc%26onclick%3DindeedApplyHandleButtonClick%"
+    "26pingbackUrl%3Dhttps%253A%252F%252Fgdc.indeed.com%252Fconv%252ForgIndApp%"
+    "253Fco%253DUS%2526vjtk%253D1h4m9oh7mirks800%2526jk%253D7590bdb1fe928d49%"
+    "2526mvj%253D0%2526tk%253D1h4m9jbiui7lq801%2526trk.origin%253Djobsearch%"
+    "2526sj%253D1%2526vjfrom%253Dhp%2526advn%253D5522285726153717%2526adid%"
+    "253D414206073%2526ad%253D-"
+    "6NYlbfkN0CHSAkotDdvvZVbhOqFdbxXOHJMhXe1DXuaBPnaU5fYte-"
+    "aud5Z0lqoqFyp33jrJfy1DYFhCWCqBjAqfX3PBXom-d5E4gy3cqbwZuMtWn4flXO-"
+    "Fd9DkMZrQjqK002kTnGqvqfkH0ftIspK3hwJPRmAEy7EY87A9OOFRyFmxA9AdiimsdRWyksA-"
+    "nCQ0w1VI28XDuVMu7qO_D46dH-"
+    "dtW5jWIG4jTe8HCv21447lFobYgFb9oJdF8NrjyCNP4fdGeojlELmcjS5cvC5dKfXi8IZm4sWW"
+    "-7b5SBQKvBMmSVDjiTsgYZS6lb8B-"
+    "a3YF1Lny7hpNfClmOcLe49wiZAG9LWJ7uRUEfzOPrUCwxdHNQK-vEo3ZhDK4AeER-"
+    "LfOUabNSjrKz7_91l8sQjBNOR-FJ25ioX0sqoNByLfJC7cWzjDxqvW-l82GsWQR2O_"
+    "6Khe2oq91fjVXMAFQdSQWdr_DWCf_"
+    "e2FYtN69Qql9maXH550XNcfynxCicTL71xLstYfWqbSMpADJhrW_"
+    "0pf4x58zLVfYLBJ7MPQaW15uKzbFn68lAlyF5GXDqWxowOm58EyeS7OmQkBdGyxYanZ6452m6O"
+    "%2526xkcb%253DSoDt-_M3NWbCoZUCiZ0KbzkdCdPP%2526astse%253Db4f6f6ed591bacca%"
+    "2526assa%253D6102%26co%3DUS%26advNum%3D5522285726153717%26noButtonUI%"
+    "3Dtrue%26iaUid%3D1h4m9oi2qj4h4800%26spn%3D1%26jobId%"
+    "3D0efc2325f6b4a2c5bc27%26isITA%3D0%26apiToken%"
+    "3Daa102235a5ccb18bd3668c0e14aa3ea7e2503cfac2a7a9bf3d6549899e125af4%"
+    "26jobLocation%3DAustin%252C%2BTX%2B78758%26twoPaneGroup%3D-1%"
+    "26indeedcsrftoken%3D7bG1QaY6YSlr3rfgMbu9YRVPyk1v2TF0%26phone%3DOPTIONAL%"
+    "26jobApplies%3D-1%26twoPaneVjGroup%3D-1%26returnToJobSearchUrl%3Dhttp%"
+    "253A%252F%252Fwww.indeed.com%252F%253Fvjk%253D4ce8c8f85737012d%2526advn%"
+    "253D2919294681304046%26indeedApplyableJobApiURI%3D&cfb=2&obo=http%3A%2F%"
+    "2Fwww.indeed.com%2F&hl=en_US&from=indapply-login-SmartApply&branding="
+    "indeed-apply"};
+
+std::vector<std::string> url_examples;
+
+double url_examples_bytes = []() -> double {
+  size_t bytes{0};
+  for (std::string& url_string : url_examples) {
+    bytes += url_string.size();
+  }
+  return double(bytes);
+}();
+
+#ifdef ADA_URL_FILE
+const char* default_file = ADA_URL_FILE;
+#else
+const char* default_file = nullptr;
+#endif
+
+size_t init_data(const char* input = default_file) {
+  // compute the number of bytes.
+  auto compute = []() -> double {
+    size_t bytes{0};
+    for (std::string& url_string : url_examples) {
+      bytes += url_string.size();
+    }
+    return double(bytes);
+  };
+  if (input == nullptr) {
+    for (const std::string& s : url_examples_default) {
+      url_examples.emplace_back(s);
+    }
+    url_examples_bytes = compute();
+    return url_examples.size();
+  }
+
+  if (!file_exists(input)) {
+    std::cout << "File not found !" << input << std::endl;
+    for (const std::string& s : url_examples_default) {
+      url_examples.emplace_back(s);
+    }
+  } else {
+    std::cout << "Loading " << input << std::endl;
+    url_examples = split_string(read_file(input));
+  }
+  url_examples_bytes = compute();
+  return url_examples.size();
+}
+
+size_t count_ada_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    auto url = ada::parse(url_string);
+    if (!url) {
+      how_many++;
+    }
+  }
+  return how_many;
+}
+
+template <class result_type = ada::url_aggregator>
+static void BasicBench_AdaURL(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t param_count = 0;
+
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      ada::result<result_type> url = ada::parse<result_type>(url_string);
+      if (url) {
+        auto params = ada::url_search_params{url->get_search()};
+        param_count += params.size();
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        ada::result<result_type> url = ada::parse<result_type>(url_string);
+        if (url) {
+          auto params = ada::url_search_params{url->get_search()};
+          param_count += params.size();
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)param_count;
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+
+auto url_search_params_AdaURL = BasicBench_AdaURL<ada::url_aggregator>;
+BENCHMARK(url_search_params_AdaURL);
+
+int main(int argc, char** argv) {
+  if (argc > 1 && file_exists(argv[1])) {
+    init_data(argv[1]);
+  } else {
+    init_data();
+  }
+#if (__APPLE__ && __aarch64__) || defined(__linux__)
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters",
+                                "No privileged access (sudo may help).");
+  }
+#else
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Unsupported system.");
+  }
+#endif
+  benchmark::AddCustomContext("input bytes",
+                              std::to_string(size_t(url_examples_bytes)));
+  benchmark::AddCustomContext("number of URLs",
+                              std::to_string(std::size(url_examples)));
+  benchmark::AddCustomContext(
+      "bytes/URL",
+      std::to_string(url_examples_bytes / std::size(url_examples)));
+  if (collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Enabled");
+  }
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+}
diff --git a/benchmarks/benchmark_header.h b/benchmarks/benchmark_header.h
new file mode 100644 (file)
index 0000000..3351e27
--- /dev/null
@@ -0,0 +1,65 @@
+#include <iostream>
+#include <memory>
+#include <cstdlib>
+#include <sstream>
+#include <fstream>
+#include <filesystem>
+
+#if ADA_VARIOUS_COMPETITION_ENABLED
+#include <uriparser/Uri.h>
+#include <EdUrlParser.h>
+#include <http_parser.h>
+#endif
+#if ADA_url_whatwg_ENABLED
+#include <upa/url.h>
+#endif
+
+#include "ada.h"
+#include "performancecounters/event_counter.h"
+event_collector collector;
+size_t N = 1000;
+
+#include <benchmark/benchmark.h>
+
+bool file_exists(const char* filename) {
+  namespace fs = std::filesystem;
+  std::filesystem::path f{filename};
+  if (std::filesystem::exists(filename)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+std::string read_file(std::string filename) {
+  constexpr size_t read_size = 4096;
+  auto stream = std::ifstream(filename.c_str());
+  stream.exceptions(std::ios_base::badbit);
+  std::string out;
+  std::string buf(read_size, '\0');
+  while (stream.read(&buf[0], read_size)) {
+    out.append(buf, 0, size_t(stream.gcount()));
+  }
+  out.append(buf, 0, size_t(stream.gcount()));
+  return out;
+}
+
+std::vector<std::string> split_string(const std::string& str) {
+  std::vector<std::string> result;
+  std::stringstream ss{str};
+  for (std::string line; std::getline(ss, line, '\n');) {
+    std::string_view view = line;
+    // Some parsers like boost/url will refuse to parse a URL with trailing
+    // whitespace.
+    while (!view.empty() && std::isspace(view.back())) {
+      view.remove_suffix(1);
+    }
+    while (!view.empty() && std::isspace(view.front())) {
+      view.remove_prefix(1);
+    }
+    if (!view.empty()) {
+      result.emplace_back(view);
+    }
+  }
+  return result;
+}
diff --git a/benchmarks/benchmark_template.cpp b/benchmarks/benchmark_template.cpp
new file mode 100644 (file)
index 0000000..922872a
--- /dev/null
@@ -0,0 +1,818 @@
+/**
+ * The main benchmark is to take an input string, and convert it into a
+ * normalized URL (or 'href').
+ */
+
+size_t count_ada_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    auto url = ada::parse(url_string);
+    if (!url) {
+      how_many++;
+    }
+  }
+  return how_many;
+}
+
+enum { JUST_PARSE = 1, PARSE_AND_HREF = 0 };
+
+template <bool just_parse = PARSE_AND_HREF,
+          class result_type = ada::url_aggregator>
+static void BasicBench_AdaURL(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t success = 0;
+  volatile size_t href_size = 0;
+
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      ada::result<result_type> url = ada::parse<result_type>(url_string);
+      if (url) {
+        success++;
+        if constexpr (!just_parse) {
+          href_size += url->get_href().size();
+        }
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        ada::result<result_type> url = ada::parse<result_type>(url_string);
+        if (url) {
+          success++;
+          if constexpr (!just_parse) {
+            href_size += url->get_href().size();
+          }
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)success;
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+
+auto BasicBench_AdaURL_href = BasicBench_AdaURL<PARSE_AND_HREF, ada::url>;
+BENCHMARK(BasicBench_AdaURL_href);
+auto BasicBench_AdaURL_aggregator_href =
+    BasicBench_AdaURL<PARSE_AND_HREF, ada::url_aggregator>;
+BENCHMARK(BasicBench_AdaURL_aggregator_href);
+
+#if ADA_url_whatwg_ENABLED
+size_t count_whatwg_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    upa::url url;
+    if (!upa::success(url.parse(url_string, nullptr))) {
+      how_many++;
+    }
+  }
+  return how_many;
+}
+
+template <bool just_parse = PARSE_AND_HREF>
+static void BasicBench_whatwg(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t success = 0;
+  volatile size_t href_size = 0;
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      upa::url url;
+      if (upa::success(url.parse(url_string, nullptr))) {
+        success++;
+        if (!just_parse) {
+          href_size += url.href().size();
+        }
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        upa::url url;
+        if (upa::success(url.parse(url_string, nullptr))) {
+          success++;
+          if (!just_parse) {
+            href_size += url.href().size();
+          }
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)success;
+  (void)href_size;
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate |
+                                   benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_whatwg);
+// There is no need for BasicBench_whatwg_just_parse because whatwg appears to
+// provide the href at a minimal cost, probably because it is already
+// materialized. auto BasicBench_whatwg_just_parse =
+// BasicBench_whatwg<JUST_PARSE>; BENCHMARK(BasicBench_whatwg_just_parse);
+
+#endif  // ADA_url_whatwg_ENABLED
+
+#if ADA_CURL_ENABLED
+#include <curl/curl.h>
+
+size_t count_curl_invalid() {
+  size_t how_many = 0;
+  CURLU* url = curl_url();
+  for (std::string& url_string : url_examples) {
+    CURLUcode rc = curl_url_set(url, CURLUPART_URL, url_string.c_str(), 0);
+    // Returns a CURLUcode error value, which is (0) if everything went fine.
+    if (rc != 0) {
+      how_many++;
+    }
+  }
+  curl_url_cleanup(url);
+  return how_many;
+}
+
+// curl follows RFC3986+
+template <bool just_parse = false>
+static void BasicBench_CURL(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t success = 0;
+  volatile size_t href_size = 0;
+
+  CURLU* url = curl_url();
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      CURLUcode rc = curl_url_set(url, CURLUPART_URL, url_string.c_str(), 0);
+      // Returns a CURLUcode error value, which is (0) if everything went fine.
+      if (rc == 0) {
+        success++;
+        if (!just_parse) {
+          char* buffer;
+          // When asked to return the full URL, curl_url_get will return a
+          // normalized and possibly cleaned up version of what was previously
+          // parsed.
+          rc = curl_url_get(url, CURLUPART_URL, &buffer, 0);
+          if (rc == 0) {
+            href_size += strlen(buffer);
+            curl_free(buffer);
+          }
+        }
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        CURLUcode rc = curl_url_set(url, CURLUPART_URL, url_string.c_str(), 0);
+        // Returns a CURLUcode error value, which is (0) if everything went
+        // fine.
+        if (!just_parse) {
+          char* buffer;
+          rc = curl_url_get(url, CURLUPART_URL, &buffer, 0);
+          if (rc == 0) {
+            href_size += strlen(buffer);
+            curl_free(buffer);
+          }
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)success;
+  curl_url_cleanup(url);
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate |
+                                   benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_CURL);
+// 'just parsing' is faster with curl, but maybe not so important for us.
+// auto BasicBench_CURL_just_parse = BasicBench_CURL<JUST_PARSE>;
+// BENCHMARK(BasicBench_CURL_just_parse);
+#endif
+
+#if ADA_BOOST_ENABLED
+#include <boost/url/src.hpp>
+using namespace boost::urls;
+
+size_t count_boosturl_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    try {
+      url u(url_string);
+      u.normalize();
+    } catch (...) {
+      how_many++;
+    }
+  }
+  return how_many;
+}
+
+// Boost URL follows RFC3986
+template <bool just_parse = false>
+static void BasicBench_BoostURL(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t success = 0;
+  volatile size_t href_size = 0;
+
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      try {
+        url u(url_string);
+        u.normalize();
+        success++;
+        if (!just_parse) {
+          href_size += u.buffer().size();
+        }
+      } catch (...) {
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        try {
+          url u(url_string);
+          u.normalize();
+          success++;
+          if (!just_parse) {
+            href_size += u.buffer().size();
+          }
+        } catch (...) {
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)success;
+  (void)href_size;
+
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate |
+                                   benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_BoostURL);
+// There is no need for 'just_parse' because BoostURL materializes the href.
+// auto BasicBench_BoostURL_just_parse = BasicBench_BoostURL<JUST_PARSE>;
+// BENCHMARK(BasicBench_BoostURL_just_parse);
+#endif  // ADA_BOOST_ENABLED
+
+#if ADA_ZURI_ENABLED
+#include <zuri.h>
+
+size_t count_zuri_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    struct zuri2k uri;
+    zuri_error err = zuri_parse2k(&uri, url_string.c_str());
+    if (err) how_many++;
+  }
+  return how_many;
+}
+
+// ZURI follows RFC3986
+template <bool just_parse = false>
+static void BasicBench_ZURI(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile size_t success = 0;
+  volatile size_t href_size = 0;
+
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      struct zuri2k uri;
+      benchmark::DoNotOptimize(uri);
+      zuri_error err = zuri_parse2k(&uri, url_string.c_str());
+      if (!err) {
+        success++;
+        if constexpr (!just_parse) {
+          char buf[2048];
+          benchmark::DoNotOptimize(href_size +=
+                                   zuri_read2k(&uri, &buf[0], sizeof(buf)));
+        }
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        struct zuri2k uri;
+        benchmark::DoNotOptimize(uri);
+        zuri_error err = zuri_parse2k(&uri, url_string.c_str());
+        if (!err) {
+          success++;
+          if constexpr (!just_parse) {
+            char buf[2048];
+            benchmark::DoNotOptimize(href_size +=
+                                     zuri_read2k(&uri, &buf[0], sizeof(buf)));
+          }
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  (void)success;
+  (void)href_size;
+
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate |
+                                   benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] = benchmark::Counter(
+      std::size(url_examples), benchmark::Counter::kIsIterationInvariantRate);
+}
+
+BENCHMARK(BasicBench_ZURI);
+#endif  // ADA_ZURI_ENABLED
+
+#if ADA_VARIOUS_COMPETITION_ENABLED
+static void BasicBench_uriparser_just_parse(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  volatile bool is_valid = true;
+  const char* errorPos;
+  UriUriA uri;
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      is_valid &= (uriParseSingleUriA(&uri, url_string.c_str(), &errorPos) ==
+                   URI_SUCCESS);
+    }
+  }
+  if (!is_valid) {
+    std::cout << "uri-parser: invalid? " << std::endl;
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        is_valid &= (uriParseSingleUriA(&uri, url_string.c_str(), &errorPos) ==
+                     URI_SUCCESS);
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  uriFreeUriMembersA(&uri);
+
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_uriparser_just_parse);
+#endif  // ADA_VARIOUS_COMPETITION_ENABLED
+
+#if ADA_VARIOUS_COMPETITION_ENABLED
+static void BasicBench_urlparser_just_parse(benchmark::State& state) {
+  // volatile to prevent optimizations.
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      std::unique_ptr<EdUrlParser> url(EdUrlParser::parseUrl(url_string));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        std::unique_ptr<EdUrlParser> url(EdUrlParser::parseUrl(url_string));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_urlparser_just_parse);
+#endif  // ADA_VARIOUS_COMPETITION_ENABLED
+
+#if ADA_VARIOUS_COMPETITION_ENABLED
+static void BasicBench_http_parser_just_parse(benchmark::State& state) {
+  volatile bool is_valid{true};
+  struct http_parser_url u;
+  http_parser_url_init(&u);
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      is_valid &=
+          !http_parser_parse_url(url_string.data(), url_string.size(), 0, &u);
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        is_valid &=
+            !http_parser_parse_url(url_string.data(), url_string.size(), 0, &u);
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+
+  if (!is_valid) {
+    std::cout << "http_parser: invalid? " << std::endl;
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_http_parser_just_parse);
+#endif  // ADA_VARIOUS_COMPETITION_ENABLED
+
+#if defined(ADA_RUST_VERSION)
+#include "competitors/servo-url/servo_url.h"
+size_t count_rust_invalid() {
+  size_t how_many = 0;
+  for (std::string& url_string : url_examples) {
+    servo_url::Url* url =
+        servo_url::parse_url(url_string.c_str(), url_string.length());
+    servo_url::free_url(url);
+    if (!url) {
+      how_many++;
+    }
+  }
+  return how_many;
+}
+
+// Emilio from Mozilla recommended that using an opaque-pointer will improve the
+// performance of this benchmark. It has indeed improved but with the cost of
+// validating the output. Reference:
+// https://twitter.com/ecbos_/status/1627494441656238082?s=61&t=vCdcfSGWHH056CBdklWfCg
+static void BasicBench_ServoUrl(benchmark::State& state) {
+  // Other benchmarks copy the 'standard url' to a structure.
+  // We try to mimic the effect.
+  volatile size_t success = 0;
+
+  for (auto _ : state) {
+    for (std::string& url_string : url_examples) {
+      // benchmark::DoNotOptimize is unnecessary and potentially misleading.
+      const char* url_href =
+          servo_url::parse_url_to_href(url_string.c_str(), url_string.length());
+      if (url_href) {
+        // if you'd like you could print it: printf("%s\n", url_href);
+        success++;
+        servo_url::free_string(url_href);
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : url_examples) {
+        const char* url_href = servo_url::parse_url_to_href(
+            url_string.c_str(), url_string.length());
+        if (url_href) {
+          success++;
+          servo_url::free_string(url_href);
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    (void)success;
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_ServoUrl);
+#endif  // ADA_RUST
+
+int main(int argc, char** argv) {
+  if (argc > 1 && file_exists(argv[1])) {
+    init_data(argv[1]);
+  } else {
+    init_data();
+  }
+  benchmark::AddCustomContext("ada spec", "Ada follows whatwg/url");
+  size_t ada_bad_url = count_ada_invalid();
+#if ADA_url_whatwg_ENABLED
+  size_t whatwg_bad_url = count_whatwg_invalid();
+#endif
+#if defined(ADA_RUST_VERSION)
+  benchmark::AddCustomContext("rust version ", ADA_RUST_VERSION);
+  size_t servo_bad_url = count_rust_invalid();
+#endif
+#if ADA_CURL_ENABLED
+  // the curl dependency will depend on the system.
+  benchmark::AddCustomContext("curl version ", LIBCURL_VERSION);
+  benchmark::AddCustomContext("curl spec",
+                              "Curl follows RFC3986, not whatwg/url");
+  size_t curl_bad_url = count_curl_invalid();
+#else
+  benchmark::AddCustomContext("curl ", "OMITTED");
+#endif
+#if ADA_BOOST_ENABLED
+  benchmark::AddCustomContext("boost-url spec",
+                              "Boost URL follows RFC3986, not whatwg/url");
+  size_t boost_bad_url = count_boosturl_invalid();
+#endif
+#if ADA_ZURI_ENABLED
+  benchmark::AddCustomContext("zuri spec",
+                              "Zuri follows RFC3986, not whatwg/url");
+  size_t zuri_bad_url = count_zuri_invalid();
+#else
+  benchmark::AddCustomContext("zuri ", "OMITTED");
+#endif
+#if (__APPLE__ && __aarch64__) || defined(__linux__)
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters",
+                                "No privileged access (sudo may help).");
+  }
+#else
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Unsupported system.");
+  }
+#endif
+  benchmark::AddCustomContext("input bytes",
+                              std::to_string(size_t(url_examples_bytes)));
+  benchmark::AddCustomContext("number of URLs",
+                              std::to_string(std::size(url_examples)));
+  benchmark::AddCustomContext(
+      "bytes/URL",
+      std::to_string(url_examples_bytes / std::size(url_examples)));
+#if ADA_VARIOUS_COMPETITION_ENABLED
+  benchmark::AddCustomContext("WARNING",
+                              "BasicBench_urlparser and BasicBench_uriparser "
+                              "do not use a normalized task.");
+#endif
+  if (collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Enabled");
+  }
+  std::stringstream badcounts;
+  badcounts << "---------------------\n";
+  badcounts << "ada---count of bad URLs       " << std::to_string(ada_bad_url)
+            << "\n";
+#if defined(ADA_RUST_VERSION)
+  badcounts << "servo/url---count of bad URLs " << std::to_string(servo_bad_url)
+            << "\n";
+#endif
+#if ADA_url_whatwg_ENABLED
+  badcounts << "whatwg---count of bad URLs    "
+            << std::to_string(whatwg_bad_url) << "\n";
+#endif
+#if ADA_CURL_ENABLED
+  badcounts << "curl---count of bad URLs      " << std::to_string(curl_bad_url)
+            << "\n";
+#endif
+#if ADA_BOOST_ENABLED
+  badcounts << "boost-url---count of bad URLs " << std::to_string(boost_bad_url)
+            << "\n";
+#endif
+#if ADA_ZURI_ENABLED
+  badcounts << "zuri---count of bad URLs      " << std::to_string(zuri_bad_url)
+            << "\n";
+#endif
+  badcounts << "-------------------------------\n";
+  benchmark::AddCustomContext("bad urls", badcounts.str());
+
+  if (size_t(url_examples_bytes) > 1000000) {
+    N = 10;
+  }
+
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+}
diff --git a/benchmarks/competitors/servo-url/Cargo.lock b/benchmarks/competitors/servo-url/Cargo.lock
new file mode 100644 (file)
index 0000000..262ac52
--- /dev/null
@@ -0,0 +1,83 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "servo-url"
+version = "0.1.0"
+dependencies = [
+ "libc",
+ "url",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
diff --git a/benchmarks/competitors/servo-url/Cargo.toml b/benchmarks/competitors/servo-url/Cargo.toml
new file mode 100644 (file)
index 0000000..900f878
--- /dev/null
@@ -0,0 +1,16 @@
+[package]
+name = "servo-url"
+version = "0.1.0"
+
+[lib]
+path = "lib.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+url = "2.5.0"
+libc = "0.2"
+
+[profile.release]
+opt-level = 3
+debug = false
+lto = true
diff --git a/benchmarks/competitors/servo-url/README.md b/benchmarks/competitors/servo-url/README.md
new file mode 100644 (file)
index 0000000..47a3c78
--- /dev/null
@@ -0,0 +1,17 @@
+## Servo URL FFI
+
+This folder includes FFI bindings for servo/url.
+
+### Links
+
+- https://github.com/eqrion/cbindgen/blob/master/docs.md
+- https://gist.github.com/zbraniecki/b251714d77ffebbc73c03447f2b2c69f
+- https://michael-f-bryan.github.io/rust-ffi-guide/setting_up.html
+
+### Building
+
+- Generating cbindgen output
+  - Install dependencies with `brew install cbindgen`
+  - Generate with `cbindgen --config cbindgen.toml --crate servo-url --output servo_url.h`
+- Building
+  - Run with `cargo build --release`
diff --git a/benchmarks/competitors/servo-url/cbindgen.toml b/benchmarks/competitors/servo-url/cbindgen.toml
new file mode 100644 (file)
index 0000000..0daad0c
--- /dev/null
@@ -0,0 +1,12 @@
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["servo_url"]
+include_guard = "servo_url_ffi_h"
+
+[parse]
+parse_deps = true
+include = ["url"]
diff --git a/benchmarks/competitors/servo-url/lib.rs b/benchmarks/competitors/servo-url/lib.rs
new file mode 100644 (file)
index 0000000..700ebf3
--- /dev/null
@@ -0,0 +1,44 @@
+use url::Url;
+use std::slice;
+use libc::{c_char, size_t};
+
+extern crate url;
+extern crate libc;
+
+#[no_mangle]
+pub unsafe extern "C" fn parse_url(raw_input: *const c_char, raw_input_length: size_t) -> *mut Url {
+  let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length));
+  // This code would assume that the URL is parsed successfully:
+  // let result = Url::parse(input).unwrap();
+  // Box::into_raw(Box::new(result))
+  // But we might get an invalid input. So we want to return null in case of
+  // error. We can do it in such a manner:
+  match Url::parse(input) {
+    Ok(result) => Box::into_raw(Box::new(result)),
+    Err(_) => std::ptr::null_mut(),
+  }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn parse_url_to_href(raw_input: *const c_char, raw_input_length: size_t) -> *const c_char {
+  let input = std::str::from_utf8_unchecked(slice::from_raw_parts(raw_input as *const u8, raw_input_length));
+  match Url::parse(input) {
+    Ok(result) => std::ffi::CString::new(result.as_str()).unwrap().into_raw(),
+    Err(_) => std::ptr::null_mut(),
+  }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn free_url(raw: *mut Url) {
+  if raw.is_null() {
+    return;
+  }
+
+  drop(Box::from_raw(raw))
+}
+
+#[no_mangle]
+pub unsafe extern fn free_string(ptr: *const c_char) {
+    // Take the ownership back to rust and drop the owner
+    let _ = std::ffi::CString::from_raw(ptr as *mut _);
+}
diff --git a/benchmarks/competitors/servo-url/servo_url.h b/benchmarks/competitors/servo-url/servo_url.h
new file mode 100644 (file)
index 0000000..406d5fc
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef servo_url_ffi_h
+#define servo_url_ffi_h
+
+/* This file was modified manually. */
+
+#include <cstdarg>
+#include <cstdint>
+#include <cstdlib>
+#include <ostream>
+#include <new>
+
+namespace servo_url {
+
+/// A parsed URL record.
+struct Url;
+
+extern "C" {
+
+Url *parse_url(const char *raw_input, size_t raw_input_length);
+
+void free_url(Url *raw);
+
+const char *parse_url_to_href(const char *raw_input, size_t raw_input_length);
+
+void free_string(const char *);
+}  // extern "C"
+
+}  // namespace servo_url
+
+#endif  // servo_url_ffi_h
diff --git a/benchmarks/model_bench.cpp b/benchmarks/model_bench.cpp
new file mode 100644 (file)
index 0000000..e9d1501
--- /dev/null
@@ -0,0 +1,280 @@
+#include <cstdlib>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <sstream>
+
+#include "ada.h"
+#include "performancecounters/event_counter.h"
+event_collector collector;
+
+bool file_exists(const char *filename) {
+  namespace fs = std::filesystem;
+  std::filesystem::path f{filename};
+  if (std::filesystem::exists(filename)) {
+    return true;
+  } else {
+    std::cout << "  file missing: " << filename << std::endl;
+    return false;
+  }
+}
+
+std::string read_file(std::string filename) {
+  constexpr size_t read_size = 4096;
+  std::ifstream stream(filename.c_str());
+  stream.exceptions(std::ios_base::badbit);
+  std::string out;
+  std::string buf(read_size, '\0');
+  while (stream.read(&buf[0], read_size)) {
+    out.append(buf, 0, size_t(stream.gcount()));
+  }
+  out.append(buf, 0, size_t(stream.gcount()));
+  return out;
+}
+
+std::vector<std::string> split_string(const std::string &str) {
+  auto result = std::vector<std::string>{};
+  std::stringstream ss{str};
+  for (std::string line; std::getline(ss, line, '\n');) {
+    std::string_view view = line;
+    // Some parsers like boost/url will refuse to parse a URL with trailing
+    // whitespace.
+    while (!view.empty() && std::isspace(view.back())) {
+      view.remove_suffix(1);
+    }
+    while (!view.empty() && std::isspace(view.front())) {
+      view.remove_prefix(1);
+    }
+    if (!view.empty()) {
+      result.emplace_back(view);
+    }
+  }
+  return result;
+}
+
+struct stat_numbers {
+  std::string url_string{};
+  std::string href{};
+  ada::url_components components{};
+  event_aggregate counters{};
+  bool is_valid = true;
+  bool has_port = false;
+  bool has_credentials = false;
+  bool has_fragment = false;
+  bool has_search = false;
+};
+
+size_t count_ascii_bytes(const std::string &s) {
+  size_t counter = 0;
+  for (uint8_t c : s) {
+    if (c < 128) {
+      counter++;
+    }
+  }
+  return counter;
+}
+
+template <class result_type = ada::url_aggregator>
+std::vector<stat_numbers> collect_values(
+    const std::vector<std::string> &url_examples, size_t trials) {
+  std::vector<stat_numbers> numbers(url_examples.size());
+  for (size_t i = 0; i < url_examples.size(); i++) {
+    numbers[i].url_string = url_examples[i];
+    ada::result<result_type> url = ada::parse<result_type>(url_examples[i]);
+    if (url) {
+      numbers[i].is_valid = true;
+      numbers[i].href = url->get_href();
+      numbers[i].components = url->get_components();
+      numbers[i].has_port = url->has_port();
+      numbers[i].has_credentials = url->has_credentials();
+      numbers[i].has_fragment = url->has_hash();
+      numbers[i].has_search = url->has_search();
+    } else {
+      numbers[i].is_valid = false;
+    }
+  }
+  volatile size_t href_size = 0;
+  for (size_t i = 0; i < trials; i++) {
+    for (stat_numbers &n : numbers) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      ada::result<result_type> url = ada::parse<result_type>(n.url_string);
+      if (url) {
+        href_size += url->get_href().size();
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      n.counters << allocate_count;
+    }
+  }
+  return numbers;
+}
+
+#ifdef ADA_URL_FILE
+const char *default_file = ADA_URL_FILE;
+#else
+const char *default_file = nullptr;
+#endif
+
+std::vector<std::string> init_data(const char *input = default_file) {
+  std::vector<std::string> input_urls;
+  if (input == nullptr) {
+    return input_urls;
+  }
+
+  if (!file_exists(input)) {
+    std::cout << "File not found !" << input << std::endl;
+    return input_urls;
+  } else {
+    std::cout << "# Loading " << input << std::endl;
+    input_urls = split_string(read_file(input));
+  }
+  return input_urls;
+}
+
+void print(const stat_numbers &n) {
+  std::cout << std::setw(15) << n.url_string.size() << ",";
+  std::cout << std::setw(15) << n.counters.best.cycles() << "," << std::setw(15)
+            << size_t(n.counters.cycles()) << ",";
+  std::cout << std::setw(15) << n.counters.best.instructions() << ","
+            << std::setw(15) << n.counters.instructions() << ",";
+  std::cout << std::setw(15) << n.is_valid << ",";
+
+  // hash size
+
+  std::cout << std::setw(15) << n.href.size() << ",";
+  size_t end = n.href.size();
+  if (n.components.hash_start != ada::url_components::omitted) {
+    std::cout << std::setw(15) << (end - n.components.hash_start) << ",";
+    end = n.components.hash_start;
+  } else {
+    std::cout << std::setw(15) << 0 << ",";
+  }
+  // search size
+  if (n.components.search_start != ada::url_components::omitted) {
+    std::cout << std::setw(15) << (end - n.components.search_start) << ",";
+    end = n.components.search_start;
+  } else {
+    std::cout << std::setw(15) << 0 << ",";
+  }
+  // path size
+  std::cout << std::setw(15) << (end - n.components.pathname_start) << ",";
+  end = n.components.pathname_start;
+  // port size
+  std::cout << std::setw(15) << (end - n.components.host_end) << ",";
+  end = n.components.host_end;
+  // host size
+  std::cout << std::setw(15) << (end - n.components.host_start) << ",";
+  end = n.components.host_start;
+  // user/pass size
+  std::cout << std::setw(15) << (end - n.components.protocol_end) << ",";
+  end = n.components.protocol_end;
+  // protocol type
+  ada::result<ada::url_aggregator> url =
+      ada::parse<ada::url_aggregator>(n.url_string);
+  if (url) {
+    std::cout << std::setw(15) << int(url->type);
+  } else {
+    std::cout << std::setw(15) << -1;
+  }
+  std::cout << ",";
+  std::cout << std::setw(15) << n.has_port << ",";
+  std::cout << std::setw(15) << n.has_credentials << ",";
+  std::cout << std::setw(15) << n.has_fragment << ",";
+  std::cout << std::setw(15) << n.has_search << ",";
+  std::cout << std::setw(15)
+            << (n.url_string.size() - count_ascii_bytes(n.url_string)) << ",";
+  std::cout << std::setw(15) << (n.href.size() - count_ascii_bytes(n.href))
+            << ",";
+  std::cout << std::setw(15)
+            << (count_ascii_bytes(n.url_string) == n.url_string.size()) << ",";
+  std::cout << std::setw(15) << (n.href == n.url_string);
+}
+void print(const std::vector<stat_numbers> numbers) {
+  std::cout << std::setw(15) << "input_size"
+            << ",";
+  std::cout << std::setw(15) << "best_cycles"
+            << ",";
+  std::cout << std::setw(15) << "mean_cycles"
+            << ",";
+  std::cout << std::setw(15) << "best_instr"
+            << ",";
+  std::cout << std::setw(15) << "mean_instr"
+            << ",";
+  std::cout << std::setw(15) << "is_valid"
+            << ",";
+  std::cout << std::setw(15) << "href_size"
+            << ",";
+  std::cout << std::setw(15) << "hash_size"
+            << ",";
+  std::cout << std::setw(15) << "search_size"
+            << ",";
+  std::cout << std::setw(15) << "path_size"
+            << ",";
+  std::cout << std::setw(15) << "port_size"
+            << ",";
+  std::cout << std::setw(15) << "host_size"
+            << ",";
+  std::cout << std::setw(15) << "credential_size"
+            << ",";
+  std::cout << std::setw(15) << "protocol_type"
+            << ",";
+  std::cout << std::setw(15) << "has_port"
+            << ",";
+  std::cout << std::setw(15) << "has_authority"
+            << ",";
+  std::cout << std::setw(15) << "has_fragment"
+            << ",";
+  std::cout << std::setw(15) << "has_search"
+            << ",";
+  std::cout << std::setw(15) << "non_ascii_bytes"
+            << ",";
+  std::cout << std::setw(15) << "href_non_ascii_bytes"
+            << ",";
+  std::cout << std::setw(15) << "is_ascii"
+            << ",";
+  std::cout << std::setw(15) << "input_is_href";
+
+  std::cout << std::endl;
+
+  for (const stat_numbers &n : numbers) {
+    print(n);
+    std::cout << std::endl;
+  }
+}
+
+int main(int argc, char **argv) {
+  std::vector<std::string> input_urls;
+  if (argc == 1) {
+    input_urls = init_data();
+  } else {
+    input_urls = init_data(argv[1]);
+  }
+  if (input_urls.empty()) {
+    std::cout << "pass the path to a file containing a list of URL (one per "
+                 "line) as a parameter."
+              << std::endl;
+    return EXIT_FAILURE;
+  }
+  if (!collector.has_events()) {
+    std::cout << "We require access to performance counters. (Try sudo.)"
+              << std::endl;
+    return EXIT_FAILURE;
+  }
+  std::string empty;
+  // We always start with a null URL for calibration.
+  input_urls.insert(input_urls.begin(), empty);
+  bool use_ada_url = (getenv("USE_URL") != nullptr);
+  size_t trials = 100;
+  std::cout << "# trials " << trials << std::endl;
+  if (use_ada_url) {
+    std::cout << "# ada::url" << std::endl;
+    print(collect_values<ada::url>(input_urls, trials));
+  } else {
+    std::cout << "# ada::url_aggregator" << std::endl;
+    print(collect_values<ada::url_aggregator>(input_urls, trials));
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/benchmarks/percent_encode.cpp b/benchmarks/percent_encode.cpp
new file mode 100644 (file)
index 0000000..97f969f
--- /dev/null
@@ -0,0 +1,266 @@
+#include <memory>
+
+#include "ada.h"
+#include "ada/character_sets.h"
+#include "ada/unicode.h"
+#include "performancecounters/event_counter.h"
+event_collector collector;
+size_t N = 1000;
+
+#include <benchmark/benchmark.h>
+
+std::string examples[] = {"á|", "other:9818274x1!!",
+                          "ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_",
+                          "connect_timeout=10&application_name=myapp"};
+
+void init_data() {}
+
+double examples_bytes = []() -> double {
+  size_t bytes{0};
+  for (std::string& url_string : examples) {
+    bytes += url_string.size();
+  }
+  return double(bytes);
+}();
+
+static void Fragment(benchmark::State& state) {
+  for (auto _ : state) {
+    for (std::string& url_string : examples) {
+      benchmark::DoNotOptimize(ada::unicode::percent_encode(
+          url_string, ada::character_sets::FRAGMENT_PERCENT_ENCODE));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : examples) {
+        benchmark::DoNotOptimize(ada::unicode::percent_encode(
+            url_string, ada::character_sets::FRAGMENT_PERCENT_ENCODE));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(examples);
+    state.counters["instructions/cycle"] =
+        aggregate.total.instructions() / aggregate.total.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / examples_bytes;
+    state.counters["GHz"] =
+        aggregate.total.cycles() / aggregate.total.elapsed_ns();
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                          benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(Fragment);
+
+static void Query(benchmark::State& state) {
+  for (auto _ : state) {
+    for (std::string& url_string : examples) {
+      benchmark::DoNotOptimize(ada::unicode::percent_encode(
+          url_string, ada::character_sets::QUERY_PERCENT_ENCODE));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : examples) {
+        benchmark::DoNotOptimize(ada::unicode::percent_encode(
+            url_string, ada::character_sets::QUERY_PERCENT_ENCODE));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(examples);
+    state.counters["instructions/cycle"] =
+        aggregate.total.instructions() / aggregate.total.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / examples_bytes;
+    state.counters["GHz"] =
+        aggregate.total.cycles() / aggregate.total.elapsed_ns();
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                          benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(Query);
+
+static void SpecialQuery(benchmark::State& state) {
+  for (auto _ : state) {
+    for (std::string& url_string : examples) {
+      benchmark::DoNotOptimize(ada::unicode::percent_encode(
+          url_string, ada::character_sets::FRAGMENT_PERCENT_ENCODE));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : examples) {
+        benchmark::DoNotOptimize(ada::unicode::percent_encode(
+            url_string, ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(examples);
+    state.counters["instructions/cycle"] =
+        aggregate.total.instructions() / aggregate.total.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / examples_bytes;
+    state.counters["GHz"] =
+        aggregate.total.cycles() / aggregate.total.elapsed_ns();
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                          benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(SpecialQuery);
+
+static void UserInfo(benchmark::State& state) {
+  for (auto _ : state) {
+    for (std::string& url_string : examples) {
+      benchmark::DoNotOptimize(ada::unicode::percent_encode(
+          url_string, ada::character_sets::USERINFO_PERCENT_ENCODE));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : examples) {
+        benchmark::DoNotOptimize(ada::unicode::percent_encode(
+            url_string, ada::character_sets::USERINFO_PERCENT_ENCODE));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(examples);
+    state.counters["instructions/cycle"] =
+        aggregate.total.instructions() / aggregate.total.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / examples_bytes;
+    state.counters["GHz"] =
+        aggregate.total.cycles() / aggregate.total.elapsed_ns();
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                          benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(UserInfo);
+
+static void C0Control(benchmark::State& state) {
+  for (auto _ : state) {
+    for (std::string& url_string : examples) {
+      benchmark::DoNotOptimize(ada::unicode::percent_encode(
+          url_string, ada::character_sets::C0_CONTROL_PERCENT_ENCODE));
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (std::string& url_string : examples) {
+        benchmark::DoNotOptimize(ada::unicode::percent_encode(
+            url_string, ada::character_sets::C0_CONTROL_PERCENT_ENCODE));
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(examples);
+    state.counters["instructions/cycle"] =
+        aggregate.total.instructions() / aggregate.total.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / examples_bytes;
+    state.counters["GHz"] =
+        aggregate.total.cycles() / aggregate.total.elapsed_ns();
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                          benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(C0Control);
+
+int main(int argc, char** argv) {
+#if defined(ADA_RUST_VERSION)
+  benchmark::AddCustomContext("rust version ", ADA_RUST_VERSION);
+#endif
+#if (__APPLE__ && __aarch64__) || defined(__linux__)
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters",
+                                "No privileged access (sudo may help).");
+  }
+#else
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Unsupported system.");
+  }
+#endif
+  if (collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Enabled");
+  }
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+}
diff --git a/benchmarks/performancecounters/apple_arm_events.h b/benchmarks/performancecounters/apple_arm_events.h
new file mode 100644 (file)
index 0000000..6088172
--- /dev/null
@@ -0,0 +1,1110 @@
+/* clang-format off */
+
+// Original design from:
+// =============================================================================
+// XNU kperf/kpc
+// Available for 64-bit Intel/Apple Silicon, macOS/iOS, with root privileges
+//
+// References:
+//
+// XNU source (since xnu 2422.1.72):
+// https://github.com/apple/darwin-xnu/blob/main/osfmk/kern/kpc.h
+// https://github.com/apple/darwin-xnu/blob/main/bsd/kern/kern_kpc.c
+//
+// Lightweight PET (Profile Every Thread, since xnu 3789.1.32):
+// https://github.com/apple/darwin-xnu/blob/main/osfmk/kperf/pet.c
+// https://github.com/apple/darwin-xnu/blob/main/osfmk/kperf/kperf_kpc.c
+//
+// System Private frameworks (since macOS 10.11, iOS 8.0):
+// /System/Library/PrivateFrameworks/kperf.framework
+// /System/Library/PrivateFrameworks/kperfdata.framework
+//
+// Xcode framework (since Xcode 7.0):
+// /Applications/Xcode.app/Contents/SharedFrameworks/DVTInstrumentsFoundation.framework
+//
+// CPU database (plist files)
+// macOS (since macOS 10.11):
+//     /usr/share/kpep/<name>.plist
+// iOS (copied from Xcode, since iOS 10.0, Xcode 8.0):
+//     /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
+//     /DeviceSupport/<version>/DeveloperDiskImage.dmg/usr/share/kpep/<name>.plist
+//
+//
+// Created by YaoYuan <ibireme@gmail.com> on 2021.
+// Released into the public domain (unlicense.org).
+// =============================================================================
+
+#ifndef M1CYCLES_H
+#define M1CYCLES_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dlfcn.h>           // for dlopen() and dlsym()
+#include <mach/mach_time.h>  // for mach_absolute_time()
+#include <sys/kdebug.h>      // for kdebug trace decode
+#include <sys/sysctl.h>      // for sysctl()
+#include <unistd.h>          // for usleep()
+
+struct performance_counters {
+  double cycles;
+  double branches;
+  double missed_branches;
+  double instructions;
+  performance_counters(uint64_t c, uint64_t b, uint64_t m, uint64_t i)
+      : cycles(c), branches(b), missed_branches(m), instructions(i) {}
+  performance_counters(double c, double b, double m, double i)
+      : cycles(c), branches(b), missed_branches(m), instructions(i) {}
+  performance_counters(double init)
+      : cycles(init),
+        branches(init),
+        missed_branches(init),
+        instructions(init) {}
+
+  inline performance_counters &operator-=(const performance_counters &other) {
+    cycles -= other.cycles;
+    branches -= other.branches;
+    missed_branches -= other.missed_branches;
+    instructions -= other.instructions;
+    return *this;
+  }
+  inline performance_counters &min(const performance_counters &other) {
+    cycles = other.cycles < cycles ? other.cycles : cycles;
+    branches = other.branches < branches ? other.branches : branches;
+    missed_branches = other.missed_branches < missed_branches
+                          ? other.missed_branches
+                          : missed_branches;
+    instructions =
+        other.instructions < instructions ? other.instructions : instructions;
+    return *this;
+  }
+  inline performance_counters &operator+=(const performance_counters &other) {
+    cycles += other.cycles;
+    branches += other.branches;
+    missed_branches += other.missed_branches;
+    instructions += other.instructions;
+    return *this;
+  }
+
+  inline performance_counters &operator/=(double numerator) {
+    cycles /= numerator;
+    branches /= numerator;
+    missed_branches /= numerator;
+    instructions /= numerator;
+    return *this;
+  }
+};
+
+inline performance_counters operator-(const performance_counters &a,
+                                      const performance_counters &b) {
+  return performance_counters(a.cycles - b.cycles, a.branches - b.branches,
+                              a.missed_branches - b.missed_branches,
+                              a.instructions - b.instructions);
+}
+
+typedef float f32;
+typedef double f64;
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint16_t u16;
+typedef int32_t i32;
+typedef uint32_t u32;
+typedef int64_t i64;
+typedef uint64_t u64;
+typedef size_t usize;
+
+// -----------------------------------------------------------------------------
+// <kperf.framework> header (reverse engineered)
+// This framework wraps some sysctl calls to communicate with the kpc in kernel.
+// Most functions requires root privileges, or process is "blessed".
+// -----------------------------------------------------------------------------
+
+// Cross-platform class constants.
+#define KPC_CLASS_FIXED (0)
+#define KPC_CLASS_CONFIGURABLE (1)
+#define KPC_CLASS_POWER (2)
+#define KPC_CLASS_RAWPMU (3)
+
+// Cross-platform class mask constants.
+#define KPC_CLASS_FIXED_MASK (1u << KPC_CLASS_FIXED)                // 1
+#define KPC_CLASS_CONFIGURABLE_MASK (1u << KPC_CLASS_CONFIGURABLE)  // 2
+#define KPC_CLASS_POWER_MASK (1u << KPC_CLASS_POWER)                // 4
+#define KPC_CLASS_RAWPMU_MASK (1u << KPC_CLASS_RAWPMU)              // 8
+
+// PMU version constants.
+#define KPC_PMU_ERROR (0)      // Error
+#define KPC_PMU_INTEL_V3 (1)   // Intel
+#define KPC_PMU_ARM_APPLE (2)  // ARM64
+#define KPC_PMU_INTEL_V2 (3)   // Old Intel
+#define KPC_PMU_ARM_V2 (4)     // Old ARM
+
+// The maximum number of counters we could read from every class in one go.
+// ARMV7: FIXED: 1, CONFIGURABLE: 4
+// ARM32: FIXED: 2, CONFIGURABLE: 6
+// ARM64: FIXED: 2, CONFIGURABLE: CORE_NCTRS - FIXED (6 or 8)
+// x86: 32
+#define KPC_MAX_COUNTERS 32
+
+// Bits for defining what to do on an action.
+// Defined in https://github.com/apple/darwin-xnu/blob/main/osfmk/kperf/action.h
+#define KPERF_SAMPLER_TH_INFO (1U << 0)
+#define KPERF_SAMPLER_TH_SNAPSHOT (1U << 1)
+#define KPERF_SAMPLER_KSTACK (1U << 2)
+#define KPERF_SAMPLER_USTACK (1U << 3)
+#define KPERF_SAMPLER_PMC_THREAD (1U << 4)
+#define KPERF_SAMPLER_PMC_CPU (1U << 5)
+#define KPERF_SAMPLER_PMC_CONFIG (1U << 6)
+#define KPERF_SAMPLER_MEMINFO (1U << 7)
+#define KPERF_SAMPLER_TH_SCHEDULING (1U << 8)
+#define KPERF_SAMPLER_TH_DISPATCH (1U << 9)
+#define KPERF_SAMPLER_TK_SNAPSHOT (1U << 10)
+#define KPERF_SAMPLER_SYS_MEM (1U << 11)
+#define KPERF_SAMPLER_TH_INSCYC (1U << 12)
+#define KPERF_SAMPLER_TK_INFO (1U << 13)
+
+// Maximum number of kperf action ids.
+#define KPERF_ACTION_MAX (32)
+
+// Maximum number of kperf timer ids.
+#define KPERF_TIMER_MAX (8)
+
+// x86/arm config registers are 64-bit
+typedef u64 kpc_config_t;
+
+/// Print current CPU identification string to the buffer (same as snprintf),
+/// such as "cpu_7_8_10b282dc_46". This string can be used to locate the PMC
+/// database in /usr/share/kpep.
+/// @return string's length, or negative value if error occurs.
+/// @note This method does not requires root privileges.
+/// @details sysctl get(hw.cputype), get(hw.cpusubtype),
+///                 get(hw.cpufamily), get(machdep.cpu.model)
+static int (*kpc_cpu_string)(char *buf, usize buf_size);
+
+/// Get the version of KPC that's being run.
+/// @return See `PMU version constants` above.
+/// @details sysctl get(kpc.pmu_version)
+static u32 (*kpc_pmu_version)(void);
+
+/// Get running PMC classes.
+/// @return See `class mask constants` above,
+///         0 if error occurs or no class is set.
+/// @details sysctl get(kpc.counting)
+static u32 (*kpc_get_counting)(void);
+
+/// Set PMC classes to enable counting.
+/// @param classes See `class mask constants` above, set 0 to shutdown counting.
+/// @return 0 for success.
+/// @details sysctl set(kpc.counting)
+static int (*kpc_set_counting)(u32 classes);
+
+/// Get running PMC classes for current thread.
+/// @return See `class mask constants` above,
+///         0 if error occurs or no class is set.
+/// @details sysctl get(kpc.thread_counting)
+static u32 (*kpc_get_thread_counting)(void);
+
+/// Set PMC classes to enable counting for current thread.
+/// @param classes See `class mask constants` above, set 0 to shutdown counting.
+/// @return 0 for success.
+/// @details sysctl set(kpc.thread_counting)
+static int (*kpc_set_thread_counting)(u32 classes);
+
+/// Get how many config registers there are for a given mask.
+/// For example: Intel may returns 1 for `KPC_CLASS_FIXED_MASK`,
+///                        returns 4 for `KPC_CLASS_CONFIGURABLE_MASK`.
+/// @param classes See `class mask constants` above.
+/// @return 0 if error occurs or no class is set.
+/// @note This method does not requires root privileges.
+/// @details sysctl get(kpc.config_count)
+static u32 (*kpc_get_config_count)(u32 classes);
+
+/// Get config registers.
+/// @param classes see `class mask constants` above.
+/// @param config Config buffer to receive values, should not smaller than
+///               kpc_get_config_count(classes) * sizeof(kpc_config_t).
+/// @return 0 for success.
+/// @details sysctl get(kpc.config_count), get(kpc.config)
+static int (*kpc_get_config)(u32 classes, kpc_config_t *config);
+
+/// Set config registers.
+/// @param classes see `class mask constants` above.
+/// @param config Config buffer, should not smaller than
+///               kpc_get_config_count(classes) * sizeof(kpc_config_t).
+/// @return 0 for success.
+/// @details sysctl get(kpc.config_count), set(kpc.config)
+static int (*kpc_set_config)(u32 classes, kpc_config_t *config);
+
+/// Get how many counters there are for a given mask.
+/// For example: Intel may returns 3 for `KPC_CLASS_FIXED_MASK`,
+///                        returns 4 for `KPC_CLASS_CONFIGURABLE_MASK`.
+/// @param classes See `class mask constants` above.
+/// @note This method does not requires root privileges.
+/// @details sysctl get(kpc.counter_count)
+static u32 (*kpc_get_counter_count)(u32 classes);
+
+/// Get counter accumulations.
+/// If `all_cpus` is true, the buffer count should not smaller than
+/// (cpu_count * counter_count). Otherwise, the buffer count should not smaller
+/// than (counter_count).
+/// @see kpc_get_counter_count(), kpc_cpu_count().
+/// @param all_cpus true for all CPUs, false for current cpu.
+/// @param classes See `class mask constants` above.
+/// @param curcpu A pointer to receive current cpu id, can be NULL.
+/// @param buf Buffer to receive counter's value.
+/// @return 0 for success.
+/// @details sysctl get(hw.ncpu), get(kpc.counter_count), get(kpc.counters)
+static int (*kpc_get_cpu_counters)(bool all_cpus, u32 classes, int *curcpu,
+                                   u64 *buf);
+
+/// Get counter accumulations for current thread.
+/// @param tid Thread id, should be 0.
+/// @param buf_count The number of buf's elements (not bytes),
+///                  should not smaller than kpc_get_counter_count().
+/// @param buf Buffer to receive counter's value.
+/// @return 0 for success.
+/// @details sysctl get(kpc.thread_counters)
+static int (*kpc_get_thread_counters)(u32 tid, u32 buf_count, u64 *buf);
+
+/// Acquire/release the counters used by the Power Manager.
+/// @param val 1:acquire, 0:release
+/// @return 0 for success.
+/// @details sysctl set(kpc.force_all_ctrs)
+static int (*kpc_force_all_ctrs_set)(int val);
+
+/// Get the state of all_ctrs.
+/// @return 0 for success.
+/// @details sysctl get(kpc.force_all_ctrs)
+static int (*kpc_force_all_ctrs_get)(int *val_out);
+
+/// Set number of actions, should be `KPERF_ACTION_MAX`.
+/// @details sysctl set(kperf.action.count)
+static int (*kperf_action_count_set)(u32 count);
+
+/// Get number of actions.
+/// @details sysctl get(kperf.action.count)
+static int (*kperf_action_count_get)(u32 *count);
+
+/// Set what to sample when a trigger fires an action, e.g.
+/// `KPERF_SAMPLER_PMC_CPU`.
+/// @details sysctl set(kperf.action.samplers)
+static int (*kperf_action_samplers_set)(u32 actionid, u32 sample);
+
+/// Get what to sample when a trigger fires an action.
+/// @details sysctl get(kperf.action.samplers)
+static int (*kperf_action_samplers_get)(u32 actionid, u32 *sample);
+
+/// Apply a task filter to the action, -1 to disable filter.
+/// @details sysctl set(kperf.action.filter_by_task)
+static int (*kperf_action_filter_set_by_task)(u32 actionid, i32 port);
+
+/// Apply a pid filter to the action, -1 to disable filter.
+/// @details sysctl set(kperf.action.filter_by_pid)
+static int (*kperf_action_filter_set_by_pid)(u32 actionid, i32 pid);
+
+/// Set number of time triggers, should be `KPERF_TIMER_MAX`.
+/// @details sysctl set(kperf.timer.count)
+static int (*kperf_timer_count_set)(u32 count);
+
+/// Get number of time triggers.
+/// @details sysctl get(kperf.timer.count)
+static int (*kperf_timer_count_get)(u32 *count);
+
+/// Set timer number and period.
+/// @details sysctl set(kperf.timer.period)
+static int (*kperf_timer_period_set)(u32 actionid, u64 tick);
+
+/// Get timer number and period.
+/// @details sysctl get(kperf.timer.period)
+static int (*kperf_timer_period_get)(u32 actionid, u64 *tick);
+
+/// Set timer number and actionid.
+/// @details sysctl set(kperf.timer.action)
+static int (*kperf_timer_action_set)(u32 actionid, u32 timerid);
+
+/// Get timer number and actionid.
+/// @details sysctl get(kperf.timer.action)
+static int (*kperf_timer_action_get)(u32 actionid, u32 *timerid);
+
+/// Set which timer ID does PET (Profile Every Thread).
+/// @details sysctl set(kperf.timer.pet_timer)
+static int (*kperf_timer_pet_set)(u32 timerid);
+
+/// Get which timer ID does PET (Profile Every Thread).
+/// @details sysctl get(kperf.timer.pet_timer)
+static int (*kperf_timer_pet_get)(u32 *timerid);
+
+/// Enable or disable sampling.
+/// @details sysctl set(kperf.sampling)
+static int (*kperf_sample_set)(u32 enabled);
+
+/// Get is currently sampling.
+/// @details sysctl get(kperf.sampling)
+static int (*kperf_sample_get)(u32 *enabled);
+
+/// Reset kperf: stop sampling, kdebug, timers and actions.
+/// @return 0 for success.
+static int (*kperf_reset)(void);
+
+/// Nanoseconds to CPU ticks.
+static u64 (*kperf_ns_to_ticks)(u64 ns);
+
+/// CPU ticks to nanoseconds.
+static u64 (*kperf_ticks_to_ns)(u64 ticks);
+
+/// CPU ticks frequency (mach_absolute_time).
+static u64 (*kperf_tick_frequency)(void);
+
+/// Get lightweight PET mode (not in kperf.framework).
+static int kperf_lightweight_pet_get(u32 *enabled) {
+  if (!enabled) return -1;
+  usize size = 4;
+  return sysctlbyname("kperf.lightweight_pet", enabled, &size, NULL, 0);
+}
+
+/// Set lightweight PET mode (not in kperf.framework).
+static int kperf_lightweight_pet_set(u32 enabled) {
+  return sysctlbyname("kperf.lightweight_pet", NULL, NULL, &enabled, 4);
+}
+
+// -----------------------------------------------------------------------------
+// <kperfdata.framework> header (reverse engineered)
+// This framework provides some functions to access the local CPU database.
+// These functions do not require root privileges.
+// -----------------------------------------------------------------------------
+
+// KPEP CPU architecture constants.
+#define KPEP_ARCH_I386 0
+#define KPEP_ARCH_X86_64 1
+#define KPEP_ARCH_ARM 2
+#define KPEP_ARCH_ARM64 3
+
+/// KPEP event (size: 48/28 bytes on 64/32 bit OS)
+typedef struct kpep_event {
+  const char *name;  ///< Unique name of a event, such as "INST_RETIRED.ANY".
+  const char *description;  ///< Description for this event.
+  const char *errata;       ///< Errata, currently NULL.
+  const char *alias;        ///< Alias name, such as "Instructions", "Cycles".
+  const char *fallback;     ///< Fallback event name for fixed counter.
+  u32 mask;
+  u8 number;
+  u8 umask;
+  u8 reserved;
+  u8 is_fixed;
+} kpep_event;
+
+/// KPEP database (size: 144/80 bytes on 64/32 bit OS)
+typedef struct kpep_db {
+  const char *name;            ///< Database name, such as "haswell".
+  const char *cpu_id;          ///< Plist name, such as "cpu_7_8_10b282dc".
+  const char *marketing_name;  ///< Marketing name, such as "Intel Haswell".
+  void *plist_data;            ///< Plist data (CFDataRef), currently NULL.
+  void *event_map;  ///< All events (CFDict<CFSTR(event_name), kpep_event *>).
+  kpep_event
+      *event_arr;  ///< Event struct buffer (sizeof(kpep_event) * events_count).
+  kpep_event **fixed_event_arr;  ///< Fixed counter events (sizeof(kpep_event *)
+                                 ///< * fixed_counter_count)
+  void *alias_map;  ///< All aliases (CFDict<CFSTR(event_name), kpep_event *>).
+  usize reserved_1;
+  usize reserved_2;
+  usize reserved_3;
+  usize event_count;  ///< All events count.
+  usize alias_count;
+  usize fixed_counter_count;
+  usize config_counter_count;
+  usize power_counter_count;
+  u32 architecture;  ///< see `KPEP CPU architecture constants` above.
+  u32 fixed_counter_bits;
+  u32 config_counter_bits;
+  u32 power_counter_bits;
+} kpep_db;
+
+/// KPEP config (size: 80/44 bytes on 64/32 bit OS)
+typedef struct kpep_config {
+  kpep_db *db;
+  kpep_event **ev_arr;  ///< (sizeof(kpep_event *) * counter_count), init NULL
+  usize *ev_map;        ///< (sizeof(usize *) * counter_count), init 0
+  usize *ev_idx;        ///< (sizeof(usize *) * counter_count), init -1
+  u32 *flags;           ///< (sizeof(u32 *) * counter_count), init 0
+  u64 *kpc_periods;     ///< (sizeof(u64 *) * counter_count), init 0
+  usize event_count;    /// kpep_config_events_count()
+  usize counter_count;
+  u32 classes;  ///< See `class mask constants` above.
+  u32 config_counter;
+  u32 power_counter;
+  u32 reserved;
+} kpep_config;
+
+/// Error code for kpep_config_xxx() and kpep_db_xxx() functions.
+typedef enum {
+  KPEP_CONFIG_ERROR_NONE = 0,
+  KPEP_CONFIG_ERROR_INVALID_ARGUMENT = 1,
+  KPEP_CONFIG_ERROR_OUT_OF_MEMORY = 2,
+  KPEP_CONFIG_ERROR_IO = 3,
+  KPEP_CONFIG_ERROR_BUFFER_TOO_SMALL = 4,
+  KPEP_CONFIG_ERROR_CUR_SYSTEM_UNKNOWN = 5,
+  KPEP_CONFIG_ERROR_DB_PATH_INVALID = 6,
+  KPEP_CONFIG_ERROR_DB_NOT_FOUND = 7,
+  KPEP_CONFIG_ERROR_DB_ARCH_UNSUPPORTED = 8,
+  KPEP_CONFIG_ERROR_DB_VERSION_UNSUPPORTED = 9,
+  KPEP_CONFIG_ERROR_DB_CORRUPT = 10,
+  KPEP_CONFIG_ERROR_EVENT_NOT_FOUND = 11,
+  KPEP_CONFIG_ERROR_CONFLICTING_EVENTS = 12,
+  KPEP_CONFIG_ERROR_COUNTERS_NOT_FORCED = 13,
+  KPEP_CONFIG_ERROR_EVENT_UNAVAILABLE = 14,
+  KPEP_CONFIG_ERROR_ERRNO = 15,
+  KPEP_CONFIG_ERROR_MAX
+} kpep_config_error_code;
+
+/// Error description for kpep_config_error_code.
+static const char *kpep_config_error_names[KPEP_CONFIG_ERROR_MAX] = {
+    "none",
+    "invalid argument",
+    "out of memory",
+    "I/O",
+    "buffer too small",
+    "current system unknown",
+    "database path invalid",
+    "database not found",
+    "database architecture unsupported",
+    "database version unsupported",
+    "database corrupt",
+    "event not found",
+    "conflicting events",
+    "all counters must be forced",
+    "event unavailable",
+    "check errno"};
+
+/// Error description.
+static const char *kpep_config_error_desc(int code) {
+  if (0 <= code && code < KPEP_CONFIG_ERROR_MAX) {
+    return kpep_config_error_names[code];
+  }
+  return "unknown error";
+}
+
+/// Create a config.
+/// @param db A kpep db, see kpep_db_create()
+/// @param cfg_ptr A pointer to receive the new config.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_create)(kpep_db *db, kpep_config **cfg_ptr);
+
+/// Free the config.
+static void (*kpep_config_free)(kpep_config *cfg);
+
+/// Add an event to config.
+/// @param cfg The config.
+/// @param ev_ptr A event pointer.
+/// @param flag 0: all, 1: user space only
+/// @param err Error bitmap pointer, can be NULL.
+///            If return value is `CONFLICTING_EVENTS`, this bitmap contains
+///            the conflicted event indices, e.g. "1 << 2" means index 2.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_add_event)(kpep_config *cfg, kpep_event **ev_ptr,
+                                    u32 flag, u32 *err);
+
+/// Remove event at index.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_remove_event)(kpep_config *cfg, usize idx);
+
+/// Force all counters.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_force_counters)(kpep_config *cfg);
+
+/// Get events count.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_events_count)(kpep_config *cfg, usize *count_ptr);
+
+/// Get all event pointers.
+/// @param buf A buffer to receive event pointers.
+/// @param buf_size The buffer's size in bytes, should not smaller than
+///                 kpep_config_events_count() * sizeof(void *).
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_events)(kpep_config *cfg, kpep_event **buf,
+                                 usize buf_size);
+
+/// Get kpc register configs.
+/// @param buf A buffer to receive kpc register configs.
+/// @param buf_size The buffer's size in bytes, should not smaller than
+///                 kpep_config_kpc_count() * sizeof(kpc_config_t).
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_kpc)(kpep_config *cfg, kpc_config_t *buf,
+                              usize buf_size);
+
+/// Get kpc register config count.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_kpc_count)(kpep_config *cfg, usize *count_ptr);
+
+/// Get kpc classes.
+/// @param classes See `class mask constants` above.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_kpc_classes)(kpep_config *cfg, u32 *classes_ptr);
+
+/// Get the index mapping from event to counter.
+/// @param buf A buffer to receive indexes.
+/// @param buf_size The buffer's size in bytes, should not smaller than
+///                 kpep_config_events_count() * sizeof(kpc_config_t).
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_config_kpc_map)(kpep_config *cfg, usize *buf, usize buf_size);
+
+/// Open a kpep database file in "/usr/share/kpep/" or "/usr/local/share/kpep/".
+/// @param name File name, for example "haswell", "cpu_100000c_1_92fb37c8".
+///             Pass NULL for current CPU.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_create)(const char *name, kpep_db **db_ptr);
+
+/// Free the kpep database.
+static void (*kpep_db_free)(kpep_db *db);
+
+/// Get the database's name.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_name)(kpep_db *db, const char **name);
+
+/// Get the event alias count.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_aliases_count)(kpep_db *db, usize *count);
+
+/// Get all alias.
+/// @param buf A buffer to receive all alias strings.
+/// @param buf_size The buffer's size in bytes,
+///        should not smaller than kpep_db_aliases_count() * sizeof(void *).
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_aliases)(kpep_db *db, const char **buf, usize buf_size);
+
+/// Get counters count for given classes.
+/// @param classes 1: Fixed, 2: Configurable.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_counters_count)(kpep_db *db, u8 classes, usize *count);
+
+/// Get all event count.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_events_count)(kpep_db *db, usize *count);
+
+/// Get all events.
+/// @param buf A buffer to receive all event pointers.
+/// @param buf_size The buffer's size in bytes,
+///        should not smaller than kpep_db_events_count() * sizeof(void *).
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_events)(kpep_db *db, kpep_event **buf, usize buf_size);
+
+/// Get one event by name.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_db_event)(kpep_db *db, const char *name, kpep_event **ev_ptr);
+
+/// Get event's name.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_event_name)(kpep_event *ev, const char **name_ptr);
+
+/// Get event's alias.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_event_alias)(kpep_event *ev, const char **alias_ptr);
+
+/// Get event's description.
+/// @return kpep_config_error_code, 0 for success.
+static int (*kpep_event_description)(kpep_event *ev, const char **str_ptr);
+
+// -----------------------------------------------------------------------------
+// load kperf/kperfdata dynamic library
+// -----------------------------------------------------------------------------
+
+typedef struct {
+  const char *name;
+  void **impl;
+} lib_symbol;
+
+#define lib_nelems(x) (sizeof(x) / sizeof((x)[0]))
+#define lib_symbol_def(name) \
+  { #name, (void **)&name }
+
+static const lib_symbol lib_symbols_kperf[] = {
+    lib_symbol_def(kpc_pmu_version),
+    lib_symbol_def(kpc_cpu_string),
+    lib_symbol_def(kpc_set_counting),
+    lib_symbol_def(kpc_get_counting),
+    lib_symbol_def(kpc_set_thread_counting),
+    lib_symbol_def(kpc_get_thread_counting),
+    lib_symbol_def(kpc_get_config_count),
+    lib_symbol_def(kpc_get_counter_count),
+    lib_symbol_def(kpc_set_config),
+    lib_symbol_def(kpc_get_config),
+    lib_symbol_def(kpc_get_cpu_counters),
+    lib_symbol_def(kpc_get_thread_counters),
+    lib_symbol_def(kpc_force_all_ctrs_set),
+    lib_symbol_def(kpc_force_all_ctrs_get),
+    lib_symbol_def(kperf_action_count_set),
+    lib_symbol_def(kperf_action_count_get),
+    lib_symbol_def(kperf_action_samplers_set),
+    lib_symbol_def(kperf_action_samplers_get),
+    lib_symbol_def(kperf_action_filter_set_by_task),
+    lib_symbol_def(kperf_action_filter_set_by_pid),
+    lib_symbol_def(kperf_timer_count_set),
+    lib_symbol_def(kperf_timer_count_get),
+    lib_symbol_def(kperf_timer_period_set),
+    lib_symbol_def(kperf_timer_period_get),
+    lib_symbol_def(kperf_timer_action_set),
+    lib_symbol_def(kperf_timer_action_get),
+    lib_symbol_def(kperf_sample_set),
+    lib_symbol_def(kperf_sample_get),
+    lib_symbol_def(kperf_reset),
+    lib_symbol_def(kperf_timer_pet_set),
+    lib_symbol_def(kperf_timer_pet_get),
+    lib_symbol_def(kperf_ns_to_ticks),
+    lib_symbol_def(kperf_ticks_to_ns),
+    lib_symbol_def(kperf_tick_frequency),
+};
+
+static const lib_symbol lib_symbols_kperfdata[] = {
+    lib_symbol_def(kpep_config_create),
+    lib_symbol_def(kpep_config_free),
+    lib_symbol_def(kpep_config_add_event),
+    lib_symbol_def(kpep_config_remove_event),
+    lib_symbol_def(kpep_config_force_counters),
+    lib_symbol_def(kpep_config_events_count),
+    lib_symbol_def(kpep_config_events),
+    lib_symbol_def(kpep_config_kpc),
+    lib_symbol_def(kpep_config_kpc_count),
+    lib_symbol_def(kpep_config_kpc_classes),
+    lib_symbol_def(kpep_config_kpc_map),
+    lib_symbol_def(kpep_db_create),
+    lib_symbol_def(kpep_db_free),
+    lib_symbol_def(kpep_db_name),
+    lib_symbol_def(kpep_db_aliases_count),
+    lib_symbol_def(kpep_db_aliases),
+    lib_symbol_def(kpep_db_counters_count),
+    lib_symbol_def(kpep_db_events_count),
+    lib_symbol_def(kpep_db_events),
+    lib_symbol_def(kpep_db_event),
+    lib_symbol_def(kpep_event_name),
+    lib_symbol_def(kpep_event_alias),
+    lib_symbol_def(kpep_event_description),
+};
+
+#define lib_path_kperf "/System/Library/PrivateFrameworks/kperf.framework/kperf"
+#define lib_path_kperfdata \
+  "/System/Library/PrivateFrameworks/kperfdata.framework/kperfdata"
+
+static bool lib_inited = false;
+static bool lib_has_err = false;
+static char lib_err_msg[256];
+
+static void *lib_handle_kperf = NULL;
+static void *lib_handle_kperfdata = NULL;
+
+static void lib_deinit(void) {
+  lib_inited = false;
+  lib_has_err = false;
+  if (lib_handle_kperf) dlclose(lib_handle_kperf);
+  if (lib_handle_kperfdata) dlclose(lib_handle_kperfdata);
+  lib_handle_kperf = NULL;
+  lib_handle_kperfdata = NULL;
+  for (usize i = 0; i < lib_nelems(lib_symbols_kperf); i++) {
+    const lib_symbol *symbol = &lib_symbols_kperf[i];
+    *symbol->impl = NULL;
+  }
+  for (usize i = 0; i < lib_nelems(lib_symbols_kperfdata); i++) {
+    const lib_symbol *symbol = &lib_symbols_kperfdata[i];
+    *symbol->impl = NULL;
+  }
+}
+
+static bool lib_init(void) {
+#define return_err()    \
+  do {                  \
+    lib_deinit();       \
+    lib_inited = true;  \
+    lib_has_err = true; \
+    return false;       \
+  } while (false)
+
+  if (lib_inited) return !lib_has_err;
+
+  // load dynamic library
+  lib_handle_kperf = dlopen(lib_path_kperf, RTLD_LAZY);
+  if (!lib_handle_kperf) {
+    snprintf(lib_err_msg, sizeof(lib_err_msg),
+             "Failed to load kperf.framework, message: %s.", dlerror());
+    return_err();
+  }
+  lib_handle_kperfdata = dlopen(lib_path_kperfdata, RTLD_LAZY);
+  if (!lib_handle_kperfdata) {
+    snprintf(lib_err_msg, sizeof(lib_err_msg),
+             "Failed to load kperfdata.framework, message: %s.", dlerror());
+    return_err();
+  }
+
+  // load symbol address from dynamic library
+  for (usize i = 0; i < lib_nelems(lib_symbols_kperf); i++) {
+    const lib_symbol *symbol = &lib_symbols_kperf[i];
+    *symbol->impl = dlsym(lib_handle_kperf, symbol->name);
+    if (!*symbol->impl) {
+      snprintf(lib_err_msg, sizeof(lib_err_msg),
+               "Failed to load kperf function: %s.", symbol->name);
+      return_err();
+    }
+  }
+  for (usize i = 0; i < lib_nelems(lib_symbols_kperfdata); i++) {
+    const lib_symbol *symbol = &lib_symbols_kperfdata[i];
+    *symbol->impl = dlsym(lib_handle_kperfdata, symbol->name);
+    if (!*symbol->impl) {
+      snprintf(lib_err_msg, sizeof(lib_err_msg),
+               "Failed to load kperfdata function: %s.", symbol->name);
+      return_err();
+    }
+  }
+
+  lib_inited = true;
+  lib_has_err = false;
+  return true;
+
+#undef return_err
+}
+
+// -----------------------------------------------------------------------------
+// kdebug private structs
+// https://github.com/apple/darwin-xnu/blob/main/bsd/sys_private/kdebug_private.h
+// -----------------------------------------------------------------------------
+
+/*
+ * Ensure that both LP32 and LP64 variants of arm64 use the same kd_buf
+ * structure.
+ */
+#if defined(__arm64__)
+typedef uint64_t kd_buf_argtype;
+#else
+typedef uintptr_t kd_buf_argtype;
+#endif
+
+typedef struct {
+  uint64_t timestamp;
+  kd_buf_argtype arg1;
+  kd_buf_argtype arg2;
+  kd_buf_argtype arg3;
+  kd_buf_argtype arg4;
+  kd_buf_argtype arg5; /* the thread ID */
+  uint32_t debugid;    /* see <sys/kdebug.h> */
+
+/*
+ * Ensure that both LP32 and LP64 variants of arm64 use the same kd_buf
+ * structure.
+ */
+#if defined(__LP64__) || defined(__arm64__)
+  uint32_t cpuid; /* cpu index, from 0 */
+  kd_buf_argtype unused;
+#endif
+} kd_buf;
+
+/* bits for the type field of kd_regtype */
+#define KDBG_CLASSTYPE 0x10000
+#define KDBG_SUBCLSTYPE 0x20000
+#define KDBG_RANGETYPE 0x40000
+#define KDBG_TYPENONE 0x80000
+#define KDBG_CKTYPES 0xF0000
+
+/* only trace at most 4 types of events, at the code granularity */
+#define KDBG_VALCHECK 0x00200000U
+
+typedef struct {
+  unsigned int type;
+  unsigned int value1;
+  unsigned int value2;
+  unsigned int value3;
+  unsigned int value4;
+} kd_regtype;
+
+typedef struct {
+  /* number of events that can fit in the buffers */
+  int nkdbufs;
+  /* set if trace is disabled */
+  int nolog;
+  /* kd_ctrl_page.flags */
+  unsigned int flags;
+  /* number of threads in thread map */
+  int nkdthreads;
+  /* the owning pid */
+  int bufid;
+} kbufinfo_t;
+
+// -----------------------------------------------------------------------------
+// kdebug utils
+// -----------------------------------------------------------------------------
+
+/// Clean up trace buffers and reset ktrace/kdebug/kperf.
+/// @return 0 on success.
+static int kdebug_reset(void) {
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDREMOVE};
+  return sysctl(mib, 3, NULL, NULL, NULL, 0);
+}
+
+/// Disable and reinitialize the trace buffers.
+/// @return 0 on success.
+static int kdebug_reinit(void) {
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDSETUP};
+  return sysctl(mib, 3, NULL, NULL, NULL, 0);
+}
+
+/// Set debug filter.
+static int kdebug_setreg(kd_regtype *kdr) {
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDSETREG};
+  usize size = sizeof(kd_regtype);
+  return sysctl(mib, 3, kdr, &size, NULL, 0);
+}
+
+/// Set maximum number of trace entries (kd_buf).
+/// Only allow allocation up to half the available memory (sane_size).
+/// @return 0 on success.
+static int kdebug_trace_setbuf(int nbufs) {
+  int mib[4] = {CTL_KERN, KERN_KDEBUG, KERN_KDSETBUF, nbufs};
+  return sysctl(mib, 4, NULL, NULL, NULL, 0);
+}
+
+/// Enable or disable kdebug trace.
+/// Trace buffer must already be initialized.
+/// @return 0 on success.
+static int kdebug_trace_enable(bool enable) {
+  int mib[4] = {CTL_KERN, KERN_KDEBUG, KERN_KDENABLE, enable};
+  return sysctl(mib, 4, NULL, 0, NULL, 0);
+}
+
+/// Retrieve trace buffer information from kernel.
+/// @return 0 on success.
+static int kdebug_get_bufinfo(kbufinfo_t *info) {
+  if (!info) return -1;
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDGETBUF};
+  size_t needed = sizeof(kbufinfo_t);
+  return sysctl(mib, 3, info, &needed, NULL, 0);
+}
+
+/// Retrieve trace buffers from kernel.
+/// @param buf Memory to receive buffer data, array of `kd_buf`.
+/// @param len Length of `buf` in bytes.
+/// @param count Number of trace entries (kd_buf) obtained.
+/// @return 0 on success.
+static int kdebug_trace_read(void *buf, usize len, usize *count) {
+  if (count) *count = 0;
+  if (!buf || !len) return -1;
+
+  // Note: the input and output units are not the same.
+  // input: bytes
+  // output: number of kd_buf
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDREADTR};
+  int ret = sysctl(mib, 3, buf, &len, NULL, 0);
+  if (ret != 0) return ret;
+  *count = len;
+  return 0;
+}
+
+/// Block until there are new buffers filled or `timeout_ms` have passed.
+/// @param timeout_ms timeout milliseconds, 0 means wait forever.
+/// @param suc set true if new buffers filled.
+/// @return 0 on success.
+static int kdebug_wait(usize timeout_ms, bool *suc) {
+  if (timeout_ms == 0) return -1;
+  int mib[3] = {CTL_KERN, KERN_KDEBUG, KERN_KDBUFWAIT};
+  usize val = timeout_ms;
+  int ret = sysctl(mib, 3, NULL, &val, NULL, 0);
+  if (suc) *suc = !!val;
+  return ret;
+}
+
+// -----------------------------------------------------------------------------
+// Demo
+// -----------------------------------------------------------------------------
+
+#define EVENT_NAME_MAX 8
+typedef struct {
+  const char *alias;                  /// name for print
+  const char *names[EVENT_NAME_MAX];  /// name from pmc db
+} event_alias;
+
+/// Event names from /usr/share/kpep/<name>.plist
+static const event_alias profile_events[] = {
+    {"cycles",
+     {
+         "FIXED_CYCLES",             // Apple A7-A15
+         "CPU_CLK_UNHALTED.THREAD",  // Intel Core 1th-10th
+         "CPU_CLK_UNHALTED.CORE",    // Intel Yonah, Merom
+     }},
+    {"instructions",
+     {
+         "FIXED_INSTRUCTIONS",  // Apple A7-A15
+         "INST_RETIRED.ANY"     // Intel Yonah, Merom, Core 1th-10th
+     }},
+    {"branches",
+     {
+         "INST_BRANCH",                   // Apple A7-A15
+         "BR_INST_RETIRED.ALL_BRANCHES",  // Intel Core 1th-10th
+         "INST_RETIRED.ANY",              // Intel Yonah, Merom
+     }},
+    {"branch-misses",
+     {
+         "BRANCH_MISPRED_NONSPEC",  // Apple A7-A15, since iOS 15, macOS 12
+         "BRANCH_MISPREDICT",       // Apple A7-A14
+         "BR_MISP_RETIRED.ALL_BRANCHES",  // Intel Core 2th-10th
+         "BR_INST_RETIRED.MISPRED",       // Intel Yonah, Merom
+     }},
+};
+
+static kpep_event *get_event(kpep_db *db, const event_alias *alias) {
+  for (usize j = 0; j < EVENT_NAME_MAX; j++) {
+    const char *name = alias->names[j];
+    if (!name) break;
+    kpep_event *ev = NULL;
+    if (kpep_db_event(db, name, &ev) == 0) {
+      return ev;
+    }
+  }
+  return NULL;
+}
+
+struct AppleEvents {
+  kpc_config_t regs[KPC_MAX_COUNTERS] = {0};
+  usize counter_map[KPC_MAX_COUNTERS] = {0};
+  u64 counters_0[KPC_MAX_COUNTERS] = {0};
+  u64 counters_1[KPC_MAX_COUNTERS] = {0};
+  static constexpr usize ev_count =
+      sizeof(profile_events) / sizeof(profile_events[0]);
+  bool init = false;
+  bool worked = false;
+  inline bool setup_performance_counters() {
+    if (init) {
+      return worked;
+    }
+    init = true;
+
+    // load dylib
+    if (!lib_init()) {
+      printf("Error: %s\n", lib_err_msg);
+      return (worked = false);
+    }
+
+    // check permission
+    int force_ctrs = 0;
+    if (kpc_force_all_ctrs_get(&force_ctrs)) {
+      printf("Permission denied, xnu/kpc requires root privileges.\n");
+      return (worked = false);
+    }
+    int ret;
+    // load pmc db
+    kpep_db *db = NULL;
+    if ((ret = kpep_db_create(NULL, &db))) {
+      printf("Error: cannot load pmc database: %d.\n", ret);
+      return (worked = false);
+    }
+    printf("loaded db: %s (%s)\n", db->name, db->marketing_name);
+    // printf("number of fixed counters: %zu\n", db->fixed_counter_count);
+    // printf("number of configurable counters: %zu\n",
+    // db->config_counter_count);
+
+    // create a config
+    kpep_config *cfg = NULL;
+    if ((ret = kpep_config_create(db, &cfg))) {
+      printf("Failed to create kpep config: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+    if ((ret = kpep_config_force_counters(cfg))) {
+      printf("Failed to force counters: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+
+    // get events
+    kpep_event *ev_arr[ev_count] = {0};
+    for (usize i = 0; i < ev_count; i++) {
+      const event_alias *alias = profile_events + i;
+      ev_arr[i] = get_event(db, alias);
+      if (!ev_arr[i]) {
+        printf("Cannot find event: %s.\n", alias->alias);
+        return (worked = false);
+      }
+    }
+
+    // add event to config
+    for (usize i = 0; i < ev_count; i++) {
+      kpep_event *ev = ev_arr[i];
+      if ((ret = kpep_config_add_event(cfg, &ev, 0, NULL))) {
+        printf("Failed to add event: %d (%s).\n", ret,
+               kpep_config_error_desc(ret));
+        return (worked = false);
+      }
+    }
+
+    // prepare buffer and config
+    u32 classes = 0;
+    usize reg_count = 0;
+    if ((ret = kpep_config_kpc_classes(cfg, &classes))) {
+      printf("Failed get kpc classes: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+    if ((ret = kpep_config_kpc_count(cfg, &reg_count))) {
+      printf("Failed get kpc count: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+    if ((ret = kpep_config_kpc_map(cfg, counter_map, sizeof(counter_map)))) {
+      printf("Failed get kpc map: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+    if ((ret = kpep_config_kpc(cfg, regs, sizeof(regs)))) {
+      printf("Failed get kpc registers: %d (%s).\n", ret,
+             kpep_config_error_desc(ret));
+      return (worked = false);
+    }
+
+    // set config to kernel
+    if ((ret = kpc_force_all_ctrs_set(1))) {
+      printf("Failed force all ctrs: %d.\n", ret);
+      return (worked = false);
+    }
+    if ((classes & KPC_CLASS_CONFIGURABLE_MASK) && reg_count) {
+      if ((ret = kpc_set_config(classes, regs))) {
+        printf("Failed set kpc config: %d.\n", ret);
+        return (worked = false);
+      }
+    }
+
+    // start counting
+    if ((ret = kpc_set_counting(classes))) {
+      printf("Failed set counting: %d.\n", ret);
+      return (worked = false);
+    }
+    if ((ret = kpc_set_thread_counting(classes))) {
+      printf("Failed set thread counting: %d.\n", ret);
+      return (worked = false);
+    }
+
+    return (worked = true);
+  }
+
+  inline performance_counters get_counters() {
+    static bool warned = false;
+    int ret;
+    // get counters before
+    if ((ret = kpc_get_thread_counters(0, KPC_MAX_COUNTERS, counters_0))) {
+      if (!warned) {
+        printf("Failed get thread counters before: %d.\n", ret);
+        warned = true;
+      }
+      return 1;
+    }
+    /*
+    // We could print it out this way if we wanted to:
+    printf("counters value:\n");
+    for (usize i = 0; i < ev_count; i++) {
+        const event_alias *alias = profile_events + i;
+        usize idx = counter_map[i];
+        u64 val = counters_1[idx] - counters_0[idx];
+        printf("%14s: %llu\n", alias->alias, val);
+    }*/
+    return performance_counters{
+        counters_0[counter_map[0]], counters_0[counter_map[2]],
+        counters_0[counter_map[2]], counters_0[counter_map[1]]};
+  }
+};
+
+#endif
diff --git a/benchmarks/performancecounters/event_counter.h b/benchmarks/performancecounters/event_counter.h
new file mode 100644 (file)
index 0000000..4a1e5ec
--- /dev/null
@@ -0,0 +1,155 @@
+#ifndef __EVENT_COUNTER_H
+#define __EVENT_COUNTER_H
+
+#include <cctype>
+#ifndef _MSC_VER
+#include <dirent.h>
+#endif
+#include <cinttypes>
+
+#include <cstring>
+
+#include <chrono>
+#include <vector>
+
+#include "linux-perf-events.h"
+#ifdef __linux__
+#include <libgen.h>
+#endif
+
+#if __APPLE__ && __aarch64__
+#include "apple_arm_events.h"
+#endif
+
+struct event_count {
+  std::chrono::duration<double> elapsed;
+  std::vector<unsigned long long> event_counts;
+  event_count() : elapsed(0), event_counts{0, 0, 0, 0, 0} {}
+  event_count(const std::chrono::duration<double> _elapsed,
+              const std::vector<unsigned long long> _event_counts)
+      : elapsed(_elapsed), event_counts(_event_counts) {}
+  event_count(const event_count& other)
+      : elapsed(other.elapsed), event_counts(other.event_counts) {}
+
+  // The types of counters (so we can read the getter more easily)
+  enum event_counter_types {
+    CPU_CYCLES,
+    INSTRUCTIONS,
+    BRANCH_MISSES = 2,
+    BRANCH = 4
+  };
+
+  double elapsed_sec() const {
+    return std::chrono::duration<double>(elapsed).count();
+  }
+  double elapsed_ns() const {
+    return std::chrono::duration<double, std::nano>(elapsed).count();
+  }
+  double cycles() const {
+    return static_cast<double>(event_counts[CPU_CYCLES]);
+  }
+  double instructions() const {
+    return static_cast<double>(event_counts[INSTRUCTIONS]);
+  }
+  double branches() const { return static_cast<double>(event_counts[BRANCH]); }
+  double branch_misses() const {
+    return static_cast<double>(event_counts[BRANCH_MISSES]);
+  }
+  event_count& operator=(const event_count& other) {
+    this->elapsed = other.elapsed;
+    this->event_counts = other.event_counts;
+    return *this;
+  }
+  event_count operator+(const event_count& other) const {
+    return event_count(elapsed + other.elapsed,
+                       {
+                           event_counts[0] + other.event_counts[0],
+                           event_counts[1] + other.event_counts[1],
+                           event_counts[2] + other.event_counts[2],
+                           event_counts[3] + other.event_counts[3],
+                           event_counts[4] + other.event_counts[4],
+                       });
+  }
+
+  void operator+=(const event_count& other) { *this = *this + other; }
+};
+
+struct event_aggregate {
+  bool has_events = false;
+  int iterations = 0;
+  event_count total{};
+  event_count best{};
+  event_count worst{};
+
+  event_aggregate() = default;
+
+  void operator<<(const event_count& other) {
+    if (iterations == 0 || other.elapsed < best.elapsed) {
+      best = other;
+    }
+    if (iterations == 0 || other.elapsed > worst.elapsed) {
+      worst = other;
+    }
+    iterations++;
+    total += other;
+  }
+
+  double elapsed_sec() const { return total.elapsed_sec() / iterations; }
+  double elapsed_ns() const { return total.elapsed_ns() / iterations; }
+  double cycles() const { return total.cycles() / iterations; }
+  double instructions() const { return total.instructions() / iterations; }
+};
+
+struct event_collector {
+  event_count count{};
+  std::chrono::time_point<std::chrono::steady_clock> start_clock{};
+
+#if defined(__linux__)
+  LinuxEvents<PERF_TYPE_HARDWARE> linux_events;
+  event_collector()
+      : linux_events(std::vector<int>{
+            PERF_COUNT_HW_CPU_CYCLES,
+            PERF_COUNT_HW_INSTRUCTIONS,
+        }) {}
+  bool has_events() { return linux_events.is_working(); }
+#elif __APPLE__ && __aarch64__
+  AppleEvents apple_events;
+  performance_counters diff;
+  event_collector() : diff(0) { apple_events.setup_performance_counters(); }
+  bool has_events() { return apple_events.setup_performance_counters(); }
+#else
+  event_collector() {}
+  bool has_events() { return false; }
+#endif
+
+  inline void start() {
+#if defined(__linux)
+    linux_events.start();
+#elif __APPLE__ && __aarch64__
+    if (has_events()) {
+      diff = apple_events.get_counters();
+    }
+#endif
+    start_clock = std::chrono::steady_clock::now();
+  }
+  inline event_count& end() {
+    const auto end_clock = std::chrono::steady_clock::now();
+#if defined(__linux)
+    linux_events.end(count.event_counts);
+#elif __APPLE__ && __aarch64__
+    if (has_events()) {
+      performance_counters end = apple_events.get_counters();
+      diff = end - diff;
+    }
+    count.event_counts[0] = diff.cycles;
+    count.event_counts[1] = diff.instructions;
+    count.event_counts[2] = diff.missed_branches;
+    count.event_counts[3] = 0;
+    count.event_counts[4] = diff.branches;
+#endif
+    count.elapsed = end_clock - start_clock;
+    return count;
+  }
+};
+
+#endif
diff --git a/benchmarks/performancecounters/linux-perf-events.h b/benchmarks/performancecounters/linux-perf-events.h
new file mode 100644 (file)
index 0000000..7ed0f8d
--- /dev/null
@@ -0,0 +1,105 @@
+#pragma once
+#ifdef __linux__
+
+#include <asm/unistd.h>        // for __NR_perf_event_open
+#include <linux/perf_event.h>  // for perf event constants
+#include <sys/ioctl.h>         // for ioctl
+#include <unistd.h>            // for syscall
+
+#include <cerrno>   // for errno
+#include <cstring>  // for memset
+#include <stdexcept>
+
+#include <iostream>
+#include <vector>
+
+template <int TYPE = PERF_TYPE_HARDWARE>
+class LinuxEvents {
+  int fd;
+  bool working;
+  perf_event_attr attribs{};
+  size_t num_events{};
+  std::vector<uint64_t> temp_result_vec{};
+  std::vector<uint64_t> ids{};
+
+ public:
+  explicit LinuxEvents(std::vector<int> config_vec) : fd(0), working(true) {
+    memset(&attribs, 0, sizeof(attribs));
+    attribs.type = TYPE;
+    attribs.size = sizeof(attribs);
+    attribs.disabled = 1;
+    attribs.exclude_kernel = 1;
+    attribs.exclude_hv = 1;
+
+    attribs.sample_period = 0;
+    attribs.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
+    const int pid = 0;   // the current process
+    const int cpu = -1;  // all CPUs
+    const unsigned long flags = 0;
+
+    int group = -1;  // no group
+    num_events = config_vec.size();
+    ids.resize(config_vec.size());
+    uint32_t i = 0;
+    for (auto config : config_vec) {
+      attribs.config = config;
+      int _fd = static_cast<int>(
+          syscall(__NR_perf_event_open, &attribs, pid, cpu, group, flags));
+      if (_fd == -1) {
+        report_error("perf_event_open");
+      }
+      ioctl(_fd, PERF_EVENT_IOC_ID, &ids[i++]);
+      if (group == -1) {
+        group = _fd;
+        fd = _fd;
+      }
+    }
+
+    temp_result_vec.resize(num_events * 2 + 1);
+  }
+
+  ~LinuxEvents() {
+    if (fd != -1) {
+      close(fd);
+    }
+  }
+
+  inline void start() {
+    if (fd != -1) {
+      if (ioctl(fd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP) == -1) {
+        report_error("ioctl(PERF_EVENT_IOC_RESET)");
+      }
+
+      if (ioctl(fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) {
+        report_error("ioctl(PERF_EVENT_IOC_ENABLE)");
+      }
+    }
+  }
+
+  inline void end(std::vector<unsigned long long> &results) {
+    if (fd != -1) {
+      if (ioctl(fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) {
+        report_error("ioctl(PERF_EVENT_IOC_DISABLE)");
+      }
+
+      if (read(fd, temp_result_vec.data(), temp_result_vec.size() * 8) == -1) {
+        report_error("read");
+      }
+    }
+    // our actual results are in slots 1,3,5, ... of this structure
+    for (uint32_t i = 1; i < temp_result_vec.size(); i += 2) {
+      results[i / 2] = temp_result_vec[i];
+    }
+    for (uint32_t i = 2; i < temp_result_vec.size(); i += 2) {
+      if (ids[i / 2 - 1] != temp_result_vec[i]) {
+        report_error("event mismatch");
+      }
+    }
+  }
+
+  bool is_working() { return working; }
+
+ private:
+  void report_error(const std::string &) { working = false; }
+};
+#endif
\ No newline at end of file
diff --git a/benchmarks/wpt_bench.cpp b/benchmarks/wpt_bench.cpp
new file mode 100644 (file)
index 0000000..2b37db1
--- /dev/null
@@ -0,0 +1,227 @@
+#include "benchmark_header.h"
+#include "simdjson.h"
+
+using namespace simdjson;
+
+double url_examples_bytes{};
+
+std::vector<std::pair<std::string, std::string>> url_examples;
+
+size_t init_data(const char *source) {
+  ondemand::parser parser;
+  std::vector<std::pair<std::string, std::string>> answer;
+
+  if (!file_exists(source)) {
+    return 0;
+  }
+  padded_string json = padded_string::load(source);
+  ondemand::document doc = parser.iterate(json);
+  for (auto element : doc.get_array()) {
+    if (element.type() == ondemand::json_type::object) {
+      std::string_view input;
+      if (element["input"].get_string(true).get(input) != simdjson::SUCCESS) {
+        printf("missing input.\n");
+      }
+      std::string_view base;
+      if (element["base"].get_string(true).get(base) != simdjson::SUCCESS) {
+      }
+      url_examples.push_back({std::string(input), std::string(base)});
+      url_examples_bytes += input.size() + base.size();
+    }
+  }
+  return url_examples.size();
+}
+
+template <class result>
+static void BasicBench_AdaURL(benchmark::State &state) {
+  // volatile to prevent optimizations.
+  volatile size_t href_size = 0;
+
+  for (auto _ : state) {
+    for (const std::pair<std::string, std::string> &url_strings :
+         url_examples) {
+      ada::result<result> base;
+      result *base_ptr = nullptr;
+      if (!url_strings.second.empty()) {
+        base = ada::parse<result>(url_strings.second);
+        if (base) {
+          base_ptr = &*base;
+        } else {
+          continue;
+        }
+      }
+      auto url = ada::parse(url_strings.first, base_ptr);
+      if (url) {
+        href_size += url->get_href().size();
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (const std::pair<std::string, std::string> &url_strings :
+           url_examples) {
+        ada::result<result> base;
+        result *base_ptr = nullptr;
+        if (!url_strings.second.empty()) {
+          base = ada::parse<result>(url_strings.second);
+          if (base) {
+            base_ptr = &*base;
+          } else {
+            continue;
+          }
+        }
+        auto url = ada::parse(url_strings.first, base_ptr);
+        if (url) {
+          href_size += url->get_href().size();
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+auto BasicBench_AdaURL_url = BasicBench_AdaURL<ada::url>;
+BENCHMARK(BasicBench_AdaURL_url);
+auto BasicBench_AdaURL_url_aggregator = BasicBench_AdaURL<ada::url_aggregator>;
+BENCHMARK(BasicBench_AdaURL_url_aggregator);
+
+#if ADA_url_whatwg_ENABLED
+
+#include <upa/url.h>
+
+static void BasicBench_whatwg(benchmark::State &state) {
+  volatile size_t success{};
+  for (auto _ : state) {
+    for (const std::pair<std::string, std::string> &url_strings :
+         url_examples) {
+      upa::url base;
+      upa::url *base_ptr = nullptr;
+      if (!url_strings.second.empty()) {
+        if (upa::success(base.parse(url_strings.second, nullptr))) {
+          base_ptr = &base;
+        }
+      }
+      upa::url url;
+      if (upa::success(url.parse(url_strings.first, base_ptr))) {
+        success++;
+      }
+    }
+  }
+  if (collector.has_events()) {
+    event_aggregate aggregate{};
+    for (size_t i = 0; i < N; i++) {
+      std::atomic_thread_fence(std::memory_order_acquire);
+      collector.start();
+      for (const std::pair<std::string, std::string> &url_strings :
+           url_examples) {
+        upa::url base;
+        upa::url *base_ptr = nullptr;
+        if (!url_strings.second.empty()) {
+          if (upa::success(base.parse(url_strings.second, nullptr))) {
+            base_ptr = &base;
+          }
+        }
+        upa::url url;
+        if (upa::success(url.parse(url_strings.first, base_ptr))) {
+          success++;
+        }
+      }
+      std::atomic_thread_fence(std::memory_order_release);
+      event_count allocate_count = collector.end();
+      aggregate << allocate_count;
+    }
+    (void)success;
+    state.counters["cycles/url"] =
+        aggregate.best.cycles() / std::size(url_examples);
+    state.counters["instructions/url"] =
+        aggregate.best.instructions() / std::size(url_examples);
+    state.counters["instructions/cycle"] =
+        aggregate.best.instructions() / aggregate.best.cycles();
+    state.counters["instructions/byte"] =
+        aggregate.best.instructions() / url_examples_bytes;
+    state.counters["instructions/ns"] =
+        aggregate.best.instructions() / aggregate.best.elapsed_ns();
+    state.counters["GHz"] =
+        aggregate.best.cycles() / aggregate.best.elapsed_ns();
+    state.counters["ns/url"] =
+        aggregate.best.elapsed_ns() / std::size(url_examples);
+    state.counters["cycle/byte"] = aggregate.best.cycles() / url_examples_bytes;
+  }
+  state.counters["time/byte"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate |
+                              benchmark::Counter::kInvert);
+  state.counters["time/url"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
+  state.counters["speed"] = benchmark::Counter(
+      url_examples_bytes, benchmark::Counter::kIsIterationInvariantRate);
+  state.counters["url/s"] =
+      benchmark::Counter(double(std::size(url_examples)),
+                         benchmark::Counter::kIsIterationInvariantRate);
+}
+BENCHMARK(BasicBench_whatwg);
+#endif  // ADA_url_whatwg_ENABLED
+
+int main(int argc, char **argv) {
+  if (argc == 1 || !init_data(argv[1])) {
+    std::cout
+        << "pass the path to the file wpt/urltestdata.json as a parameter."
+        << std::endl;
+    std::cout
+        << "E.g., './build/benchmarks/wpt_bench tests/wpt/urltestdata.json'"
+        << std::endl;
+    return EXIT_SUCCESS;
+  }
+#if defined(ADA_RUST_VERSION)
+  benchmark::AddCustomContext("rust version ", ADA_RUST_VERSION);
+#endif
+#if (__APPLE__ && __aarch64__) || defined(__linux__)
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters",
+                                "No privileged access (sudo may help).");
+  }
+#else
+  if (!collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Unsupported system.");
+  }
+#endif
+
+  if (collector.has_events()) {
+    benchmark::AddCustomContext("performance counters", "Enabled");
+  }
+  benchmark::Initialize(&argc, argv);
+  benchmark::RunSpecifiedBenchmarks();
+  benchmark::Shutdown();
+}
diff --git a/clang-format-ignore.txt b/clang-format-ignore.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake
new file mode 100644 (file)
index 0000000..ad6b74a
--- /dev/null
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: MIT
+#
+# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
+
+set(CPM_DOWNLOAD_VERSION 0.38.6)
+set(CPM_HASH_SUM "11c3fa5f1ba14f15d31c2fb63dbc8628ee133d81c8d764caad9a8db9e0bacb07")
+
+if(CPM_SOURCE_CACHE)
+  set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
+elseif(DEFINED ENV{CPM_SOURCE_CACHE})
+  set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
+else()
+  set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
+endif()
+
+# Expand relative path. This is important if the provided path contains a tilde (~)
+get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
+
+file(DOWNLOAD
+     https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
+     ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
+)
+
+include(${CPM_DOWNLOAD_LOCATION})
diff --git a/cmake/ada-config.cmake.in b/cmake/ada-config.cmake.in
new file mode 100644 (file)
index 0000000..0c5d540
--- /dev/null
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/ada_targets.cmake")
diff --git a/cmake/ada-flags.cmake b/cmake/ada-flags.cmake
new file mode 100644 (file)
index 0000000..43fdcae
--- /dev/null
@@ -0,0 +1,59 @@
+option(ADA_LOGGING "verbose output (useful for debugging)" OFF)
+option(ADA_DEVELOPMENT_CHECKS "development checks (useful for debugging)" OFF)
+option(ADA_SANITIZE "Sanitize addresses" OFF)
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+  option(ADA_SANITIZE_BOUNDS_STRICT "Sanitize bounds (strict): only for GCC" OFF)
+endif()
+option(ADA_SANITIZE_UNDEFINED "Sanitize undefined behaviour" OFF)
+if(ADA_SANITIZE)
+  message(STATUS "Address sanitizer enabled.")
+endif()
+if(ADA_SANITIZE_UNDEFINED)
+  message(STATUS "Undefined sanitizer enabled.")
+endif()
+option(ADA_COVERAGE "Compute coverage" OFF)
+option(ADA_TOOLS "Build cli tools (adaparse)" ON)
+
+if (ADA_COVERAGE)
+    message(STATUS "You want to compute coverage. We assume that you have installed gcovr.")
+    if (NOT CMAKE_BUILD_TYPE)
+        set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
+    endif()
+    #######################
+    # You need to install gcovr. Under macos, you may do so with brew.
+    # brew install gcovr
+    # Then build...
+    # cmake -D ADA_COVERAGE=ON  -B buildcoverage
+    # cmake --build buildcoverage
+    # cmake --build buildcoverage --target ada_coverage
+    #
+    # open buildcoverage/ada_coverage/index.html
+    #####################
+    include(${PROJECT_SOURCE_DIR}/cmake/codecoverage.cmake)
+    APPEND_COVERAGE_COMPILER_FLAGS()
+    setup_target_for_coverage_gcovr_html(NAME ada_coverage EXECUTABLE ctest EXCLUDE "${PROJECT_SOURCE_DIR}/dependencies/*" "${PROJECT_SOURCE_DIR}/tools/*"  "${PROJECT_SOURCE_DIR}/singleheader/*" ${PROJECT_SOURCE_DIR}/include/ada/common_defs.h)
+endif()
+
+if (NOT CMAKE_BUILD_TYPE)
+  if(ADA_SANITIZE OR ADA_SANITIZE_BOUNDS_STRICT OR ADA_SANITIZE_UNDEFINED)
+    message(STATUS "No build type selected, default to Debug because you have sanitizers.")
+    set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
+  else()
+    message(STATUS "No build type selected, default to Release")
+    set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
+  endif()
+endif()
+
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake")
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+find_program(CCACHE_FOUND ccache)
+if(CCACHE_FOUND)
+  message(STATUS "Ccache found using it as compiler launcher.")
+  set(CMAKE_C_COMPILER_LAUNCHER ccache)
+  set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
+endif(CCACHE_FOUND)
diff --git a/cmake/add-cpp-test.cmake b/cmake/add-cpp-test.cmake
new file mode 100644 (file)
index 0000000..3d1e785
--- /dev/null
@@ -0,0 +1,67 @@
+# Helper so we don't have to repeat ourselves so much
+# Usage: add_cpp_test(testname [COMPILE_ONLY] [SOURCES a.cpp b.cpp ...] [LABELS acceptance per_implementation ...])
+# SOURCES defaults to testname.cpp if not specified.
+function(add_cpp_test TEST_NAME)
+  # Parse arguments
+  cmake_parse_arguments(PARSE_ARGV 1 ARGS "COMPILE_ONLY;LIBRARY;WILL_FAIL" "" "SOURCES;LABELS;DEPENDENCY_OF")
+  if (NOT ARGS_SOURCES)
+    list(APPEND ARGS_SOURCES ${TEST_NAME}.cpp)
+  endif()
+  if (ARGS_COMPILE_ONLY)
+    list(APPEND ${ARGS_LABELS} compile_only)
+  endif()
+  if(ADA_SANITIZE)
+    add_compile_options(-fsanitize=address -fno-omit-frame-pointer -fno-sanitize-recover=all)
+    add_compile_definitions(ASAN_OPTIONS=detect_leaks=1)
+  endif()
+  if(ADA_SANITIZE_BOUNDS_STRICT)
+    add_compile_options(-fsanitize=bounds-strict -fno-sanitize-recover=all)
+    add_link_options(-fsanitize=bounds-strict)
+  endif()
+  if(ADA_SANITIZE_UNDEFINED)
+    add_compile_options(-fsanitize=undefined -fno-sanitize-recover=all)
+    add_link_options(-fsanitize=undefined)
+  endif()
+  # Add the compile target
+  if (ARGS_LIBRARY)
+    add_library(${TEST_NAME} STATIC ${ARGS_SOURCES})
+  else(ARGS_LIBRARY)
+    add_executable(${TEST_NAME} ${ARGS_SOURCES})
+  endif(ARGS_LIBRARY)
+
+  # Add test
+  if (ARGS_COMPILE_ONLY OR ARGS_LIBRARY)
+    add_test(
+      NAME ${TEST_NAME}
+      COMMAND ${CMAKE_COMMAND} --build . --target ${TEST_NAME} --config $<CONFIGURATION>
+      WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+    )
+    set_target_properties(${TEST_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE)
+  else()
+    add_test(${TEST_NAME} ${TEST_NAME})
+
+    # Add to <label>_tests make targets
+    foreach(label ${ARGS_LABELS})
+      list(APPEND ARGS_DEPENDENCY_OF ${label})
+    endforeach(label ${ARGS_LABELS})
+  endif()
+
+  # Add to test labels
+  if (ARGS_LABELS)
+    set_property(TEST ${TEST_NAME} APPEND PROPERTY LABELS ${ARGS_LABELS})
+  endif()
+
+  # Add as a dependency of given targets
+  foreach(dependency_of ${ARGS_DEPENDENCY_OF})
+    if (NOT TARGET ${dependency_of}_tests)
+      add_custom_target(${dependency_of}_tests)
+      add_dependencies(all_tests ${dependency_of}_tests)
+    endif(NOT TARGET ${dependency_of}_tests)
+    add_dependencies(${dependency_of}_tests ${TEST_NAME})
+  endforeach(dependency_of ${ARGS_DEPENDENCY_OF})
+
+  # If it will fail, mark the test as such
+  if (ARGS_WILL_FAIL)
+    set_property(TEST ${TEST_NAME} PROPERTY WILL_FAIL TRUE)
+  endif()
+endfunction()
\ No newline at end of file
diff --git a/cmake/clang-format.cmake b/cmake/clang-format.cmake
new file mode 100644 (file)
index 0000000..db34c55
--- /dev/null
@@ -0,0 +1,37 @@
+find_program(PYTHON_EXECUTABLE python3 python)
+
+if(NOT PYTHON_EXECUTABLE)
+  message(WARNING "Python not found. Skipping lint and format checks.")
+else()
+  set(LINT_AND_FORMAT_SCRIPT_PATH ${CMAKE_SOURCE_DIR}/tools/lint_and_format.py)
+
+  # Should run on CI
+  if(DEFINED ENV{LINT_AND_FORMAT_CHECK})
+    message(STATUS "Checking code with clang-format...")
+    execute_process(
+      COMMAND ${PYTHON_EXECUTABLE} ${LINT_AND_FORMAT_SCRIPT_PATH} check
+      WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+      RESULT_VARIABLE clang_check_result
+    )
+
+    if(clang_check_result)
+      message(FATAL_ERROR "Clang-format check failed with error code ${clang_check_result}")
+    endif()
+
+  else()
+    if(DEFINED ENV{FORMAT_ENABLED})
+      message(STATUS "Formatting code with clang-format...")
+      execute_process(
+        COMMAND ${PYTHON_EXECUTABLE} ${LINT_AND_FORMAT_SCRIPT_PATH} format
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+        RESULT_VARIABLE clang_format_result
+      )
+
+      if(clang_format_result)
+        message(FATAL_ERROR "Clang-format format failed with error code ${clang_check_result}")
+      endif()
+    else()
+      message(STATUS "Code formatting is not enabled.")
+    endif()
+  endif()
+endif()
diff --git a/cmake/codecoverage.cmake b/cmake/codecoverage.cmake
new file mode 100644 (file)
index 0000000..d4a039f
--- /dev/null
@@ -0,0 +1,742 @@
+# Copyright (c) 2012 - 2017, Lars Bilke
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+#    may be used to endorse or promote products derived from this software without
+#    specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# CHANGES:
+#
+# 2012-01-31, Lars Bilke
+# - Enable Code Coverage
+#
+# 2013-09-17, Joakim Söderberg
+# - Added support for Clang.
+# - Some additional usage instructions.
+#
+# 2016-02-03, Lars Bilke
+# - Refactored functions to use named parameters
+#
+# 2017-06-02, Lars Bilke
+# - Merged with modified version from github.com/ufz/ogs
+#
+# 2019-05-06, Anatolii Kurotych
+# - Remove unnecessary --coverage flag
+#
+# 2019-12-13, FeRD (Frank Dana)
+# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor
+#   of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments.
+# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY
+# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list
+# - Set lcov basedir with -b argument
+# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be
+#   overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().)
+# - Delete output dir, .info file on 'make clean'
+# - Remove Python detection, since version mismatches will break gcovr
+# - Minor cleanup (lowercase function names, update examples...)
+#
+# 2019-12-19, FeRD (Frank Dana)
+# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets
+#
+# 2020-01-19, Bob Apthorpe
+# - Added gfortran support
+#
+# 2020-02-17, FeRD (Frank Dana)
+# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters
+#   in EXCLUDEs, and remove manual escaping from gcovr targets
+#
+# 2021-01-19, Robin Mueller
+# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run
+# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional
+#   flags to the gcovr command
+#
+# 2020-05-04, Mihchael Davis
+#     - Add -fprofile-abs-path to make gcno files contain absolute paths
+#     - Fix BASE_DIRECTORY not working when defined
+#     - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines
+#
+# 2021-05-10, Martin Stump
+#     - Check if the generator is multi-config before warning about non-Debug builds
+#
+# 2022-02-22, Marko Wehle
+#     - Change gcovr output from -o <filename> for --xml <filename> and --html <filename> output respectively.
+#       This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt".
+#
+# 2022-09-28, Sebastian Mueller
+#     - fix append_coverage_compiler_flags_to_target to correctly add flags
+#     - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent)
+#
+# USAGE:
+#
+# 1. Copy this file into your cmake modules path.
+#
+# 2. Add the following line to your CMakeLists.txt (best inside an if-condition
+#    using a CMake option() to enable it just optionally):
+#      include(CodeCoverage)
+#
+# 3. Append necessary compiler flags for all supported source files:
+#      append_coverage_compiler_flags()
+#    Or for specific target:
+#      append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME)
+#
+# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
+#
+# 4. If you need to exclude additional directories from the report, specify them
+#    using full paths in the COVERAGE_EXCLUDES variable before calling
+#    setup_target_for_coverage_*().
+#    Example:
+#      set(COVERAGE_EXCLUDES
+#          '${PROJECT_SOURCE_DIR}/src/dir1/*'
+#          '/path/to/my/src/dir2/*')
+#    Or, use the EXCLUDE argument to setup_target_for_coverage_*().
+#    Example:
+#      setup_target_for_coverage_lcov(
+#          NAME coverage
+#          EXECUTABLE testrunner
+#          EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*")
+#
+# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set
+#     relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
+#     Example:
+#       set(COVERAGE_EXCLUDES "dir1/*")
+#       setup_target_for_coverage_gcovr_html(
+#           NAME coverage
+#           EXECUTABLE testrunner
+#           BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
+#           EXCLUDE "dir2/*")
+#
+# 5. Use the functions described below to create a custom make target which
+#    runs your test executable and produces a code coverage report.
+#
+# 6. Build a Debug build:
+#      cmake -DCMAKE_BUILD_TYPE=Debug ..
+#      make
+#      make my_coverage_target
+#
+
+include(CMakeParseArguments)
+
+option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE)
+
+# Check prereqs
+find_program( GCOV_PATH gcov )
+find_program( LCOV_PATH  NAMES lcov lcov.bat lcov.exe lcov.perl)
+find_program( FASTCOV_PATH NAMES fastcov fastcov.py )
+find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
+find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
+find_program( CPPFILT_PATH NAMES c++filt )
+
+if(NOT GCOV_PATH)
+    message(FATAL_ERROR "gcov not found! Aborting...")
+endif() # NOT GCOV_PATH
+
+# Check supported compiler (Clang, GNU and Flang)
+get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
+foreach(LANG ${LANGUAGES})
+  if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
+    if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3)
+      message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
+    endif()
+  elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU"
+         AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang")
+    message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...")
+  endif()
+endforeach()
+
+set(COVERAGE_COMPILER_FLAGS "-g --coverage"
+    CACHE INTERNAL "")
+if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)")
+    include(CheckCXXCompilerFlag)
+    check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path)
+    if(HAVE_fprofile_abs_path)
+        set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path")
+    endif()
+endif()
+
+set(CMAKE_Fortran_FLAGS_COVERAGE
+    ${COVERAGE_COMPILER_FLAGS}
+    CACHE STRING "Flags used by the Fortran compiler during coverage builds."
+    FORCE )
+set(CMAKE_CXX_FLAGS_COVERAGE
+    ${COVERAGE_COMPILER_FLAGS}
+    CACHE STRING "Flags used by the C++ compiler during coverage builds."
+    FORCE )
+set(CMAKE_C_FLAGS_COVERAGE
+    ${COVERAGE_COMPILER_FLAGS}
+    CACHE STRING "Flags used by the C compiler during coverage builds."
+    FORCE )
+set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
+    ""
+    CACHE STRING "Flags used for linking binaries during coverage builds."
+    FORCE )
+set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
+    ""
+    CACHE STRING "Flags used by the shared libraries linker during coverage builds."
+    FORCE )
+mark_as_advanced(
+    CMAKE_Fortran_FLAGS_COVERAGE
+    CMAKE_CXX_FLAGS_COVERAGE
+    CMAKE_C_FLAGS_COVERAGE
+    CMAKE_EXE_LINKER_FLAGS_COVERAGE
+    CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
+
+get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG))
+    message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
+endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)
+
+if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
+    link_libraries(gcov)
+endif()
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_lcov(
+#     NAME testrunner_coverage                    # New target name
+#     EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES testrunner                     # Dependencies to build first
+#     BASE_DIRECTORY "../"                        # Base directory for report
+#                                                 #  (defaults to PROJECT_SOURCE_DIR)
+#     EXCLUDE "src/dir1/*" "src/dir2/*"           # Patterns to exclude (can be relative
+#                                                 #  to BASE_DIRECTORY, with CMake 3.4+)
+#     NO_DEMANGLE                                 # Don't demangle C++ symbols
+#                                                 #  even if c++filt is found
+# )
+function(setup_target_for_coverage_lcov)
+
+    set(options NO_DEMANGLE SONARQUBE)
+    set(oneValueArgs BASE_DIRECTORY NAME)
+    set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
+    cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    if(NOT LCOV_PATH)
+        message(FATAL_ERROR "lcov not found! Aborting...")
+    endif() # NOT LCOV_PATH
+
+    if(NOT GENHTML_PATH)
+        message(FATAL_ERROR "genhtml not found! Aborting...")
+    endif() # NOT GENHTML_PATH
+
+    # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+    if(DEFINED Coverage_BASE_DIRECTORY)
+        get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+    else()
+        set(BASEDIR ${PROJECT_SOURCE_DIR})
+    endif()
+
+    # Collect excludes (CMake 3.4+: Also compute absolute paths)
+    set(LCOV_EXCLUDES "")
+    foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES})
+        if(CMAKE_VERSION VERSION_GREATER 3.4)
+            get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+        endif()
+        list(APPEND LCOV_EXCLUDES "${EXCLUDE}")
+    endforeach()
+    list(REMOVE_DUPLICATES LCOV_EXCLUDES)
+
+    # Conditional arguments
+    if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
+      set(GENHTML_EXTRA_ARGS "--demangle-cpp")
+    endif()
+
+    # Setting up commands which will be run to generate coverage data.
+    # Cleanup lcov
+    set(LCOV_CLEAN_CMD
+        ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory .
+        -b ${BASEDIR} --zerocounters
+    )
+    # Create baseline to make sure untouched files show up in the report
+    set(LCOV_BASELINE_CMD
+        ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b
+        ${BASEDIR} -o ${Coverage_NAME}.base
+    )
+    # Run tests
+    set(LCOV_EXEC_TESTS_CMD
+        ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+    )
+    # Capturing lcov counters and generating report
+    set(LCOV_CAPTURE_CMD
+        ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b
+        ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture
+    )
+    # add baseline counters
+    set(LCOV_BASELINE_COUNT_CMD
+        ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base
+        -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total
+    )
+    # filter collected data to final coverage report
+    set(LCOV_FILTER_CMD
+        ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove
+        ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info
+    )
+    # Generate HTML output
+    set(LCOV_GEN_HTML_CMD
+        ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o
+        ${Coverage_NAME} ${Coverage_NAME}.info
+    )
+    if(${Coverage_SONARQUBE})
+        # Generate SonarQube output
+        set(GCOVR_XML_CMD
+            ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
+            ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
+        )
+        set(GCOVR_XML_CMD_COMMAND
+            COMMAND ${GCOVR_XML_CMD}
+        )
+        set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml)
+        set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.")
+    endif()
+
+
+    if(CODE_COVERAGE_VERBOSE)
+        message(STATUS "Executed command report")
+        message(STATUS "Command to clean up lcov: ")
+        string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}")
+        message(STATUS "${LCOV_CLEAN_CMD_SPACED}")
+
+        message(STATUS "Command to create baseline: ")
+        string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}")
+        message(STATUS "${LCOV_BASELINE_CMD_SPACED}")
+
+        message(STATUS "Command to run the tests: ")
+        string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}")
+        message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}")
+
+        message(STATUS "Command to capture counters and generate report: ")
+        string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}")
+        message(STATUS "${LCOV_CAPTURE_CMD_SPACED}")
+
+        message(STATUS "Command to add baseline counters: ")
+        string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}")
+        message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}")
+
+        message(STATUS "Command to filter collected data: ")
+        string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}")
+        message(STATUS "${LCOV_FILTER_CMD_SPACED}")
+
+        message(STATUS "Command to generate lcov HTML output: ")
+        string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}")
+        message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}")
+
+        if(${Coverage_SONARQUBE})
+            message(STATUS "Command to generate SonarQube XML output: ")
+            string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
+            message(STATUS "${GCOVR_XML_CMD_SPACED}")
+        endif()
+    endif()
+
+    # Setup target
+    add_custom_target(${Coverage_NAME}
+        COMMAND ${LCOV_CLEAN_CMD}
+        COMMAND ${LCOV_BASELINE_CMD}
+        COMMAND ${LCOV_EXEC_TESTS_CMD}
+        COMMAND ${LCOV_CAPTURE_CMD}
+        COMMAND ${LCOV_BASELINE_COUNT_CMD}
+        COMMAND ${LCOV_FILTER_CMD}
+        COMMAND ${LCOV_GEN_HTML_CMD}
+        ${GCOVR_XML_CMD_COMMAND}
+
+        # Set output files as GENERATED (will be removed on 'make clean')
+        BYPRODUCTS
+            ${Coverage_NAME}.base
+            ${Coverage_NAME}.capture
+            ${Coverage_NAME}.total
+            ${Coverage_NAME}.info
+            ${GCOVR_XML_CMD_BYPRODUCTS}
+            ${Coverage_NAME}/index.html
+        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+        DEPENDS ${Coverage_DEPENDENCIES}
+        VERBATIM # Protect arguments to commands
+        COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
+    )
+
+    # Show where to find the lcov info report
+    add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+        COMMAND ;
+        COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
+        ${GCOVR_XML_CMD_COMMENT}
+    )
+
+    # Show info where to find the report
+    add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+        COMMAND ;
+        COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
+    )
+
+endfunction() # setup_target_for_coverage_lcov
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_gcovr_xml(
+#     NAME ctest_coverage                    # New target name
+#     EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES executable_target         # Dependencies to build first
+#     BASE_DIRECTORY "../"                   # Base directory for report
+#                                            #  (defaults to PROJECT_SOURCE_DIR)
+#     EXCLUDE "src/dir1/*" "src/dir2/*"      # Patterns to exclude (can be relative
+#                                            #  to BASE_DIRECTORY, with CMake 3.4+)
+# )
+# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
+# GCVOR command.
+function(setup_target_for_coverage_gcovr_xml)
+
+    set(options NONE)
+    set(oneValueArgs BASE_DIRECTORY NAME)
+    set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+    cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    if(NOT GCOVR_PATH)
+        message(FATAL_ERROR "gcovr not found! Aborting...")
+    endif() # NOT GCOVR_PATH
+
+    # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+    if(DEFINED Coverage_BASE_DIRECTORY)
+        get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+    else()
+        set(BASEDIR ${PROJECT_SOURCE_DIR})
+    endif()
+
+    # Collect excludes (CMake 3.4+: Also compute absolute paths)
+    set(GCOVR_EXCLUDES "")
+    foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
+        if(CMAKE_VERSION VERSION_GREATER 3.4)
+            get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+        endif()
+        list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
+    endforeach()
+    list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
+
+    # Combine excludes to several -e arguments
+    set(GCOVR_EXCLUDE_ARGS "")
+    foreach(EXCLUDE ${GCOVR_EXCLUDES})
+        list(APPEND GCOVR_EXCLUDE_ARGS "-e")
+        list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
+    endforeach()
+
+    # Set up commands which will be run to generate coverage data
+    # Run tests
+    set(GCOVR_XML_EXEC_TESTS_CMD
+        ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+    )
+    # Running gcovr
+    set(GCOVR_XML_CMD
+        ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
+        ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
+    )
+
+    if(CODE_COVERAGE_VERBOSE)
+        message(STATUS "Executed command report")
+
+        message(STATUS "Command to run tests: ")
+        string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}")
+        message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}")
+
+        message(STATUS "Command to generate gcovr XML coverage data: ")
+        string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}")
+        message(STATUS "${GCOVR_XML_CMD_SPACED}")
+    endif()
+
+    add_custom_target(${Coverage_NAME}
+        COMMAND ${GCOVR_XML_EXEC_TESTS_CMD}
+        COMMAND ${GCOVR_XML_CMD}
+
+        BYPRODUCTS ${Coverage_NAME}.xml
+        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+        DEPENDS ${Coverage_DEPENDENCIES}
+        VERBATIM # Protect arguments to commands
+        COMMENT "Running gcovr to produce Cobertura code coverage report."
+    )
+
+    # Show info where to find the report
+    add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+        COMMAND ;
+        COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
+    )
+endfunction() # setup_target_for_coverage_gcovr_xml
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_gcovr_html(
+#     NAME ctest_coverage                    # New target name
+#     EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES executable_target         # Dependencies to build first
+#     BASE_DIRECTORY "../"                   # Base directory for report
+#                                            #  (defaults to PROJECT_SOURCE_DIR)
+#     EXCLUDE "src/dir1/*" "src/dir2/*"      # Patterns to exclude (can be relative
+#                                            #  to BASE_DIRECTORY, with CMake 3.4+)
+# )
+# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the
+# GCVOR command.
+function(setup_target_for_coverage_gcovr_html)
+
+    set(options NONE)
+    set(oneValueArgs BASE_DIRECTORY NAME)
+    set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+    cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    if(NOT GCOVR_PATH)
+        message(FATAL_ERROR "gcovr not found! Aborting...")
+    endif() # NOT GCOVR_PATH
+
+    # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+    if(DEFINED Coverage_BASE_DIRECTORY)
+        get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+    else()
+        set(BASEDIR ${PROJECT_SOURCE_DIR})
+    endif()
+
+    # Collect excludes (CMake 3.4+: Also compute absolute paths)
+    set(GCOVR_EXCLUDES "")
+    foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
+        if(CMAKE_VERSION VERSION_GREATER 3.4)
+            get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+        endif()
+        list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
+    endforeach()
+    list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
+
+    # Combine excludes to several -e arguments
+    set(GCOVR_EXCLUDE_ARGS "")
+    foreach(EXCLUDE ${GCOVR_EXCLUDES})
+        list(APPEND GCOVR_EXCLUDE_ARGS "-e")
+        list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
+    endforeach()
+
+    # Set up commands which will be run to generate coverage data
+    # Run tests
+    set(GCOVR_HTML_EXEC_TESTS_CMD
+        ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+    )
+    # Create folder
+    set(GCOVR_HTML_FOLDER_CMD
+        ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
+    )
+    # Running gcovr
+    set(GCOVR_HTML_CMD
+        ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS}
+        ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR}
+    )
+
+    if(CODE_COVERAGE_VERBOSE)
+        message(STATUS "Executed command report")
+
+        message(STATUS "Command to run tests: ")
+        string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}")
+        message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}")
+
+        message(STATUS "Command to create a folder: ")
+        string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}")
+        message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}")
+
+        message(STATUS "Command to generate gcovr HTML coverage data: ")
+        string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}")
+        message(STATUS "${GCOVR_HTML_CMD_SPACED}")
+    endif()
+
+    add_custom_target(${Coverage_NAME}
+        COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD}
+        COMMAND ${GCOVR_HTML_FOLDER_CMD}
+        COMMAND ${GCOVR_HTML_CMD}
+
+        BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html  # report directory
+        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+        DEPENDS ${Coverage_DEPENDENCIES}
+        VERBATIM # Protect arguments to commands
+        COMMENT "Running gcovr to produce HTML code coverage report."
+    )
+
+    # Show info where to find the report
+    add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+        COMMAND ;
+        COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
+    )
+
+endfunction() # setup_target_for_coverage_gcovr_html
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_fastcov(
+#     NAME testrunner_coverage                    # New target name
+#     EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES testrunner                     # Dependencies to build first
+#     BASE_DIRECTORY "../"                        # Base directory for report
+#                                                 #  (defaults to PROJECT_SOURCE_DIR)
+#     EXCLUDE "src/dir1/" "src/dir2/"             # Patterns to exclude.
+#     NO_DEMANGLE                                 # Don't demangle C++ symbols
+#                                                 #  even if c++filt is found
+#     SKIP_HTML                                   # Don't create html report
+#     POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json  # E.g. for stripping source dir from file paths
+# )
+function(setup_target_for_coverage_fastcov)
+
+    set(options NO_DEMANGLE SKIP_HTML)
+    set(oneValueArgs BASE_DIRECTORY NAME)
+    set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD)
+    cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+    if(NOT FASTCOV_PATH)
+        message(FATAL_ERROR "fastcov not found! Aborting...")
+    endif()
+
+    if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH)
+        message(FATAL_ERROR "genhtml not found! Aborting...")
+    endif()
+
+    # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+    if(Coverage_BASE_DIRECTORY)
+        get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+    else()
+        set(BASEDIR ${PROJECT_SOURCE_DIR})
+    endif()
+
+    # Collect excludes (Patterns, not paths, for fastcov)
+    set(FASTCOV_EXCLUDES "")
+    foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES})
+        list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}")
+    endforeach()
+    list(REMOVE_DUPLICATES FASTCOV_EXCLUDES)
+
+    # Conditional arguments
+    if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
+        set(GENHTML_EXTRA_ARGS "--demangle-cpp")
+    endif()
+
+    # Set up commands which will be run to generate coverage data
+    set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS})
+
+    set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
+        --search-directory ${BASEDIR}
+        --process-gcno
+        --output ${Coverage_NAME}.json
+        --exclude ${FASTCOV_EXCLUDES}
+    )
+
+    set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH}
+        -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info
+    )
+
+    if(Coverage_SKIP_HTML)
+        set(FASTCOV_HTML_CMD ";")
+    else()
+        set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS}
+            -o ${Coverage_NAME} ${Coverage_NAME}.info
+        )
+    endif()
+
+    set(FASTCOV_POST_CMD ";")
+    if(Coverage_POST_CMD)
+        set(FASTCOV_POST_CMD ${Coverage_POST_CMD})
+    endif()
+
+    if(CODE_COVERAGE_VERBOSE)
+        message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):")
+
+        message("   Running tests:")
+        string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}")
+        message("     ${FASTCOV_EXEC_TESTS_CMD_SPACED}")
+
+        message("   Capturing fastcov counters and generating report:")
+        string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}")
+        message("     ${FASTCOV_CAPTURE_CMD_SPACED}")
+
+        message("   Converting fastcov .json to lcov .info:")
+        string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}")
+        message("     ${FASTCOV_CONVERT_CMD_SPACED}")
+
+        if(NOT Coverage_SKIP_HTML)
+            message("   Generating HTML report: ")
+            string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}")
+            message("     ${FASTCOV_HTML_CMD_SPACED}")
+        endif()
+        if(Coverage_POST_CMD)
+            message("   Running post command: ")
+            string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}")
+            message("     ${FASTCOV_POST_CMD_SPACED}")
+        endif()
+    endif()
+
+    # Setup target
+    add_custom_target(${Coverage_NAME}
+
+        # Cleanup fastcov
+        COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH}
+            --search-directory ${BASEDIR}
+            --zerocounters
+
+        COMMAND ${FASTCOV_EXEC_TESTS_CMD}
+        COMMAND ${FASTCOV_CAPTURE_CMD}
+        COMMAND ${FASTCOV_CONVERT_CMD}
+        COMMAND ${FASTCOV_HTML_CMD}
+        COMMAND ${FASTCOV_POST_CMD}
+
+        # Set output files as GENERATED (will be removed on 'make clean')
+        BYPRODUCTS
+             ${Coverage_NAME}.info
+             ${Coverage_NAME}.json
+             ${Coverage_NAME}/index.html  # report directory
+
+        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+        DEPENDS ${Coverage_DEPENDENCIES}
+        VERBATIM # Protect arguments to commands
+        COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report."
+    )
+
+    set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.")
+    if(NOT Coverage_SKIP_HTML)
+        string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.")
+    endif()
+    # Show where to find the fastcov info report
+    add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG}
+    )
+
+endfunction() # setup_target_for_coverage_fastcov
+
+function(append_coverage_compiler_flags)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+    set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+    message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
+endfunction() # append_coverage_compiler_flags
+
+# Setup coverage for specific library
+function(append_coverage_compiler_flags_to_target name)
+    separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}")
+    target_compile_options(${name} PRIVATE ${_flag_list})
+    if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
+        target_link_libraries(${name} PRIVATE gcov)
+    endif()
+endfunction()
diff --git a/docs/RELEASE.md b/docs/RELEASE.md
new file mode 100644 (file)
index 0000000..cbf918d
--- /dev/null
@@ -0,0 +1,19 @@
+# Release Document
+
+## Preparation
+
+In order to release a new version of Ada, please update the
+following documents:
+
+- [CmakeLists.txt](../CmakeLists.txt)
+- [Doxygen](../doxygen)
+- [ada_version.h](../include/ada/ada_version.h)
+
+## Release
+
+- Run amalgation script using `./singleheader/amalgamate.py`
+- Create a Github release with following format: `v1.0.0`
+- Upload the following documents to the release
+  - `./singleheader/ada.h`
+  - `./singleheader/ada.cpp`
+  - `./singleheader/singleheader.zip`
diff --git a/docs/cli.md b/docs/cli.md
new file mode 100644 (file)
index 0000000..649300b
--- /dev/null
@@ -0,0 +1,204 @@
+## Command line interface (CLI)
+
+The adaparse command tool takes URL strings (ASCII/UTF-8) and it validates, normalizes and queries them efficiently.
+
+### Command line options
+
+- Options:
+    - `-d`, `--diagram`: Print a diagram of the result
+    - `-u`, `--url`: URL Parameter (required)
+    - `-h`, `--help`: Print usage
+    - `-g`, `--get`: Get a specific part of the URL (e.g., 'origin', 'host', etc. as mentioned in the examples above)
+    - `-b`, `--benchmark`: Run benchmark for piped file functions
+    - `-p`, `--path`: Process all the URLs in a given file
+    - `-o`, `--output`: Output the results of the parsing to a file
+
+### Performance
+
+Our `adaparse` tool may outperform other popular alternatives. We offer a [collection of
+sets of URLs](https://github.com/ada-url/url-various-datasets) for benchmarking purposes.
+The following results are on a MacBook Air 2022 (M2 processor) using LLVM 14. We
+compare against [trurl](https://github.com/curl/trurl) version 0.6 (libcurl/7.87.0).
+
+<details>
+<summary>With the wikipedia_100k dataset, we get that adaparse can generate normalized URLs about **three times faster than trurl**.</summary>
+```
+time cat url-various-datasets/wikipedia/wikipedia_100k.txt| trurl --url-file - &> /dev/null   1
+cat url-various-datasets/wikipedia/wikipedia_100k.txt  0,00s user 0,01s system 3% cpu 0,179 total
+trurl --url-file - &> /dev/null  0,14s user 0,03s system 98% cpu 0,180 total
+
+
+time cat url-various-datasets/wikipedia/wikipedia_100k.txt| ./build/tools/cli/adaparse -g href &> /dev/null
+cat url-various-datasets/wikipedia/wikipedia_100k.txt  0,00s user 0,00s system 10% cpu 0,056 total
+./build/tools/cli/adaparse -g href &> /dev/null  0,05s user 0,00s system 93% cpu 0,055 total
+```
+</details>
+
+<details>
+<summary>With the top100 dataset, the adaparse tool is **twice as fast as the trurl**.</summary>
+```
+time cat url-various-datasets/top100/top100.txt| trurl --url-file - &> /dev/null              1
+cat url-various-datasets/top100/top100.txt  0,00s user 0,00s system 4% cpu 0,115 total
+trurl --url-file - &> /dev/null  0,09s user 0,02s system 97% cpu 0,113 total
+
+time cat url-various-datasets/top100/top100.txt| ./build/tools/cli/adaparse -g href &> /dev/null
+cat url-various-datasets/top100/top100.txt  0,00s user 0,01s system 11% cpu 0,062 total
+./build/tools/cli/adaparse -g href &> /dev/null  0,05s user 0,00s system 94% cpu 0,061 total
+```
+</details>
+
+
+#### Comparison
+
+```
+wikipedia 100k
+ada ▏   55 ms ███████▋
+trurl ▏  180 ms █████████████████████████
+
+top100
+ada ▏   61 ms █████████████▍
+trurl ▏  113 ms █████████████████████████
+```
+
+The results will vary depending on your system. We invite you to run your own benchmarks.
+
+### Usage/Examples
+
+#### Well-formatted URL
+
+```bash 
+adaparse "http://www.google.com"
+```
+Output: 
+
+```
+http://www.google.com
+```
+
+#### Diagram
+
+```bash
+adaparse -d http://www.google.com/bal\?a\=\=11\#fddfds
+```
+
+Output:
+
+```
+ http://www.google.com/bal?a==11#fddfds [38 bytes]
+      | |             |   |     |
+      | |             |   |     `------ hash_start
+      | |             |   `------------ search_start 25
+      | |             `---------------- pathname_start 21
+      | |             `---------------- host_end 21
+      | `------------------------------ host_start 7
+      | `------------------------------ username_end 7
+      `-------------------------------- protocol_end 5
+```
+
+#### Pipe Operator
+
+Ada can process URLs from piped input, making it easy to integrate with other command-line tools
+that produce ASCII or UTF-8 outputs. Here's an example of how to pipe the output of another command into Ada.
+Given a list of URLs, one by line, we may query the normalized URL string (`href`) and detect any malformed URL:
+
+```bash
+cat dragonball_url.txt | adaparse --get href
+```
+
+Output:
+```
+http://www.goku.com
+http://www.vegeta.com
+http://www.gohan.com
+
+```
+
+Our tool supports the passing of arguments to each URL in said file so
+that you can query for the hash, the host, the protocol, the port, 
+the origin, the search, the password, the username, the pathname
+or the hostname:
+
+```bash
+cat dragonball_url.txt  | adaparse -g host
+```
+
+Output:
+```
+www.goku.com
+www.vegeta.com
+www.gohan.com
+```
+
+If you omit `-g`, it will only provide a list of invalid URLs. This might be
+useful if you want to valid quickly a list of URLs.
+
+### Benchmark Runner
+
+The benchmark flag can be used to output the time it takes to process piped input:
+
+```bash
+cat wikipedia_100k.txt | adaparse -b
+```
+
+Output:
+```
+Invalid URL: 1968:_Die_Kinder_der_Diktatur
+Invalid URL: 58957:_The_Bluegrass_Guitar_Collection
+Invalid URL: 650luc:_Gangsta_Grillz
+Invalid URL: Q4%3A57
+Invalid URL: Q10%3A47
+Invalid URL: Q5%3A45
+Invalid URL: Q40%3A28
+Invalid URL: 1:1_scale
+Invalid URL: 1893:_A_World's_Fair_Mystery
+Invalid URL: 12:51_(Krissy_%26_Ericka_song)
+Invalid URL: 111:_A_Nelson_Number
+Invalid URL: 7:00AM-8%3A00AM_(24_season_5)
+Invalid URL: Q53%3A31
+read 5209265 bytes in 32819917 ns using 100000 lines, used 160 loads
+0.1587226744053009 GB/s
+```
+
+#### Saving result to file system
+
+There is an option to output to a file on disk:
+
+```bash 
+cat wikipedia_100k.txt | adaparse -o wiki_output.txt
+```
+
+As well as read in from a file on disk without going through cat:
+
+```bash
+adaparse -p wikipedia_top_100_txt
+```
+
+#### Advanced Usage
+
+You may also combine different flags together. E.g. Say one wishes to extract only the host from URLs stored in wikipedia.txt and output it to the test_write.txt file:
+
+```bash
+adaparse" -p wikipedia_top100.txt -o test_write.txt -g host -b
+```
+
+Output:
+```bash
+read 5209265 bytes in 26737131 ns using 100000 lines, total_bytes is 5209265 used 160 loads
+0.19483260937757307 GB/s(base)
+```
+
+Content of test_write.txt:
+```bash
+(---snip---)
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+en.wikipedia.org
+(---snip---)
+```
diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html
new file mode 100644 (file)
index 0000000..96cecd0
--- /dev/null
@@ -0,0 +1,7 @@
+<!-- HTML footer for doxygen 1.9.6-->
+<!-- start footer part -->
+<!--BEGIN GENERATE_TREEVIEW-->
+<div id="nav-path"><!-- id is needed for treeview function! --></div>
+<!--END GENERATE_TREEVIEW-->
+</body>
+</html>
diff --git a/docs/doxygen/header.html b/docs/doxygen/header.html
new file mode 100644 (file)
index 0000000..88c4029
--- /dev/null
@@ -0,0 +1,84 @@
+<!-- HTML header for doxygen 1.9.6-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
+<head>
+<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
+<meta http-equiv="X-UA-Compatible" content="IE=11"/>
+<meta name="generator" content="Doxygen $doxygenversion"/>
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
+<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
+<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
+<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
+<!--BEGIN DISABLE_INDEX-->
+  <!--BEGIN FULL_SIDEBAR-->
+<script type="text/javascript">var page_layout=1;</script>
+  <!--END FULL_SIDEBAR-->
+<!--END DISABLE_INDEX-->
+<script type="text/javascript" src="$relpath^jquery.js"></script>
+<script type="text/javascript" src="$relpath^dynsections.js"></script>
+<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
+<script type="text/javascript" src="$relpath^doxygen-awesome-fragment-copy-button.js"></script>
+<script type="text/javascript" src="$relpath^doxygen-awesome-paragraph-link.js"></script>
+<script type="text/javascript" src="$relpath^doxygen-awesome-interactive-toc.js"></script>
+<script type="text/javascript">
+  DoxygenAwesomeDarkModeToggle.init()
+  DoxygenAwesomeFragmentCopyButton.init()
+  DoxygenAwesomeParagraphLink.init()
+  DoxygenAwesomeInteractiveToc.init()
+</script>
+$treeview
+$search
+$mathjax
+$darkmode
+<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
+$extrastylesheet
+</head>
+<body>
+<!--BEGIN DISABLE_INDEX-->
+  <!--BEGIN FULL_SIDEBAR-->
+<div id="side-nav" class="ui-resizable side-nav-resizable"><!-- do not remove this div, it is closed by doxygen! -->
+  <!--END FULL_SIDEBAR-->
+<!--END DISABLE_INDEX-->
+
+<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
+
+<!--BEGIN TITLEAREA-->
+<div id="titlearea">
+<table cellspacing="0" cellpadding="0">
+ <tbody>
+ <tr id="projectrow">
+  <!--BEGIN PROJECT_LOGO-->
+  <td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
+  <!--END PROJECT_LOGO-->
+  <!--BEGIN PROJECT_NAME-->
+  <td id="projectalign">
+   <div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span><!--END PROJECT_NUMBER-->
+   </div>
+   <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
+  </td>
+  <!--END PROJECT_NAME-->
+  <!--BEGIN !PROJECT_NAME-->
+   <!--BEGIN PROJECT_BRIEF-->
+    <td>
+    <div id="projectbrief">$projectbrief</div>
+    </td>
+   <!--END PROJECT_BRIEF-->
+  <!--END !PROJECT_NAME-->
+  <!--BEGIN DISABLE_INDEX-->
+   <!--BEGIN SEARCHENGINE-->
+     <!--BEGIN !FULL_SIDEBAR-->
+    <td>$searchbox</td>
+     <!--END !FULL_SIDEBAR-->
+   <!--END SEARCHENGINE-->
+  <!--END DISABLE_INDEX-->
+ </tr>
+  <!--BEGIN SEARCHENGINE-->
+   <!--BEGIN FULL_SIDEBAR-->
+   <tr><td colspan="2">$searchbox</td></tr>
+   <!--END FULL_SIDEBAR-->
+  <!--END SEARCHENGINE-->
+ </tbody>
+</table>
+</div>
+<!--END TITLEAREA-->
+<!-- end header part -->
diff --git a/doxygen b/doxygen
new file mode 100644 (file)
index 0000000..2c7abb1
--- /dev/null
+++ b/doxygen
@@ -0,0 +1,2741 @@
+# Doxyfile 1.9.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "Ada"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = "2.7.8"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Fast spec-compliant URL parser"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "docs"
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = YES
+
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+CREATE_SUBDIRS_LEVEL   = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 2
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = YES
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = YES
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = YES
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = YES
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# will also hide undocumented C++ concepts if enabled. This option has no effect
+# if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# Possible values are: SYSTEM, NO and YES.
+# The default value is: SYSTEM.
+
+CASE_SENSE_NAMES       = SYSTEM
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE        = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = NO
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = NO
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = NO
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= NO
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = YES
+
+# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
+# undocumented enumeration values. If set to NO, doxygen will accept
+# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: NO.
+
+WARN_IF_UNDOC_ENUM_VAL = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT       = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  =
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# See also: INPUT_FILE_ENCODING
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
+# character encoding on a per file pattern basis. Doxygen will compare the file
+# name with each pattern and apply the encoding instead of the default
+# INPUT_ENCODING) if there is a match. The character encodings are a list of the
+# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
+# "INPUT_ENCODING" for further information on supported encodings.
+
+INPUT_FILE_ENCODING    =
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.l \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f18 \
+                         *.f \
+                         *.for \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf \
+                         *.ice
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = benchmarks, tests, Testing, tools, build, docs/html, docs/theme, include/ada/expected.h, singleheader, include/ada/log.h, SECURITY.md, .github, docs/RELEASE.md
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = "*/test/*"
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# ANamespace::AClass, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that doxygen will use the data processed and written to standard output
+# for further processing, therefore nothing else, like debug statements or used
+# commands (so in case of a Windows batch file always use @echo OFF), should be
+# written to standard output.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = README.md
+
+# The Fortran standard specifies that for fixed formatted Fortran code all
+# characters from position 72 are to be considered as comment. A common
+# extension is to allow longer lines before the automatic comment starts. The
+# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
+# be processed before the automatic comment starts.
+# Minimum value: 7, maximum value: 10000, default value: 72.
+
+FORTRAN_COMMENT_AFTER  = 72
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = NO
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
+# that should be ignored while generating the index headers. The IGNORE_PREFIX
+# tag works for classes, function and member names. The entity will be placed in
+# the alphabetical list under the first letter of the entity name that remains
+# after removing the prefix.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            = ./docs/doxygen/header.html
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            = ./docs/doxygen/footer.html
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# Note: Since the styling of scrollbars can currently not be overruled in
+# Webkit/Chromium, the styling will be left out of the default doxygen.css if
+# one or more extra stylesheets have been specified. So if scrollbar
+# customization is desired it has to be added explicitly. For an example see the
+# documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  = docs/theme/doxygen-awesome.css \
+                         docs/theme/doxygen-awesome-sidebar-only.css \
+                         docs/theme/doxygen-awesome-sidebar-only-darkmode-toggle.css
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       = docs/theme/doxygen-awesome-darkmode-toggle.js \
+                         docs/theme/doxygen-awesome-fragment-copy-button.js \
+                         docs/theme/doxygen-awesome-paragraph-link.js \
+                         docs/theme/doxygen-awesome-interactive-toc.js
+
+# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
+# should be rendered with a dark or light theme.
+# Possible values are: LIGHT always generate light mode output, DARK always
+# generate dark mode output, AUTO_LIGHT automatically set the mode according to
+# the user preference, use light mode if no preference is set (the default),
+# AUTO_DARK automatically set the mode according to the user preference, use
+# dark mode if no preference is set and TOGGLE allow to user to switch between
+# light and dark mode via a button.
+# The default value is: AUTO_LIGHT.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE        = LIGHT
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 209
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 255
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 113
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL         =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = YES
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR           = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+OBFUSCATE_EMAILS       = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION        = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        =
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         =
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
+# subgraphs. When you want a differently looking font in the dot files that
+# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
+# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
+# Edge and Graph Attributes specification</a> You need to make sure dot is able
+# to find the font, which can be done by putting it in a standard location or by
+# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font. Default graphviz fontsize is 14.
+# The default value is: fontname=Helvetica,fontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_COMMON_ATTR        = "fontname=Helvetica,fontsize=10"
+
+# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
+# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
+# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
+# arrows shapes.</a>
+# The default value is: labelfontname=Helvetica,labelfontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_EDGE_ATTR          = "labelfontname=Helvetica,labelfontsize=10"
+
+# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
+# around nodes set 'shape=plain' or 'shape=plaintext' <a
+# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
+# The default value is: shape=box,height=0.2,width=0.4.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NODE_ATTR          = "shape=box,height=0.2,width=0.4"
+
+# You can set the path where dot can find font specified with fontname in
+# DOT_COMMON_ATTR and others dot attributes.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts /
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
+# The default value is: YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. See also the chapter Grouping
+# in the manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = YES
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = YES
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH    = 1
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
+DOT_CLEANUP            = YES
diff --git a/fuzz/build.sh b/fuzz/build.sh
new file mode 100755 (executable)
index 0000000..bf37656
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+cd $SRC/ada-url
+
+mkdir build
+AMALGAMATE_OUTPUT_PATH=./build/singleheader python3 singleheader/amalgamate.py
+
+$CXX $CFLAGS $CXXFLAGS \
+     -std=c++17 \
+     -I build/singleheader \
+     -c fuzz/parse.cc -o parse.o
+
+$CXX $CFLAGS $CXXFLAGS $LIB_FUZZING_ENGINE parse.o \
+     -o $OUT/parse
diff --git a/fuzz/parse.cc b/fuzz/parse.cc
new file mode 100644 (file)
index 0000000..8041093
--- /dev/null
@@ -0,0 +1,140 @@
+#include <fuzzer/FuzzedDataProvider.h>
+#include <memory>
+#include <string>
+
+#include "ada.cpp"
+#include "ada.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  FuzzedDataProvider fdp(data, size);
+  std::string source = fdp.ConsumeRandomLengthString(256);
+  std::string base_source = fdp.ConsumeRandomLengthString(256);
+
+  /**
+   * ada::parse<ada::url>
+   */
+  auto out_url = ada::parse<ada::url>(source);
+
+  if (out_url) {
+    std::string input = fdp.ConsumeRandomLengthString(256);
+    out_url->set_protocol(input);
+    out_url->set_username(input);
+    out_url->set_password(input);
+    out_url->set_hostname(input);
+    out_url->set_host(input);
+    out_url->set_pathname(input);
+    out_url->set_search(input);
+    out_url->set_hash(input);
+    out_url->set_port(input);
+
+    // volatile forces the compiler to store the results without undue
+    // optimizations
+    volatile size_t length = 0;
+
+    // getters
+    length += out_url->get_protocol().size();
+    length += out_url->get_username().size();
+    length += out_url->get_password().size();
+    length += out_url->get_hostname().size();
+    length += out_url->get_host().size();
+    length += out_url->get_pathname().size();
+    length += out_url->get_search().size();
+    length += out_url->get_hash().size();
+    length += out_url->get_origin().size();
+    length += out_url->get_port().size();
+  }
+
+  /**
+   * ada::parse<ada::url_aggregator>
+   */
+  auto out_aggregator = ada::parse<ada::url_aggregator>(source);
+
+  if (out_aggregator) {
+    std::string input = fdp.ConsumeRandomLengthString(256);
+    out_aggregator->set_protocol(input);
+    out_aggregator->set_username(input);
+    out_aggregator->set_password(input);
+    out_aggregator->set_hostname(input);
+    out_aggregator->set_host(input);
+    out_aggregator->set_pathname(input);
+    out_aggregator->set_search(input);
+    out_aggregator->set_hash(input);
+    out_aggregator->set_port(input);
+
+    // volatile forces the compiler to store the results without undue
+    // optimizations
+    volatile size_t length = 0;
+
+    // getters
+    length += out_aggregator->get_protocol().size();
+    length += out_aggregator->get_username().size();
+    length += out_aggregator->get_password().size();
+    length += out_aggregator->get_hostname().size();
+    length += out_aggregator->get_host().size();
+    length += out_aggregator->get_pathname().size();
+    length += out_aggregator->get_search().size();
+    length += out_aggregator->get_hash().size();
+    length += out_aggregator->get_origin().size();
+    length += out_aggregator->get_port().size();
+
+    // clear methods
+    out_aggregator->clear_port();
+    out_aggregator->clear_search();
+    out_aggregator->clear_hash();
+  }
+
+  /**
+   * ada::can_parse
+   */
+  auto base_source_view =
+      std::string_view(base_source.data(), base_source.length());
+  ada::can_parse(source);
+  ada::can_parse(source, &base_source_view);
+
+  /**
+   * ada::idna
+   */
+  ada::idna::to_ascii(source);
+  ada::idna::to_unicode(source);
+
+  /**
+   * Node.js specific
+   */
+  ada::href_from_file(source);
+
+  /**
+   * ada::url_search_params
+   */
+  auto initialized = ada::url_search_params(base_source_view);
+
+  auto search_params = ada::url_search_params();
+  search_params.append(source, base_source);
+  search_params.set(source, base_source);
+  search_params.to_string();
+  if (!search_params.has(base_source)) {
+    search_params.append(base_source, source);
+  }
+  search_params.remove(source);
+  search_params.remove(source, base_source);
+  if (search_params.has(base_source, source)) {
+    search_params.remove(base_source);
+    search_params.remove(base_source, source);
+  }
+
+  auto keys = search_params.get_keys();
+  while (keys.has_next()) {
+    keys.next();
+  }
+
+  auto values = search_params.get_values();
+  while (values.has_next()) {
+    values.next();
+  }
+
+  auto entries = search_params.get_entries();
+  while (entries.has_next()) {
+    entries.next();
+  }
+
+  return 0;
+}  // extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
diff --git a/include/ada.h b/include/ada.h
new file mode 100644 (file)
index 0000000..667c7fc
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * @file ada.h
+ * @brief Includes all definitions for Ada.
+ */
+#ifndef ADA_H
+#define ADA_H
+
+#include "ada/ada_idna.h"
+#include "ada/character_sets-inl.h"
+#include "ada/checkers-inl.h"
+#include "ada/common_defs.h"
+#include "ada/log.h"
+#include "ada/encoding_type.h"
+#include "ada/helpers.h"
+#include "ada/parser.h"
+#include "ada/scheme-inl.h"
+#include "ada/serializers.h"
+#include "ada/state.h"
+#include "ada/unicode.h"
+#include "ada/url_base.h"
+#include "ada/url_base-inl.h"
+#include "ada/url-inl.h"
+#include "ada/url_components.h"
+#include "ada/url_aggregator.h"
+#include "ada/url_aggregator-inl.h"
+#include "ada/url_search_params.h"
+#include "ada/url_search_params-inl.h"
+
+// Public API
+#include "ada/ada_version.h"
+#include "ada/implementation.h"
+
+#endif  // ADA_H
diff --git a/include/ada/ada_idna.h b/include/ada/ada_idna.h
new file mode 100644 (file)
index 0000000..8d91625
--- /dev/null
@@ -0,0 +1,149 @@
+/* auto-generated on 2023-09-19 15:58:51 -0400. Do not edit! */
+/* begin file include/idna.h */
+#ifndef ADA_IDNA_H
+#define ADA_IDNA_H
+
+/* begin file include/ada/idna/unicode_transcoding.h */
+#ifndef ADA_IDNA_UNICODE_TRANSCODING_H
+#define ADA_IDNA_UNICODE_TRANSCODING_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output);
+
+size_t utf8_length_from_utf32(const char32_t* buf, size_t len);
+
+size_t utf32_length_from_utf8(const char* buf, size_t len);
+
+size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output);
+
+}  // namespace ada::idna
+
+#endif  // ADA_IDNA_UNICODE_TRANSCODING_H
+/* end file include/ada/idna/unicode_transcoding.h */
+/* begin file include/ada/idna/mapping.h */
+#ifndef ADA_IDNA_MAPPING_H
+#define ADA_IDNA_MAPPING_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+// If the input is ascii, then the mapping is just -> lower case.
+void ascii_map(char* input, size_t length);
+// check whether an ascii string needs mapping
+bool ascii_has_upper_case(char* input, size_t length);
+// Map the characters according to IDNA, returning the empty string on error.
+std::u32string map(std::u32string_view input);
+
+}  // namespace ada::idna
+
+#endif
+/* end file include/ada/idna/mapping.h */
+/* begin file include/ada/idna/normalization.h */
+#ifndef ADA_IDNA_NORMALIZATION_H
+#define ADA_IDNA_NORMALIZATION_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+// Normalize the characters according to IDNA (Unicode Normalization Form C).
+void normalize(std::u32string& input);
+
+}  // namespace ada::idna
+#endif
+/* end file include/ada/idna/normalization.h */
+/* begin file include/ada/idna/punycode.h */
+#ifndef ADA_IDNA_PUNYCODE_H
+#define ADA_IDNA_PUNYCODE_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+bool punycode_to_utf32(std::string_view input, std::u32string& out);
+bool verify_punycode(std::string_view input);
+bool utf32_to_punycode(std::u32string_view input, std::string& out);
+
+}  // namespace ada::idna
+
+#endif  // ADA_IDNA_PUNYCODE_H
+/* end file include/ada/idna/punycode.h */
+/* begin file include/ada/idna/validity.h */
+#ifndef ADA_IDNA_VALIDITY_H
+#define ADA_IDNA_VALIDITY_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+/**
+ * @see https://www.unicode.org/reports/tr46/#Validity_Criteria
+ */
+bool is_label_valid(std::u32string_view label);
+
+}  // namespace ada::idna
+
+#endif  // ADA_IDNA_VALIDITY_H
+/* end file include/ada/idna/validity.h */
+/* begin file include/ada/idna/to_ascii.h */
+#ifndef ADA_IDNA_TO_ASCII_H
+#define ADA_IDNA_TO_ASCII_H
+
+#include <string>
+#include <string_view>
+
+namespace ada::idna {
+
+// Converts a domain (e.g., www.google.com) possibly containing international
+// characters to an ascii domain (with punycode). It will not do percent
+// decoding: percent decoding should be done prior to calling this function. We
+// do not remove tabs and spaces, they should have been removed prior to calling
+// this function. We also do not trim control characters. We also assume that
+// the input is not empty. We return "" on error.
+//
+//
+// This function may accept or even produce invalid domains.
+std::string to_ascii(std::string_view ut8_string);
+
+// Returns true if the string contains a forbidden code point according to the
+// WHATGL URL specification:
+// https://url.spec.whatwg.org/#forbidden-domain-code-point
+bool contains_forbidden_domain_code_point(std::string_view ascii_string);
+
+bool begins_with(std::u32string_view view, std::u32string_view prefix);
+bool begins_with(std::string_view view, std::string_view prefix);
+
+bool constexpr is_ascii(std::u32string_view view);
+bool constexpr is_ascii(std::string_view view);
+
+}  // namespace ada::idna
+
+#endif  // ADA_IDNA_TO_ASCII_H
+/* end file include/ada/idna/to_ascii.h */
+/* begin file include/ada/idna/to_unicode.h */
+
+#ifndef ADA_IDNA_TO_UNICODE_H
+#define ADA_IDNA_TO_UNICODE_H
+
+#include <string_view>
+
+namespace ada::idna {
+
+std::string to_unicode(std::string_view input);
+
+}  // namespace ada::idna
+
+#endif  // ADA_IDNA_TO_UNICODE_H
+/* end file include/ada/idna/to_unicode.h */
+
+#endif
+/* end file include/idna.h */
diff --git a/include/ada/ada_version.h b/include/ada/ada_version.h
new file mode 100644 (file)
index 0000000..7a28b2a
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * @file ada_version.h
+ * @brief Definitions for Ada's version number.
+ */
+#ifndef ADA_ADA_VERSION_H
+#define ADA_ADA_VERSION_H
+
+#define ADA_VERSION "2.7.8"
+
+namespace ada {
+
+enum {
+  ADA_VERSION_MAJOR = 2,
+  ADA_VERSION_MINOR = 7,
+  ADA_VERSION_REVISION = 8,
+};
+
+}  // namespace ada
+
+#endif  // ADA_ADA_VERSION_H
diff --git a/include/ada/character_sets-inl.h b/include/ada/character_sets-inl.h
new file mode 100644 (file)
index 0000000..32b5c3a
--- /dev/null
@@ -0,0 +1,521 @@
+/**
+ * @file character_sets-inl.h
+ * @brief Definitions of the character sets used by unicode functions.
+ * @author Node.js
+ * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc
+ */
+#ifndef ADA_CHARACTER_SETS_INL_H
+#define ADA_CHARACTER_SETS_INL_H
+
+#include "ada/character_sets.h"
+
+/**
+ * These functions are not part of our public API and may
+ * change at any time.
+ * @private
+ */
+namespace ada::character_sets {
+
+constexpr char hex[1024] =
+    "%00\0%01\0%02\0%03\0%04\0%05\0%06\0%07\0"
+    "%08\0%09\0%0A\0%0B\0%0C\0%0D\0%0E\0%0F\0"
+    "%10\0%11\0%12\0%13\0%14\0%15\0%16\0%17\0"
+    "%18\0%19\0%1A\0%1B\0%1C\0%1D\0%1E\0%1F\0"
+    "%20\0%21\0%22\0%23\0%24\0%25\0%26\0%27\0"
+    "%28\0%29\0%2A\0%2B\0%2C\0%2D\0%2E\0%2F\0"
+    "%30\0%31\0%32\0%33\0%34\0%35\0%36\0%37\0"
+    "%38\0%39\0%3A\0%3B\0%3C\0%3D\0%3E\0%3F\0"
+    "%40\0%41\0%42\0%43\0%44\0%45\0%46\0%47\0"
+    "%48\0%49\0%4A\0%4B\0%4C\0%4D\0%4E\0%4F\0"
+    "%50\0%51\0%52\0%53\0%54\0%55\0%56\0%57\0"
+    "%58\0%59\0%5A\0%5B\0%5C\0%5D\0%5E\0%5F\0"
+    "%60\0%61\0%62\0%63\0%64\0%65\0%66\0%67\0"
+    "%68\0%69\0%6A\0%6B\0%6C\0%6D\0%6E\0%6F\0"
+    "%70\0%71\0%72\0%73\0%74\0%75\0%76\0%77\0"
+    "%78\0%79\0%7A\0%7B\0%7C\0%7D\0%7E\0%7F\0"
+    "%80\0%81\0%82\0%83\0%84\0%85\0%86\0%87\0"
+    "%88\0%89\0%8A\0%8B\0%8C\0%8D\0%8E\0%8F\0"
+    "%90\0%91\0%92\0%93\0%94\0%95\0%96\0%97\0"
+    "%98\0%99\0%9A\0%9B\0%9C\0%9D\0%9E\0%9F\0"
+    "%A0\0%A1\0%A2\0%A3\0%A4\0%A5\0%A6\0%A7\0"
+    "%A8\0%A9\0%AA\0%AB\0%AC\0%AD\0%AE\0%AF\0"
+    "%B0\0%B1\0%B2\0%B3\0%B4\0%B5\0%B6\0%B7\0"
+    "%B8\0%B9\0%BA\0%BB\0%BC\0%BD\0%BE\0%BF\0"
+    "%C0\0%C1\0%C2\0%C3\0%C4\0%C5\0%C6\0%C7\0"
+    "%C8\0%C9\0%CA\0%CB\0%CC\0%CD\0%CE\0%CF\0"
+    "%D0\0%D1\0%D2\0%D3\0%D4\0%D5\0%D6\0%D7\0"
+    "%D8\0%D9\0%DA\0%DB\0%DC\0%DD\0%DE\0%DF\0"
+    "%E0\0%E1\0%E2\0%E3\0%E4\0%E5\0%E6\0%E7\0"
+    "%E8\0%E9\0%EA\0%EB\0%EC\0%ED\0%EE\0%EF\0"
+    "%F0\0%F1\0%F2\0%F3\0%F4\0%F5\0%F6\0%F7\0"
+    "%F8\0%F9\0%FA\0%FB\0%FC\0%FD\0%FE\0%FF";
+
+constexpr uint8_t C0_CONTROL_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 40     41     42     43     44     45     46     47
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t SPECIAL_QUERY_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
+    // 40     41     42     43     44     45     46     47
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t QUERY_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
+    // 40     41     42     43     44     45     46     47
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t FRAGMENT_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x01 | 0x00 | 0x04 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x00,
+    // 40     41     42     43     44     45     46     47
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t USERINFO_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x80,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 40     41     42     43     44     45     46     47
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t PATH_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x01 | 0x00 | 0x04 | 0x08 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x10 | 0x00 | 0x40 | 0x80,
+    // 40     41     42     43     44     45     46     47
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x00 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+constexpr uint8_t WWW_FORM_URLENCODED_PERCENT_ENCODE[32] = {
+    // 00     01     02     03     04     05     06     07
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 08     09     0A     0B     0C     0D     0E     0F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 10     11     12     13     14     15     16     17
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 18     19     1A     1B     1C     1D     1E     1F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 20     21     22     23     24     25     26     27
+    0x00 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 28     29     2A     2B     2C     2D     2E     2F
+    0x01 | 0x02 | 0x00 | 0x08 | 0x10 | 0x00 | 0x00 | 0x80,
+    // 30     31     32     33     34     35     36     37
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 38     39     3A     3B     3C     3D     3E     3F
+    0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 40     41     42     43     44     45     46     47
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 48     49     4A     4B     4C     4D     4E     4F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 50     51     52     53     54     55     56     57
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 58     59     5A     5B     5C     5D     5E     5F
+    0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x40 | 0x00,
+    // 60     61     62     63     64     65     66     67
+    0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 68     69     6A     6B     6C     6D     6E     6F
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 70     71     72     73     74     75     76     77
+    0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
+    // 78     79     7A     7B     7C     7D     7E     7F
+    0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 80     81     82     83     84     85     86     87
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 88     89     8A     8B     8C     8D     8E     8F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 90     91     92     93     94     95     96     97
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // 98     99     9A     9B     9C     9D     9E     9F
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A0     A1     A2     A3     A4     A5     A6     A7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // A8     A9     AA     AB     AC     AD     AE     AF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B0     B1     B2     B3     B4     B5     B6     B7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // B8     B9     BA     BB     BC     BD     BE     BF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C0     C1     C2     C3     C4     C5     C6     C7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // C8     C9     CA     CB     CC     CD     CE     CF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D0     D1     D2     D3     D4     D5     D6     D7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // D8     D9     DA     DB     DC     DD     DE     DF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E0     E1     E2     E3     E4     E5     E6     E7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // E8     E9     EA     EB     EC     ED     EE     EF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F0     F1     F2     F3     F4     F5     F6     F7
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
+    // F8     F9     FA     FB     FC     FD     FE     FF
+    0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80};
+
+ada_really_inline bool bit_at(const uint8_t a[], const uint8_t i) {
+  return !!(a[i >> 3] & (1 << (i & 7)));
+}
+
+}  // namespace ada::character_sets
+
+#endif  // ADA_CHARACTER_SETS_INL_H
diff --git a/include/ada/character_sets.h b/include/ada/character_sets.h
new file mode 100644 (file)
index 0000000..5728f31
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * @file character_sets.h
+ * @brief Declaration of the character sets used by unicode functions.
+ * @author Node.js
+ * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc
+ */
+#ifndef ADA_CHARACTER_SETS_H
+#define ADA_CHARACTER_SETS_H
+
+#include "ada/common_defs.h"
+#include <cstdint>
+
+/**
+ * These functions are not part of our public API and may
+ * change at any time.
+ * @private
+ * @namespace ada::character_sets
+ * @brief Includes the definitions for unicode character sets.
+ */
+namespace ada::character_sets {
+ada_really_inline bool bit_at(const uint8_t a[], uint8_t i);
+}  // namespace ada::character_sets
+
+#endif  // ADA_CHARACTER_SETS_H
diff --git a/include/ada/checkers-inl.h b/include/ada/checkers-inl.h
new file mode 100644 (file)
index 0000000..4bf05b6
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * @file checkers-inl.h
+ * @brief Definitions for URL specific checkers used within Ada.
+ */
+#ifndef ADA_CHECKERS_INL_H
+#define ADA_CHECKERS_INL_H
+
+#include "ada/common_defs.h"
+
+#include <algorithm>
+#include <string_view>
+#include <cstring>
+
+namespace ada::checkers {
+
+inline bool has_hex_prefix_unsafe(std::string_view input) {
+  // This is actually efficient code, see has_hex_prefix for the assembly.
+  uint32_t value_one = 1;
+  bool is_little_endian = (reinterpret_cast<char*>(&value_one)[0] == 1);
+  uint16_t word0x{};
+  std::memcpy(&word0x, "0x", 2);  // we would use bit_cast in C++20 and the
+                                  // function could be constexpr.
+  uint16_t two_first_bytes{};
+  std::memcpy(&two_first_bytes, input.data(), 2);
+  if (is_little_endian) {
+    two_first_bytes |= 0x2000;
+  } else {
+    two_first_bytes |= 0x020;
+  }
+  return two_first_bytes == word0x;
+}
+
+inline bool has_hex_prefix(std::string_view input) {
+  return input.size() >= 2 && has_hex_prefix_unsafe(input);
+}
+
+constexpr bool is_digit(char x) noexcept { return (x >= '0') & (x <= '9'); }
+
+constexpr char to_lower(char x) noexcept { return (x | 0x20); }
+
+constexpr bool is_alpha(char x) noexcept {
+  return (to_lower(x) >= 'a') && (to_lower(x) <= 'z');
+}
+
+inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept {
+  return input.size() >= 2 &&
+         (is_alpha(input[0]) && ((input[1] == ':') || (input[1] == '|'))) &&
+         ((input.size() == 2) || (input[2] == '/' || input[2] == '\\' ||
+                                  input[2] == '?' || input[2] == '#'));
+}
+
+inline constexpr bool is_normalized_windows_drive_letter(
+    std::string_view input) noexcept {
+  return input.size() >= 2 && (is_alpha(input[0]) && (input[1] == ':'));
+}
+
+ada_really_inline bool begins_with(std::string_view view,
+                                   std::string_view prefix) {
+  // in C++20, you have view.begins_with(prefix)
+  // std::equal is constexpr in C++20
+  return view.size() >= prefix.size() &&
+         std::equal(prefix.begin(), prefix.end(), view.begin());
+}
+
+}  // namespace ada::checkers
+
+#endif  // ADA_CHECKERS_INL_H
diff --git a/include/ada/checkers.h b/include/ada/checkers.h
new file mode 100644 (file)
index 0000000..8442e99
--- /dev/null
@@ -0,0 +1,123 @@
+/**
+ * @file checkers.h
+ * @brief Declarations for URL specific checkers used within Ada.
+ */
+#ifndef ADA_CHECKERS_H
+#define ADA_CHECKERS_H
+
+#include "ada/common_defs.h"
+
+#include <string_view>
+#include <cstring>
+
+/**
+ * These functions are not part of our public API and may
+ * change at any time.
+ * @private
+ * @namespace ada::checkers
+ * @brief Includes the definitions for validation functions
+ */
+namespace ada::checkers {
+
+/**
+ * @private
+ * Assuming that x is an ASCII letter, this function returns the lower case
+ * equivalent.
+ * @details More likely to be inlined by the compiler and constexpr.
+ */
+constexpr char to_lower(char x) noexcept;
+
+/**
+ * @private
+ * Returns true if the character is an ASCII letter. Equivalent to std::isalpha
+ * but more likely to be inlined by the compiler.
+ *
+ * @attention std::isalpha is not constexpr generally.
+ */
+constexpr bool is_alpha(char x) noexcept;
+
+/**
+ * @private
+ * Check whether a string starts with 0x or 0X. The function is only
+ * safe if input.size() >=2.
+ *
+ * @see has_hex_prefix
+ */
+inline bool has_hex_prefix_unsafe(std::string_view input);
+/**
+ * @private
+ * Check whether a string starts with 0x or 0X.
+ */
+inline bool has_hex_prefix(std::string_view input);
+
+/**
+ * @private
+ * Check whether x is an ASCII digit. More likely to be inlined than
+ * std::isdigit.
+ */
+constexpr bool is_digit(char x) noexcept;
+
+/**
+ * @private
+ * @details A string starts with a Windows drive letter if all of the following
+ * are true:
+ *
+ *   - its length is greater than or equal to 2
+ *   - its first two code points are a Windows drive letter
+ *   - its length is 2 or its third code point is U+002F (/), U+005C (\), U+003F
+ * (?), or U+0023 (#).
+ *
+ * https://url.spec.whatwg.org/#start-with-a-windows-drive-letter
+ */
+inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept;
+
+/**
+ * @private
+ * @details A normalized Windows drive letter is a Windows drive letter of which
+ * the second code point is U+003A (:).
+ */
+inline constexpr bool is_normalized_windows_drive_letter(
+    std::string_view input) noexcept;
+
+/**
+ * @private
+ * @warning Will be removed when Ada requires C++20.
+ */
+ada_really_inline bool begins_with(std::string_view view,
+                                   std::string_view prefix);
+
+/**
+ * @private
+ * Returns true if an input is an ipv4 address. It is assumed that the string
+ * does not contain uppercase ASCII characters (the input should have been
+ * lowered cased before calling this function) and is not empty.
+ */
+ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept;
+
+/**
+ * @private
+ * Returns a bitset. If the first bit is set, then at least one character needs
+ * percent encoding. If the second bit is set, a \\ is found. If the third bit
+ * is set then we have a dot. If the fourth bit is set, then we have a percent
+ * character.
+ */
+ada_really_inline constexpr uint8_t path_signature(
+    std::string_view input) noexcept;
+
+/**
+ * @private
+ * Returns true if the length of the domain name and its labels are according to
+ * the specifications. The length of the domain must be 255 octets (253
+ * characters not including the last 2 which are the empty label reserved at the
+ * end). When the empty label is included (a dot at the end), the domain name
+ * can have 254 characters. The length of a label must be at least 1 and at most
+ * 63 characters.
+ * @see section 3.1. of https://www.rfc-editor.org/rfc/rfc1034
+ * @see https://www.unicode.org/reports/tr46/#ToASCII
+ */
+ada_really_inline constexpr bool verify_dns_length(
+    std::string_view input) noexcept;
+
+}  // namespace ada::checkers
+
+#endif  // ADA_CHECKERS_H
diff --git a/include/ada/common_defs.h b/include/ada/common_defs.h
new file mode 100644 (file)
index 0000000..cefed4e
--- /dev/null
@@ -0,0 +1,301 @@
+/**
+ * @file common_defs.h
+ * @brief Common definitions for cross-platform compiler support.
+ */
+#ifndef ADA_COMMON_DEFS_H
+#define ADA_COMMON_DEFS_H
+
+#ifdef _MSC_VER
+#define ADA_VISUAL_STUDIO 1
+/**
+ * We want to differentiate carefully between
+ * clang under visual studio and regular visual
+ * studio.
+ */
+#ifdef __clang__
+// clang under visual studio
+#define ADA_CLANG_VISUAL_STUDIO 1
+#else
+// just regular visual studio (best guess)
+#define ADA_REGULAR_VISUAL_STUDIO 1
+#endif  // __clang__
+#endif  // _MSC_VER
+
+#if defined(__GNUC__)
+// Marks a block with a name so that MCA analysis can see it.
+#define ADA_BEGIN_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-BEGIN " #name);
+#define ADA_END_DEBUG_BLOCK(name) __asm volatile("# LLVM-MCA-END " #name);
+#define ADA_DEBUG_BLOCK(name, block) \
+  BEGIN_DEBUG_BLOCK(name);           \
+  block;                             \
+  END_DEBUG_BLOCK(name);
+#else
+#define ADA_BEGIN_DEBUG_BLOCK(name)
+#define ADA_END_DEBUG_BLOCK(name)
+#define ADA_DEBUG_BLOCK(name, block)
+#endif
+
+// Align to N-byte boundary
+#define ADA_ROUNDUP_N(a, n) (((a) + ((n)-1)) & ~((n)-1))
+#define ADA_ROUNDDOWN_N(a, n) ((a) & ~((n)-1))
+
+#define ADA_ISALIGNED_N(ptr, n) (((uintptr_t)(ptr) & ((n)-1)) == 0)
+
+#if defined(ADA_REGULAR_VISUAL_STUDIO)
+
+#define ada_really_inline __forceinline
+#define ada_never_inline __declspec(noinline)
+
+#define ada_unused
+#define ada_warn_unused
+
+#ifndef ada_likely
+#define ada_likely(x) x
+#endif
+#ifndef ada_unlikely
+#define ada_unlikely(x) x
+#endif
+
+#define ADA_PUSH_DISABLE_WARNINGS __pragma(warning(push))
+#define ADA_PUSH_DISABLE_ALL_WARNINGS __pragma(warning(push, 0))
+#define ADA_DISABLE_VS_WARNING(WARNING_NUMBER) \
+  __pragma(warning(disable : WARNING_NUMBER))
+// Get rid of Intellisense-only warnings (Code Analysis)
+// Though __has_include is C++17, it is supported in Visual Studio 2017 or
+// better (_MSC_VER>=1910).
+#ifdef __has_include
+#if __has_include(<CppCoreCheck\Warnings.h>)
+#include <CppCoreCheck\Warnings.h>
+#define ADA_DISABLE_UNDESIRED_WARNINGS \
+  ADA_DISABLE_VS_WARNING(ALL_CPPCORECHECK_WARNINGS)
+#endif
+#endif
+
+#ifndef ADA_DISABLE_UNDESIRED_WARNINGS
+#define ADA_DISABLE_UNDESIRED_WARNINGS
+#endif
+
+#define ADA_DISABLE_DEPRECATED_WARNING ADA_DISABLE_VS_WARNING(4996)
+#define ADA_DISABLE_STRICT_OVERFLOW_WARNING
+#define ADA_POP_DISABLE_WARNINGS __pragma(warning(pop))
+
+#else  // ADA_REGULAR_VISUAL_STUDIO
+
+#define ada_really_inline inline __attribute__((always_inline))
+#define ada_never_inline inline __attribute__((noinline))
+
+#define ada_unused __attribute__((unused))
+#define ada_warn_unused __attribute__((warn_unused_result))
+
+#ifndef ada_likely
+#define ada_likely(x) __builtin_expect(!!(x), 1)
+#endif
+#ifndef ada_unlikely
+#define ada_unlikely(x) __builtin_expect(!!(x), 0)
+#endif
+
+#define ADA_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push")
+// gcc doesn't seem to disable all warnings with all and extra, add warnings
+// here as necessary
+#define ADA_PUSH_DISABLE_ALL_WARNINGS               \
+  ADA_PUSH_DISABLE_WARNINGS                         \
+  ADA_DISABLE_GCC_WARNING("-Weffc++")               \
+  ADA_DISABLE_GCC_WARNING("-Wall")                  \
+  ADA_DISABLE_GCC_WARNING("-Wconversion")           \
+  ADA_DISABLE_GCC_WARNING("-Wextra")                \
+  ADA_DISABLE_GCC_WARNING("-Wattributes")           \
+  ADA_DISABLE_GCC_WARNING("-Wimplicit-fallthrough") \
+  ADA_DISABLE_GCC_WARNING("-Wnon-virtual-dtor")     \
+  ADA_DISABLE_GCC_WARNING("-Wreturn-type")          \
+  ADA_DISABLE_GCC_WARNING("-Wshadow")               \
+  ADA_DISABLE_GCC_WARNING("-Wunused-parameter")     \
+  ADA_DISABLE_GCC_WARNING("-Wunused-variable")
+#define ADA_PRAGMA(P) _Pragma(#P)
+#define ADA_DISABLE_GCC_WARNING(WARNING) \
+  ADA_PRAGMA(GCC diagnostic ignored WARNING)
+#if defined(ADA_CLANG_VISUAL_STUDIO)
+#define ADA_DISABLE_UNDESIRED_WARNINGS \
+  ADA_DISABLE_GCC_WARNING("-Wmicrosoft-include")
+#else
+#define ADA_DISABLE_UNDESIRED_WARNINGS
+#endif
+#define ADA_DISABLE_DEPRECATED_WARNING \
+  ADA_DISABLE_GCC_WARNING("-Wdeprecated-declarations")
+#define ADA_DISABLE_STRICT_OVERFLOW_WARNING \
+  ADA_DISABLE_GCC_WARNING("-Wstrict-overflow")
+#define ADA_POP_DISABLE_WARNINGS _Pragma("GCC diagnostic pop")
+
+#endif  // MSC_VER
+
+#if defined(ADA_VISUAL_STUDIO)
+/**
+ * It does not matter here whether you are using
+ * the regular visual studio or clang under visual
+ * studio.
+ */
+#if ADA_USING_LIBRARY
+#define ADA_DLLIMPORTEXPORT __declspec(dllimport)
+#else
+#define ADA_DLLIMPORTEXPORT __declspec(dllexport)
+#endif
+#else
+#define ADA_DLLIMPORTEXPORT
+#endif
+
+/// If EXPR is an error, returns it.
+#define ADA_TRY(EXPR)   \
+  {                     \
+    auto _err = (EXPR); \
+    if (_err) {         \
+      return _err;      \
+    }                   \
+  }
+
+// __has_cpp_attribute is part of C++20
+#if !defined(__has_cpp_attribute)
+#define __has_cpp_attribute(x) 0
+#endif
+
+#if __has_cpp_attribute(gnu::noinline)
+#define ADA_ATTRIBUTE_NOINLINE [[gnu::noinline]]
+#else
+#define ADA_ATTRIBUTE_NOINLINE
+#endif
+
+namespace ada {
+[[noreturn]] inline void unreachable() {
+#ifdef __GNUC__
+  __builtin_unreachable();
+#elif defined(_MSC_VER)
+  __assume(false);
+#else
+#endif
+}
+}  // namespace ada
+
+#if defined(__GNUC__) && !defined(__clang__)
+#if __GNUC__ <= 8
+#define ADA_OLD_GCC 1
+#endif  //  __GNUC__ <= 8
+#endif  // defined(__GNUC__) && !defined(__clang__)
+
+#if ADA_OLD_GCC
+#define ada_constexpr
+#else
+#define ada_constexpr constexpr
+#endif
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+#define ADA_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#elif defined(_WIN32)
+#define ADA_IS_BIG_ENDIAN 0
+#else
+#if defined(__APPLE__) || \
+    defined(__FreeBSD__)  // defined __BYTE_ORDER__ && defined
+                          // __ORDER_BIG_ENDIAN__
+#include <machine/endian.h>
+#elif defined(sun) || \
+    defined(__sun)  // defined(__APPLE__) || defined(__FreeBSD__)
+#include <sys/byteorder.h>
+#else  // defined(__APPLE__) || defined(__FreeBSD__)
+
+#ifdef __has_include
+#if __has_include(<endian.h>)
+#include <endian.h>
+#endif  //__has_include(<endian.h>)
+#endif  //__has_include
+
+#endif  // defined(__APPLE__) || defined(__FreeBSD__)
+
+#ifndef !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__)
+#define ADA_IS_BIG_ENDIAN 0
+#endif
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define ADA_IS_BIG_ENDIAN 0
+#else  // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define ADA_IS_BIG_ENDIAN 1
+#endif  // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+
+#endif  // defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__
+
+// Unless the programmer has already set ADA_DEVELOPMENT_CHECKS,
+// we want to set it under debug builds. We detect a debug build
+// under Visual Studio when the _DEBUG macro is set. Under the other
+// compilers, we use the fact that they define __OPTIMIZE__ whenever
+// they allow optimizations.
+// It is possible that this could miss some cases where ADA_DEVELOPMENT_CHECKS
+// is helpful, but the programmer can set the macro ADA_DEVELOPMENT_CHECKS.
+// It could also wrongly set ADA_DEVELOPMENT_CHECKS (e.g., if the programmer
+// sets _DEBUG in a release build under Visual Studio, or if some compiler fails
+// to set the __OPTIMIZE__ macro).
+#if !defined(ADA_DEVELOPMENT_CHECKS) && !defined(NDEBUG)
+#ifdef _MSC_VER
+// Visual Studio seems to set _DEBUG for debug builds.
+#ifdef _DEBUG
+#define ADA_DEVELOPMENT_CHECKS 1
+#endif  // _DEBUG
+#else   // _MSC_VER
+// All other compilers appear to set __OPTIMIZE__ to a positive integer
+// when the compiler is optimizing.
+#ifndef __OPTIMIZE__
+#define ADA_DEVELOPMENT_CHECKS 1
+#endif  // __OPTIMIZE__
+#endif  // _MSC_VER
+#endif  // ADA_DEVELOPMENT_CHECKS
+
+#define ADA_STR(x) #x
+
+#if ADA_DEVELOPMENT_CHECKS
+#define ADA_REQUIRE(EXPR) \
+  {                       \
+    if (!(EXPR) { abort(); }) }
+
+#define ADA_FAIL(MESSAGE)                            \
+  do {                                               \
+    std::cerr << "FAIL: " << (MESSAGE) << std::endl; \
+    abort();                                         \
+  } while (0);
+#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE)                                    \
+  do {                                                                         \
+    if (LHS != RHS) {                                                          \
+      std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \
+      ADA_FAIL(MESSAGE);                                                       \
+    }                                                                          \
+  } while (0);
+#define ADA_ASSERT_TRUE(COND)                                               \
+  do {                                                                      \
+    if (!(COND)) {                                                          \
+      std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \
+                << std::endl;                                               \
+      ADA_FAIL(ADA_STR(COND));                                              \
+    }                                                                       \
+  } while (0);
+#else
+#define ADA_FAIL(MESSAGE)
+#define ADA_ASSERT_EQUAL(LHS, RHS, MESSAGE)
+#define ADA_ASSERT_TRUE(COND)
+#endif
+
+#ifdef ADA_VISUAL_STUDIO
+#define ADA_ASSUME(COND) __assume(COND)
+#else
+#define ADA_ASSUME(COND)       \
+  do {                         \
+    if (!(COND)) {             \
+      __builtin_unreachable(); \
+    }                          \
+  } while (0)
+#endif
+
+#if defined(__SSE2__) || defined(__x86_64__) || defined(__x86_64) || \
+    (defined(_M_AMD64) || defined(_M_X64) ||                         \
+     (defined(_M_IX86_FP) && _M_IX86_FP == 2))
+#define ADA_SSE2 1
+#endif
+
+#if defined(__aarch64__) || defined(_M_ARM64)
+#define ADA_NEON 1
+#endif
+
+#endif  // ADA_COMMON_DEFS_H
diff --git a/include/ada/encoding_type.h b/include/ada/encoding_type.h
new file mode 100644 (file)
index 0000000..931590e
--- /dev/null
@@ -0,0 +1,32 @@
+/**
+ * @file encoding_type.h
+ * @brief Definition for supported encoding types.
+ */
+#ifndef ADA_ENCODING_TYPE_H
+#define ADA_ENCODING_TYPE_H
+
+#include "ada/common_defs.h"
+#include <string>
+
+namespace ada {
+
+/**
+ * This specification defines three encodings with the same names as encoding
+ * schemes defined in the Unicode standard: UTF-8, UTF-16LE, and UTF-16BE.
+ *
+ * @see https://encoding.spec.whatwg.org/#encodings
+ */
+enum class encoding_type {
+  UTF8,
+  UTF_16LE,
+  UTF_16BE,
+};
+
+/**
+ * Convert a encoding_type to string.
+ */
+ada_warn_unused std::string to_string(encoding_type type);
+
+}  // namespace ada
+
+#endif  // ADA_ENCODING_TYPE_H
diff --git a/include/ada/expected.h b/include/ada/expected.h
new file mode 100644 (file)
index 0000000..0b0496e
--- /dev/null
@@ -0,0 +1,2515 @@
+/**
+ * @file expected.h
+ * @brief Definitions for std::expected
+ * @private Excluded from docs through the doxygen file.
+ */
+///
+// expected - An implementation of std::expected with extensions
+// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama)
+//
+// Documentation available at http://tl.tartanllama.xyz/
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this software to the
+// public domain worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication
+// along with this software. If not, see
+// <http://creativecommons.org/publicdomain/zero/1.0/>.
+///
+
+#ifndef TL_EXPECTED_HPP
+#define TL_EXPECTED_HPP
+
+#define TL_EXPECTED_VERSION_MAJOR 1
+#define TL_EXPECTED_VERSION_MINOR 1
+#define TL_EXPECTED_VERSION_PATCH 0
+
+#include <exception>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#if defined(__EXCEPTIONS) || defined(_CPPUNWIND)
+#define TL_EXPECTED_EXCEPTIONS_ENABLED
+#endif
+
+#if (defined(_MSC_VER) && _MSC_VER == 1900)
+#define TL_EXPECTED_MSVC2015
+#define TL_EXPECTED_MSVC2015_CONSTEXPR
+#else
+#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
+     !defined(__clang__))
+#define TL_EXPECTED_GCC49
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \
+     !defined(__clang__))
+#define TL_EXPECTED_GCC54
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \
+     !defined(__clang__))
+#define TL_EXPECTED_GCC55
+#endif
+
+#if !defined(TL_ASSERT)
+// can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug
+#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49)
+#include <cassert>
+#define TL_ASSERT(x) assert(x)
+#else
+#define TL_ASSERT(x)
+#endif
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \
+     !defined(__clang__))
+// GCC < 5 doesn't support overloading on const&& for member functions
+
+#define TL_EXPECTED_NO_CONSTRR
+// GCC < 5 doesn't support some standard C++11 type traits
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
+  std::has_trivial_copy_constructor<T>
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
+  std::has_trivial_copy_assign<T>
+
+// This one will be different for GCC 5.7 if it's ever supported
+#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
+  std::is_trivially_destructible<T>
+
+// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks
+// std::vector for non-copyable types
+#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__))
+#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX
+#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX
+namespace tl {
+namespace detail {
+template <class T>
+struct is_trivially_copy_constructible
+    : std::is_trivially_copy_constructible<T> {};
+#ifdef _GLIBCXX_VECTOR
+template <class T, class A>
+struct is_trivially_copy_constructible<std::vector<T, A>> : std::false_type {};
+#endif
+}  // namespace detail
+}  // namespace tl
+#endif
+
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
+  tl::detail::is_trivially_copy_constructible<T>
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
+  std::is_trivially_copy_assignable<T>
+#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
+  std::is_trivially_destructible<T>
+#else
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \
+  std::is_trivially_copy_constructible<T>
+#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \
+  std::is_trivially_copy_assignable<T>
+#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \
+  std::is_trivially_destructible<T>
+#endif
+
+#if __cplusplus > 201103L
+#define TL_EXPECTED_CXX14
+#endif
+
+#ifdef TL_EXPECTED_GCC49
+#define TL_EXPECTED_GCC49_CONSTEXPR
+#else
+#define TL_EXPECTED_GCC49_CONSTEXPR constexpr
+#endif
+
+#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \
+     defined(TL_EXPECTED_GCC49))
+#define TL_EXPECTED_11_CONSTEXPR
+#else
+#define TL_EXPECTED_11_CONSTEXPR constexpr
+#endif
+
+namespace tl {
+template <class T, class E>
+class expected;
+
+#ifndef TL_MONOSTATE_INPLACE_MUTEX
+#define TL_MONOSTATE_INPLACE_MUTEX
+class monostate {};
+
+struct in_place_t {
+  explicit in_place_t() = default;
+};
+static constexpr in_place_t in_place{};
+#endif
+
+template <class E>
+class unexpected {
+ public:
+  static_assert(!std::is_same<E, void>::value, "E must not be void");
+
+  unexpected() = delete;
+  constexpr explicit unexpected(const E &e) : m_val(e) {}
+
+  constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {}
+
+  template <class... Args, typename std::enable_if<std::is_constructible<
+                               E, Args &&...>::value>::type * = nullptr>
+  constexpr explicit unexpected(Args &&...args)
+      : m_val(std::forward<Args>(args)...) {}
+  template <
+      class U, class... Args,
+      typename std::enable_if<std::is_constructible<
+          E, std::initializer_list<U> &, Args &&...>::value>::type * = nullptr>
+  constexpr explicit unexpected(std::initializer_list<U> l, Args &&...args)
+      : m_val(l, std::forward<Args>(args)...) {}
+
+  constexpr const E &value() const & { return m_val; }
+  TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; }
+  TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); }
+  constexpr const E &&value() const && { return std::move(m_val); }
+
+ private:
+  E m_val;
+};
+
+#ifdef __cpp_deduction_guides
+template <class E>
+unexpected(E) -> unexpected<E>;
+#endif
+
+template <class E>
+constexpr bool operator==(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() == rhs.value();
+}
+template <class E>
+constexpr bool operator!=(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() != rhs.value();
+}
+template <class E>
+constexpr bool operator<(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() < rhs.value();
+}
+template <class E>
+constexpr bool operator<=(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() <= rhs.value();
+}
+template <class E>
+constexpr bool operator>(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() > rhs.value();
+}
+template <class E>
+constexpr bool operator>=(const unexpected<E> &lhs, const unexpected<E> &rhs) {
+  return lhs.value() >= rhs.value();
+}
+
+template <class E>
+unexpected<typename std::decay<E>::type> make_unexpected(E &&e) {
+  return unexpected<typename std::decay<E>::type>(std::forward<E>(e));
+}
+
+struct unexpect_t {
+  unexpect_t() = default;
+};
+static constexpr unexpect_t unexpect{};
+
+namespace detail {
+template <typename E>
+[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) {
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+  throw std::forward<E>(e);
+#else
+  (void)e;
+#ifdef _MSC_VER
+  __assume(0);
+#else
+  __builtin_unreachable();
+#endif
+#endif
+}
+
+#ifndef TL_TRAITS_MUTEX
+#define TL_TRAITS_MUTEX
+// C++14-style aliases for brevity
+template <class T>
+using remove_const_t = typename std::remove_const<T>::type;
+template <class T>
+using remove_reference_t = typename std::remove_reference<T>::type;
+template <class T>
+using decay_t = typename std::decay<T>::type;
+template <bool E, class T = void>
+using enable_if_t = typename std::enable_if<E, T>::type;
+template <bool B, class T, class F>
+using conditional_t = typename std::conditional<B, T, F>::type;
+
+// std::conjunction from C++17
+template <class...>
+struct conjunction : std::true_type {};
+template <class B>
+struct conjunction<B> : B {};
+template <class B, class... Bs>
+struct conjunction<B, Bs...>
+    : std::conditional<bool(B::value), conjunction<Bs...>, B>::type {};
+
+#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L
+#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
+#endif
+
+// In C++11 mode, there's an issue in libc++'s std::mem_fn
+// which results in a hard-error when using it in a noexcept expression
+// in some cases. This is a check to workaround the common failing case.
+#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
+template <class T>
+struct is_pointer_to_non_const_member_func : std::false_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...)>
+    : std::true_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &>
+    : std::true_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &&>
+    : std::true_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile>
+    : std::true_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile &>
+    : std::true_type {};
+template <class T, class Ret, class... Args>
+struct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile &&>
+    : std::true_type {};
+
+template <class T>
+struct is_const_or_const_ref : std::false_type {};
+template <class T>
+struct is_const_or_const_ref<T const &> : std::true_type {};
+template <class T>
+struct is_const_or_const_ref<T const> : std::true_type {};
+#endif
+
+// std::invoke from C++17
+// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround
+template <
+    typename Fn, typename... Args,
+#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND
+    typename = enable_if_t<!(is_pointer_to_non_const_member_func<Fn>::value &&
+                             is_const_or_const_ref<Args...>::value)>,
+#endif
+    typename = enable_if_t<std::is_member_pointer<decay_t<Fn>>::value>, int = 0>
+constexpr auto invoke(Fn &&f, Args &&...args) noexcept(
+    noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
+    -> decltype(std::mem_fn(f)(std::forward<Args>(args)...)) {
+  return std::mem_fn(f)(std::forward<Args>(args)...);
+}
+
+template <typename Fn, typename... Args,
+          typename = enable_if_t<!std::is_member_pointer<decay_t<Fn>>::value>>
+constexpr auto invoke(Fn &&f, Args &&...args) noexcept(
+    noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
+    -> decltype(std::forward<Fn>(f)(std::forward<Args>(args)...)) {
+  return std::forward<Fn>(f)(std::forward<Args>(args)...);
+}
+
+// std::invoke_result from C++17
+template <class F, class, class... Us>
+struct invoke_result_impl;
+
+template <class F, class... Us>
+struct invoke_result_impl<
+    F,
+    decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...), void()),
+    Us...> {
+  using type =
+      decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...));
+};
+
+template <class F, class... Us>
+using invoke_result = invoke_result_impl<F, void, Us...>;
+
+template <class F, class... Us>
+using invoke_result_t = typename invoke_result<F, Us...>::type;
+
+#if defined(_MSC_VER) && _MSC_VER <= 1900
+// TODO make a version which works with MSVC 2015
+template <class T, class U = T>
+struct is_swappable : std::true_type {};
+
+template <class T, class U = T>
+struct is_nothrow_swappable : std::true_type {};
+#else
+// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept
+namespace swap_adl_tests {
+// if swap ADL finds this then it would call std::swap otherwise (same
+// signature)
+struct tag {};
+
+template <class T>
+tag swap(T &, T &);
+template <class T, std::size_t N>
+tag swap(T (&a)[N], T (&b)[N]);
+
+// helper functions to test if an unqualified swap is possible, and if it
+// becomes std::swap
+template <class, class>
+std::false_type can_swap(...) noexcept(false);
+template <class T, class U,
+          class = decltype(swap(std::declval<T &>(), std::declval<U &>()))>
+std::true_type can_swap(int) noexcept(noexcept(swap(std::declval<T &>(),
+                                                    std::declval<U &>())));
+
+template <class, class>
+std::false_type uses_std(...);
+template <class T, class U>
+std::is_same<decltype(swap(std::declval<T &>(), std::declval<U &>())), tag>
+uses_std(int);
+
+template <class T>
+struct is_std_swap_noexcept
+    : std::integral_constant<bool,
+                             std::is_nothrow_move_constructible<T>::value &&
+                                 std::is_nothrow_move_assignable<T>::value> {};
+
+template <class T, std::size_t N>
+struct is_std_swap_noexcept<T[N]> : is_std_swap_noexcept<T> {};
+
+template <class T, class U>
+struct is_adl_swap_noexcept
+    : std::integral_constant<bool, noexcept(can_swap<T, U>(0))> {};
+}  // namespace swap_adl_tests
+
+template <class T, class U = T>
+struct is_swappable
+    : std::integral_constant<
+          bool,
+          decltype(detail::swap_adl_tests::can_swap<T, U>(0))::value &&
+              (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value ||
+               (std::is_move_assignable<T>::value &&
+                std::is_move_constructible<T>::value))> {};
+
+template <class T, std::size_t N>
+struct is_swappable<T[N], T[N]>
+    : std::integral_constant<
+          bool,
+          decltype(detail::swap_adl_tests::can_swap<T[N], T[N]>(0))::value &&
+              (!decltype(detail::swap_adl_tests::uses_std<T[N], T[N]>(
+                   0))::value ||
+               is_swappable<T, T>::value)> {};
+
+template <class T, class U = T>
+struct is_nothrow_swappable
+    : std::integral_constant<
+          bool,
+          is_swappable<T, U>::value &&
+              ((decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
+                detail::swap_adl_tests::is_std_swap_noexcept<T>::value) ||
+               (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&
+                detail::swap_adl_tests::is_adl_swap_noexcept<T, U>::value))> {};
+#endif
+#endif
+
+// Trait for checking if a type is a tl::expected
+template <class T>
+struct is_expected_impl : std::false_type {};
+template <class T, class E>
+struct is_expected_impl<expected<T, E>> : std::true_type {};
+template <class T>
+using is_expected = is_expected_impl<decay_t<T>>;
+
+template <class T, class E, class U>
+using expected_enable_forward_value = detail::enable_if_t<
+    std::is_constructible<T, U &&>::value &&
+    !std::is_same<detail::decay_t<U>, in_place_t>::value &&
+    !std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
+    !std::is_same<unexpected<E>, detail::decay_t<U>>::value>;
+
+template <class T, class E, class U, class G, class UR, class GR>
+using expected_enable_from_other = detail::enable_if_t<
+    std::is_constructible<T, UR>::value &&
+    std::is_constructible<E, GR>::value &&
+    !std::is_constructible<T, expected<U, G> &>::value &&
+    !std::is_constructible<T, expected<U, G> &&>::value &&
+    !std::is_constructible<T, const expected<U, G> &>::value &&
+    !std::is_constructible<T, const expected<U, G> &&>::value &&
+    !std::is_convertible<expected<U, G> &, T>::value &&
+    !std::is_convertible<expected<U, G> &&, T>::value &&
+    !std::is_convertible<const expected<U, G> &, T>::value &&
+    !std::is_convertible<const expected<U, G> &&, T>::value>;
+
+template <class T, class U>
+using is_void_or = conditional_t<std::is_void<T>::value, std::true_type, U>;
+
+template <class T>
+using is_copy_constructible_or_void =
+    is_void_or<T, std::is_copy_constructible<T>>;
+
+template <class T>
+using is_move_constructible_or_void =
+    is_void_or<T, std::is_move_constructible<T>>;
+
+template <class T>
+using is_copy_assignable_or_void = is_void_or<T, std::is_copy_assignable<T>>;
+
+template <class T>
+using is_move_assignable_or_void = is_void_or<T, std::is_move_assignable<T>>;
+
+}  // namespace detail
+
+namespace detail {
+struct no_init_t {};
+static constexpr no_init_t no_init{};
+
+// Implements the storage of the values, and ensures that the destructor is
+// trivial if it can be.
+//
+// This specialization is for where neither `T` or `E` is trivially
+// destructible, so the destructors must be called on destruction of the
+// `expected`
+template <class T, class E, bool = std::is_trivially_destructible<T>::value,
+          bool = std::is_trivially_destructible<E>::value>
+struct expected_storage_base {
+  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
+  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =
+                nullptr>
+  constexpr expected_storage_base(in_place_t, Args &&...args)
+      : m_val(std::forward<Args>(args)...), m_has_val(true) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
+                                  Args &&...args)
+      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() {
+    if (m_has_val) {
+      m_val.~T();
+    } else {
+      m_unexpect.~unexpected<E>();
+    }
+  }
+  union {
+    T m_val;
+    unexpected<E> m_unexpect;
+    char m_no_init;
+  };
+  bool m_has_val;
+};
+
+// This specialization is for when both `T` and `E` are trivially-destructible,
+// so the destructor of the `expected` can be trivial.
+template <class T, class E>
+struct expected_storage_base<T, E, true, true> {
+  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
+  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =
+                nullptr>
+  constexpr expected_storage_base(in_place_t, Args &&...args)
+      : m_val(std::forward<Args>(args)...), m_has_val(true) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
+                                  Args &&...args)
+      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() = default;
+  union {
+    T m_val;
+    unexpected<E> m_unexpect;
+    char m_no_init;
+  };
+  bool m_has_val;
+};
+
+// T is trivial, E is not.
+template <class T, class E>
+struct expected_storage_base<T, E, true, false> {
+  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
+  TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t)
+      : m_no_init(), m_has_val(false) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =
+                nullptr>
+  constexpr expected_storage_base(in_place_t, Args &&...args)
+      : m_val(std::forward<Args>(args)...), m_has_val(true) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
+                                  Args &&...args)
+      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() {
+    if (!m_has_val) {
+      m_unexpect.~unexpected<E>();
+    }
+  }
+
+  union {
+    T m_val;
+    unexpected<E> m_unexpect;
+    char m_no_init;
+  };
+  bool m_has_val;
+};
+
+// E is trivial, T is not.
+template <class T, class E>
+struct expected_storage_base<T, E, false, true> {
+  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}
+  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =
+                nullptr>
+  constexpr expected_storage_base(in_place_t, Args &&...args)
+      : m_val(std::forward<Args>(args)...), m_has_val(true) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,
+                                  Args &&...args)
+      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() {
+    if (m_has_val) {
+      m_val.~T();
+    }
+  }
+  union {
+    T m_val;
+    unexpected<E> m_unexpect;
+    char m_no_init;
+  };
+  bool m_has_val;
+};
+
+// `T` is `void`, `E` is trivially-destructible
+template <class E>
+struct expected_storage_base<void, E, false, true> {
+#if __GNUC__ <= 5
+// no constexpr for GCC 4/5 bug
+#else
+  TL_EXPECTED_MSVC2015_CONSTEXPR
+#endif
+  expected_storage_base() : m_has_val(true) {}
+
+  constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {}
+
+  constexpr expected_storage_base(in_place_t) : m_has_val(true) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() = default;
+  struct dummy {};
+  union {
+    unexpected<E> m_unexpect;
+    dummy m_val;
+  };
+  bool m_has_val;
+};
+
+// `T` is `void`, `E` is not trivially-destructible
+template <class E>
+struct expected_storage_base<void, E, false, false> {
+  constexpr expected_storage_base() : m_dummy(), m_has_val(true) {}
+  constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {}
+
+  constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)
+      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected_storage_base(unexpect_t,
+                                           std::initializer_list<U> il,
+                                           Args &&...args)
+      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}
+
+  ~expected_storage_base() {
+    if (!m_has_val) {
+      m_unexpect.~unexpected<E>();
+    }
+  }
+
+  union {
+    unexpected<E> m_unexpect;
+    char m_dummy;
+  };
+  bool m_has_val;
+};
+
+// This base class provides some handy member functions which can be used in
+// further derived classes
+template <class T, class E>
+struct expected_operations_base : expected_storage_base<T, E> {
+  using expected_storage_base<T, E>::expected_storage_base;
+
+  template <class... Args>
+  void construct(Args &&...args) noexcept {
+    new (std::addressof(this->m_val)) T(std::forward<Args>(args)...);
+    this->m_has_val = true;
+  }
+
+  template <class Rhs>
+  void construct_with(Rhs &&rhs) noexcept {
+    new (std::addressof(this->m_val)) T(std::forward<Rhs>(rhs).get());
+    this->m_has_val = true;
+  }
+
+  template <class... Args>
+  void construct_error(Args &&...args) noexcept {
+    new (std::addressof(this->m_unexpect))
+        unexpected<E>(std::forward<Args>(args)...);
+    this->m_has_val = false;
+  }
+
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+
+  // These assign overloads ensure that the most efficient assignment
+  // implementation is used while maintaining the strong exception guarantee.
+  // The problematic case is where rhs has a value, but *this does not.
+  //
+  // This overload handles the case where we can just copy-construct `T`
+  // directly into place without throwing.
+  template <class U = T,
+            detail::enable_if_t<std::is_nothrow_copy_constructible<U>::value>
+                * = nullptr>
+  void assign(const expected_operations_base &rhs) noexcept {
+    if (!this->m_has_val && rhs.m_has_val) {
+      geterr().~unexpected<E>();
+      construct(rhs.get());
+    } else {
+      assign_common(rhs);
+    }
+  }
+
+  // This overload handles the case where we can attempt to create a copy of
+  // `T`, then no-throw move it into place if the copy was successful.
+  template <class U = T,
+            detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&
+                                std::is_nothrow_move_constructible<U>::value>
+                * = nullptr>
+  void assign(const expected_operations_base &rhs) noexcept {
+    if (!this->m_has_val && rhs.m_has_val) {
+      T tmp = rhs.get();
+      geterr().~unexpected<E>();
+      construct(std::move(tmp));
+    } else {
+      assign_common(rhs);
+    }
+  }
+
+  // This overload is the worst-case, where we have to move-construct the
+  // unexpected value into temporary storage, then try to copy the T into place.
+  // If the construction succeeds, then everything is fine, but if it throws,
+  // then we move the old unexpected value back into place before rethrowing the
+  // exception.
+  template <class U = T,
+            detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&
+                                !std::is_nothrow_move_constructible<U>::value>
+                * = nullptr>
+  void assign(const expected_operations_base &rhs) {
+    if (!this->m_has_val && rhs.m_has_val) {
+      auto tmp = std::move(geterr());
+      geterr().~unexpected<E>();
+
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+      try {
+        construct(rhs.get());
+      } catch (...) {
+        geterr() = std::move(tmp);
+        throw;
+      }
+#else
+      construct(rhs.get());
+#endif
+    } else {
+      assign_common(rhs);
+    }
+  }
+
+  // These overloads do the same as above, but for rvalues
+  template <class U = T,
+            detail::enable_if_t<std::is_nothrow_move_constructible<U>::value>
+                * = nullptr>
+  void assign(expected_operations_base &&rhs) noexcept {
+    if (!this->m_has_val && rhs.m_has_val) {
+      geterr().~unexpected<E>();
+      construct(std::move(rhs).get());
+    } else {
+      assign_common(std::move(rhs));
+    }
+  }
+
+  template <class U = T,
+            detail::enable_if_t<!std::is_nothrow_move_constructible<U>::value>
+                * = nullptr>
+  void assign(expected_operations_base &&rhs) {
+    if (!this->m_has_val && rhs.m_has_val) {
+      auto tmp = std::move(geterr());
+      geterr().~unexpected<E>();
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+      try {
+        construct(std::move(rhs).get());
+      } catch (...) {
+        geterr() = std::move(tmp);
+        throw;
+      }
+#else
+      construct(std::move(rhs).get());
+#endif
+    } else {
+      assign_common(std::move(rhs));
+    }
+  }
+
+#else
+
+  // If exceptions are disabled then we can just copy-construct
+  void assign(const expected_operations_base &rhs) noexcept {
+    if (!this->m_has_val && rhs.m_has_val) {
+      geterr().~unexpected<E>();
+      construct(rhs.get());
+    } else {
+      assign_common(rhs);
+    }
+  }
+
+  void assign(expected_operations_base &&rhs) noexcept {
+    if (!this->m_has_val && rhs.m_has_val) {
+      geterr().~unexpected<E>();
+      construct(std::move(rhs).get());
+    } else {
+      assign_common(std::move(rhs));
+    }
+  }
+
+#endif
+
+  // The common part of move/copy assigning
+  template <class Rhs>
+  void assign_common(Rhs &&rhs) {
+    if (this->m_has_val) {
+      if (rhs.m_has_val) {
+        get() = std::forward<Rhs>(rhs).get();
+      } else {
+        destroy_val();
+        construct_error(std::forward<Rhs>(rhs).geterr());
+      }
+    } else {
+      if (!rhs.m_has_val) {
+        geterr() = std::forward<Rhs>(rhs).geterr();
+      }
+    }
+  }
+
+  bool has_value() const { return this->m_has_val; }
+
+  TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; }
+  constexpr const T &get() const & { return this->m_val; }
+  TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); }
+#ifndef TL_EXPECTED_NO_CONSTRR
+  constexpr const T &&get() const && { return std::move(this->m_val); }
+#endif
+
+  TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & {
+    return this->m_unexpect;
+  }
+  constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; }
+  TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && {
+    return std::move(this->m_unexpect);
+  }
+#ifndef TL_EXPECTED_NO_CONSTRR
+  constexpr const unexpected<E> &&geterr() const && {
+    return std::move(this->m_unexpect);
+  }
+#endif
+
+  TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); }
+};
+
+// This base class provides some handy member functions which can be used in
+// further derived classes
+template <class E>
+struct expected_operations_base<void, E> : expected_storage_base<void, E> {
+  using expected_storage_base<void, E>::expected_storage_base;
+
+  template <class... Args>
+  void construct() noexcept {
+    this->m_has_val = true;
+  }
+
+  // This function doesn't use its argument, but needs it so that code in
+  // levels above this can work independently of whether T is void
+  template <class Rhs>
+  void construct_with(Rhs &&) noexcept {
+    this->m_has_val = true;
+  }
+
+  template <class... Args>
+  void construct_error(Args &&...args) noexcept {
+    new (std::addressof(this->m_unexpect))
+        unexpected<E>(std::forward<Args>(args)...);
+    this->m_has_val = false;
+  }
+
+  template <class Rhs>
+  void assign(Rhs &&rhs) noexcept {
+    if (!this->m_has_val) {
+      if (rhs.m_has_val) {
+        geterr().~unexpected<E>();
+        construct();
+      } else {
+        geterr() = std::forward<Rhs>(rhs).geterr();
+      }
+    } else {
+      if (!rhs.m_has_val) {
+        construct_error(std::forward<Rhs>(rhs).geterr());
+      }
+    }
+  }
+
+  bool has_value() const { return this->m_has_val; }
+
+  TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & {
+    return this->m_unexpect;
+  }
+  constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; }
+  TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && {
+    return std::move(this->m_unexpect);
+  }
+#ifndef TL_EXPECTED_NO_CONSTRR
+  constexpr const unexpected<E> &&geterr() const && {
+    return std::move(this->m_unexpect);
+  }
+#endif
+
+  TL_EXPECTED_11_CONSTEXPR void destroy_val() {
+    // no-op
+  }
+};
+
+// This class manages conditionally having a trivial copy constructor
+// This specialization is for when T and E are trivially copy constructible
+template <class T, class E,
+          bool = is_void_or<T, TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>::
+              value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value>
+struct expected_copy_base : expected_operations_base<T, E> {
+  using expected_operations_base<T, E>::expected_operations_base;
+};
+
+// This specialization is for when T or E are not trivially copy constructible
+template <class T, class E>
+struct expected_copy_base<T, E, false> : expected_operations_base<T, E> {
+  using expected_operations_base<T, E>::expected_operations_base;
+
+  expected_copy_base() = default;
+  expected_copy_base(const expected_copy_base &rhs)
+      : expected_operations_base<T, E>(no_init) {
+    if (rhs.has_value()) {
+      this->construct_with(rhs);
+    } else {
+      this->construct_error(rhs.geterr());
+    }
+  }
+
+  expected_copy_base(expected_copy_base &&rhs) = default;
+  expected_copy_base &operator=(const expected_copy_base &rhs) = default;
+  expected_copy_base &operator=(expected_copy_base &&rhs) = default;
+};
+
+// This class manages conditionally having a trivial move constructor
+// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it
+// doesn't implement an analogue to std::is_trivially_move_constructible. We
+// have to make do with a non-trivial move constructor even if T is trivially
+// move constructible
+#ifndef TL_EXPECTED_GCC49
+template <class T, class E,
+          bool = is_void_or<T, std::is_trivially_move_constructible<T>>::value
+              &&std::is_trivially_move_constructible<E>::value>
+struct expected_move_base : expected_copy_base<T, E> {
+  using expected_copy_base<T, E>::expected_copy_base;
+};
+#else
+template <class T, class E, bool = false>
+struct expected_move_base;
+#endif
+template <class T, class E>
+struct expected_move_base<T, E, false> : expected_copy_base<T, E> {
+  using expected_copy_base<T, E>::expected_copy_base;
+
+  expected_move_base() = default;
+  expected_move_base(const expected_move_base &rhs) = default;
+
+  expected_move_base(expected_move_base &&rhs) noexcept(
+      std::is_nothrow_move_constructible<T>::value)
+      : expected_copy_base<T, E>(no_init) {
+    if (rhs.has_value()) {
+      this->construct_with(std::move(rhs));
+    } else {
+      this->construct_error(std::move(rhs.geterr()));
+    }
+  }
+  expected_move_base &operator=(const expected_move_base &rhs) = default;
+  expected_move_base &operator=(expected_move_base &&rhs) = default;
+};
+
+// This class manages conditionally having a trivial copy assignment operator
+template <class T, class E,
+          bool = is_void_or<
+              T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T),
+                             TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T),
+                             TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value
+              &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value
+                  &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value
+                      &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value>
+struct expected_copy_assign_base : expected_move_base<T, E> {
+  using expected_move_base<T, E>::expected_move_base;
+};
+
+template <class T, class E>
+struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {
+  using expected_move_base<T, E>::expected_move_base;
+
+  expected_copy_assign_base() = default;
+  expected_copy_assign_base(const expected_copy_assign_base &rhs) = default;
+
+  expected_copy_assign_base(expected_copy_assign_base &&rhs) = default;
+  expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) {
+    this->assign(rhs);
+    return *this;
+  }
+  expected_copy_assign_base &operator=(expected_copy_assign_base &&rhs) =
+      default;
+};
+
+// This class manages conditionally having a trivial move assignment operator
+// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it
+// doesn't implement an analogue to std::is_trivially_move_assignable. We have
+// to make do with a non-trivial move assignment operator even if T is trivially
+// move assignable
+#ifndef TL_EXPECTED_GCC49
+template <class T, class E,
+          bool =
+              is_void_or<T, conjunction<std::is_trivially_destructible<T>,
+                                        std::is_trivially_move_constructible<T>,
+                                        std::is_trivially_move_assignable<T>>>::
+                  value &&std::is_trivially_destructible<E>::value
+                      &&std::is_trivially_move_constructible<E>::value
+                          &&std::is_trivially_move_assignable<E>::value>
+struct expected_move_assign_base : expected_copy_assign_base<T, E> {
+  using expected_copy_assign_base<T, E>::expected_copy_assign_base;
+};
+#else
+template <class T, class E, bool = false>
+struct expected_move_assign_base;
+#endif
+
+template <class T, class E>
+struct expected_move_assign_base<T, E, false>
+    : expected_copy_assign_base<T, E> {
+  using expected_copy_assign_base<T, E>::expected_copy_assign_base;
+
+  expected_move_assign_base() = default;
+  expected_move_assign_base(const expected_move_assign_base &rhs) = default;
+
+  expected_move_assign_base(expected_move_assign_base &&rhs) = default;
+
+  expected_move_assign_base &operator=(const expected_move_assign_base &rhs) =
+      default;
+
+  expected_move_assign_base &
+  operator=(expected_move_assign_base &&rhs) noexcept(
+      std::is_nothrow_move_constructible<T>::value
+          &&std::is_nothrow_move_assignable<T>::value) {
+    this->assign(std::move(rhs));
+    return *this;
+  }
+};
+
+// expected_delete_ctor_base will conditionally delete copy and move
+// constructors depending on whether T is copy/move constructible
+template <class T, class E,
+          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&
+                             std::is_copy_constructible<E>::value),
+          bool EnableMove = (is_move_constructible_or_void<T>::value &&
+                             std::is_move_constructible<E>::value)>
+struct expected_delete_ctor_base {
+  expected_delete_ctor_base() = default;
+  expected_delete_ctor_base(const expected_delete_ctor_base &) = default;
+  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default;
+  expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) =
+      default;
+  expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept =
+      default;
+};
+
+template <class T, class E>
+struct expected_delete_ctor_base<T, E, true, false> {
+  expected_delete_ctor_base() = default;
+  expected_delete_ctor_base(const expected_delete_ctor_base &) = default;
+  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete;
+  expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) =
+      default;
+  expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept =
+      default;
+};
+
+template <class T, class E>
+struct expected_delete_ctor_base<T, E, false, true> {
+  expected_delete_ctor_base() = default;
+  expected_delete_ctor_base(const expected_delete_ctor_base &) = delete;
+  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default;
+  expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) =
+      default;
+  expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept =
+      default;
+};
+
+template <class T, class E>
+struct expected_delete_ctor_base<T, E, false, false> {
+  expected_delete_ctor_base() = default;
+  expected_delete_ctor_base(const expected_delete_ctor_base &) = delete;
+  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete;
+  expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) =
+      default;
+  expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept =
+      default;
+};
+
+// expected_delete_assign_base will conditionally delete copy and move
+// constructors depending on whether T and E are copy/move constructible +
+// assignable
+template <class T, class E,
+          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&
+                             std::is_copy_constructible<E>::value &&
+                             is_copy_assignable_or_void<T>::value &&
+                             std::is_copy_assignable<E>::value),
+          bool EnableMove = (is_move_constructible_or_void<T>::value &&
+                             std::is_move_constructible<E>::value &&
+                             is_move_assignable_or_void<T>::value &&
+                             std::is_move_assignable<E>::value)>
+struct expected_delete_assign_base {
+  expected_delete_assign_base() = default;
+  expected_delete_assign_base(const expected_delete_assign_base &) = default;
+  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =
+      default;
+  expected_delete_assign_base &operator=(const expected_delete_assign_base &) =
+      default;
+  expected_delete_assign_base &operator=(
+      expected_delete_assign_base &&) noexcept = default;
+};
+
+template <class T, class E>
+struct expected_delete_assign_base<T, E, true, false> {
+  expected_delete_assign_base() = default;
+  expected_delete_assign_base(const expected_delete_assign_base &) = default;
+  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =
+      default;
+  expected_delete_assign_base &operator=(const expected_delete_assign_base &) =
+      default;
+  expected_delete_assign_base &operator=(
+      expected_delete_assign_base &&) noexcept = delete;
+};
+
+template <class T, class E>
+struct expected_delete_assign_base<T, E, false, true> {
+  expected_delete_assign_base() = default;
+  expected_delete_assign_base(const expected_delete_assign_base &) = default;
+  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =
+      default;
+  expected_delete_assign_base &operator=(const expected_delete_assign_base &) =
+      delete;
+  expected_delete_assign_base &operator=(
+      expected_delete_assign_base &&) noexcept = default;
+};
+
+template <class T, class E>
+struct expected_delete_assign_base<T, E, false, false> {
+  expected_delete_assign_base() = default;
+  expected_delete_assign_base(const expected_delete_assign_base &) = default;
+  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =
+      default;
+  expected_delete_assign_base &operator=(const expected_delete_assign_base &) =
+      delete;
+  expected_delete_assign_base &operator=(
+      expected_delete_assign_base &&) noexcept = delete;
+};
+
+// This is needed to be able to construct the expected_default_ctor_base which
+// follows, while still conditionally deleting the default constructor.
+struct default_constructor_tag {
+  explicit constexpr default_constructor_tag() = default;
+};
+
+// expected_default_ctor_base will ensure that expected has a deleted default
+// consturctor if T is not default constructible.
+// This specialization is for when T is default constructible
+template <class T, class E,
+          bool Enable =
+              std::is_default_constructible<T>::value || std::is_void<T>::value>
+struct expected_default_ctor_base {
+  constexpr expected_default_ctor_base() noexcept = default;
+  constexpr expected_default_ctor_base(
+      expected_default_ctor_base const &) noexcept = default;
+  constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept =
+      default;
+  expected_default_ctor_base &operator=(
+      expected_default_ctor_base const &) noexcept = default;
+  expected_default_ctor_base &operator=(
+      expected_default_ctor_base &&) noexcept = default;
+
+  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
+};
+
+// This specialization is for when T is not default constructible
+template <class T, class E>
+struct expected_default_ctor_base<T, E, false> {
+  constexpr expected_default_ctor_base() noexcept = delete;
+  constexpr expected_default_ctor_base(
+      expected_default_ctor_base const &) noexcept = default;
+  constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept =
+      default;
+  expected_default_ctor_base &operator=(
+      expected_default_ctor_base const &) noexcept = default;
+  expected_default_ctor_base &operator=(
+      expected_default_ctor_base &&) noexcept = default;
+
+  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}
+};
+}  // namespace detail
+
+template <class E>
+class bad_expected_access : public std::exception {
+ public:
+  explicit bad_expected_access(E e) : m_val(std::move(e)) {}
+
+  virtual const char *what() const noexcept override {
+    return "Bad expected access";
+  }
+
+  const E &error() const & { return m_val; }
+  E &error() & { return m_val; }
+  const E &&error() const && { return std::move(m_val); }
+  E &&error() && { return std::move(m_val); }
+
+ private:
+  E m_val;
+};
+
+/// An `expected<T, E>` object is an object that contains the storage for
+/// another object and manages the lifetime of this contained object `T`.
+/// Alternatively it could contain the storage for another unexpected object
+/// `E`. The contained object may not be initialized after the expected object
+/// has been initialized, and may not be destroyed before the expected object
+/// has been destroyed. The initialization state of the contained object is
+/// tracked by the expected object.
+template <class T, class E>
+class expected : private detail::expected_move_assign_base<T, E>,
+                 private detail::expected_delete_ctor_base<T, E>,
+                 private detail::expected_delete_assign_base<T, E>,
+                 private detail::expected_default_ctor_base<T, E> {
+  static_assert(!std::is_reference<T>::value, "T must not be a reference");
+  static_assert(!std::is_same<T, std::remove_cv<in_place_t>::type>::value,
+                "T must not be in_place_t");
+  static_assert(!std::is_same<T, std::remove_cv<unexpect_t>::type>::value,
+                "T must not be unexpect_t");
+  static_assert(
+      !std::is_same<T, typename std::remove_cv<unexpected<E>>::type>::value,
+      "T must not be unexpected<E>");
+  static_assert(!std::is_reference<E>::value, "E must not be a reference");
+
+  T *valptr() { return std::addressof(this->m_val); }
+  const T *valptr() const { return std::addressof(this->m_val); }
+  unexpected<E> *errptr() { return std::addressof(this->m_unexpect); }
+  const unexpected<E> *errptr() const {
+    return std::addressof(this->m_unexpect);
+  }
+
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR U &val() {
+    return this->m_val;
+  }
+  TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; }
+
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  constexpr const U &val() const {
+    return this->m_val;
+  }
+  constexpr const unexpected<E> &err() const { return this->m_unexpect; }
+
+  using impl_base = detail::expected_move_assign_base<T, E>;
+  using ctor_base = detail::expected_default_ctor_base<T, E>;
+
+ public:
+  typedef T value_type;
+  typedef E error_type;
+  typedef unexpected<E> unexpected_type;
+
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & {
+    return and_then_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && {
+    return and_then_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto and_then(F &&f) const & {
+    return and_then_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr auto and_then(F &&f) const && {
+    return and_then_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+
+#else
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & -> decltype(and_then_impl(
+      std::declval<expected &>(), std::forward<F>(f))) {
+    return and_then_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype(and_then_impl(
+      std::declval<expected &&>(), std::forward<F>(f))) {
+    return and_then_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto and_then(F &&f) const & -> decltype(and_then_impl(
+      std::declval<expected const &>(), std::forward<F>(f))) {
+    return and_then_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr auto and_then(F &&f) const && -> decltype(and_then_impl(
+      std::declval<expected const &&>(), std::forward<F>(f))) {
+    return and_then_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+#endif
+
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto map(F &&f) const & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto map(F &&f) const && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+#else
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(
+      std::declval<expected &>(), std::declval<F &&>()))
+  map(F &&f) & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),
+                                                      std::declval<F &&>()))
+  map(F &&f) && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr decltype(expected_map_impl(std::declval<const expected &>(),
+                                       std::declval<F &&>()))
+  map(F &&f) const & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr decltype(expected_map_impl(std::declval<const expected &&>(),
+                                       std::declval<F &&>()))
+  map(F &&f) const && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+#endif
+
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto transform(F &&f) const & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto transform(F &&f) const && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+#else
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(
+      std::declval<expected &>(), std::declval<F &&>()))
+  transform(F &&f) & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),
+                                                      std::declval<F &&>()))
+  transform(F &&f) && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr decltype(expected_map_impl(std::declval<const expected &>(),
+                                       std::declval<F &&>()))
+  transform(F &&f) const & {
+    return expected_map_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr decltype(expected_map_impl(std::declval<const expected &&>(),
+                                       std::declval<F &&>()))
+  transform(F &&f) const && {
+    return expected_map_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+#endif
+
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto map_error(F &&f) const & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto map_error(F &&f) const && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+#else
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(),
+                                                   std::declval<F &&>()))
+  map_error(F &&f) & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(),
+                                                   std::declval<F &&>()))
+  map_error(F &&f) && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr decltype(map_error_impl(std::declval<const expected &>(),
+                                    std::declval<F &&>()))
+  map_error(F &&f) const & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr decltype(map_error_impl(std::declval<const expected &&>(),
+                                    std::declval<F &&>()))
+  map_error(F &&f) const && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+#endif
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto transform_error(F &&f) const & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  constexpr auto transform_error(F &&f) const && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+#else
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(),
+                                                   std::declval<F &&>()))
+  transform_error(F &&f) & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+  template <class F>
+  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(),
+                                                   std::declval<F &&>()))
+  transform_error(F &&f) && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+  template <class F>
+  constexpr decltype(map_error_impl(std::declval<const expected &>(),
+                                    std::declval<F &&>()))
+  transform_error(F &&f) const & {
+    return map_error_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  constexpr decltype(map_error_impl(std::declval<const expected &&>(),
+                                    std::declval<F &&>()))
+  transform_error(F &&f) const && {
+    return map_error_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+#endif
+  template <class F>
+  expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & {
+    return or_else_impl(*this, std::forward<F>(f));
+  }
+
+  template <class F>
+  expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && {
+    return or_else_impl(std::move(*this), std::forward<F>(f));
+  }
+
+  template <class F>
+  expected constexpr or_else(F &&f) const & {
+    return or_else_impl(*this, std::forward<F>(f));
+  }
+
+#ifndef TL_EXPECTED_NO_CONSTRR
+  template <class F>
+  expected constexpr or_else(F &&f) const && {
+    return or_else_impl(std::move(*this), std::forward<F>(f));
+  }
+#endif
+  constexpr expected() = default;
+  constexpr expected(const expected &rhs) = default;
+  constexpr expected(expected &&rhs) = default;
+  expected &operator=(const expected &rhs) = default;
+  expected &operator=(expected &&rhs) = default;
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =
+                nullptr>
+  constexpr expected(in_place_t, Args &&...args)
+      : impl_base(in_place, std::forward<Args>(args)...),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr expected(in_place_t, std::initializer_list<U> il, Args &&...args)
+      : impl_base(in_place, il, std::forward<Args>(args)...),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <class G = E,
+            detail::enable_if_t<std::is_constructible<E, const G &>::value> * =
+                nullptr,
+            detail::enable_if_t<!std::is_convertible<const G &, E>::value> * =
+                nullptr>
+  explicit constexpr expected(const unexpected<G> &e)
+      : impl_base(unexpect, e.value()),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <
+      class G = E,
+      detail::enable_if_t<std::is_constructible<E, const G &>::value> * =
+          nullptr,
+      detail::enable_if_t<std::is_convertible<const G &, E>::value> * = nullptr>
+  constexpr expected(unexpected<G> const &e)
+      : impl_base(unexpect, e.value()),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <
+      class G = E,
+      detail::enable_if_t<std::is_constructible<E, G &&>::value> * = nullptr,
+      detail::enable_if_t<!std::is_convertible<G &&, E>::value> * = nullptr>
+  explicit constexpr expected(unexpected<G> &&e) noexcept(
+      std::is_nothrow_constructible<E, G &&>::value)
+      : impl_base(unexpect, std::move(e.value())),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <
+      class G = E,
+      detail::enable_if_t<std::is_constructible<E, G &&>::value> * = nullptr,
+      detail::enable_if_t<std::is_convertible<G &&, E>::value> * = nullptr>
+  constexpr expected(unexpected<G> &&e) noexcept(
+      std::is_nothrow_constructible<E, G &&>::value)
+      : impl_base(unexpect, std::move(e.value())),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <class... Args,
+            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =
+                nullptr>
+  constexpr explicit expected(unexpect_t, Args &&...args)
+      : impl_base(unexpect, std::forward<Args>(args)...),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_constructible<
+                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  constexpr explicit expected(unexpect_t, std::initializer_list<U> il,
+                              Args &&...args)
+      : impl_base(unexpect, il, std::forward<Args>(args)...),
+        ctor_base(detail::default_constructor_tag{}) {}
+
+  template <class U, class G,
+            detail::enable_if_t<!(std::is_convertible<U const &, T>::value &&
+                                  std::is_convertible<G const &, E>::value)> * =
+                nullptr,
+            detail::expected_enable_from_other<T, E, U, G, const U &, const G &>
+                * = nullptr>
+  explicit TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs)
+      : ctor_base(detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(*rhs);
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  template <class U, class G,
+            detail::enable_if_t<(std::is_convertible<U const &, T>::value &&
+                                 std::is_convertible<G const &, E>::value)> * =
+                nullptr,
+            detail::expected_enable_from_other<T, E, U, G, const U &, const G &>
+                * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs)
+      : ctor_base(detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(*rhs);
+    } else {
+      this->construct_error(rhs.error());
+    }
+  }
+
+  template <
+      class U, class G,
+      detail::enable_if_t<!(std::is_convertible<U &&, T>::value &&
+                            std::is_convertible<G &&, E>::value)> * = nullptr,
+      detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr>
+  explicit TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs)
+      : ctor_base(detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(std::move(*rhs));
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  template <
+      class U, class G,
+      detail::enable_if_t<(std::is_convertible<U &&, T>::value &&
+                           std::is_convertible<G &&, E>::value)> * = nullptr,
+      detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs)
+      : ctor_base(detail::default_constructor_tag{}) {
+    if (rhs.has_value()) {
+      this->construct(std::move(*rhs));
+    } else {
+      this->construct_error(std::move(rhs.error()));
+    }
+  }
+
+  template <
+      class U = T,
+      detail::enable_if_t<!std::is_convertible<U &&, T>::value> * = nullptr,
+      detail::expected_enable_forward_value<T, E, U> * = nullptr>
+  explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v)
+      : expected(in_place, std::forward<U>(v)) {}
+
+  template <
+      class U = T,
+      detail::enable_if_t<std::is_convertible<U &&, T>::value> * = nullptr,
+      detail::expected_enable_forward_value<T, E, U> * = nullptr>
+  TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v)
+      : expected(in_place, std::forward<U>(v)) {}
+
+  template <
+      class U = T, class G = T,
+      detail::enable_if_t<std::is_nothrow_constructible<T, U &&>::value> * =
+          nullptr,
+      detail::enable_if_t<!std::is_void<G>::value> * = nullptr,
+      detail::enable_if_t<
+          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
+           !detail::conjunction<std::is_scalar<T>,
+                                std::is_same<T, detail::decay_t<U>>>::value &&
+           std::is_constructible<T, U>::value &&
+           std::is_assignable<G &, U>::value &&
+           std::is_nothrow_move_constructible<E>::value)> * = nullptr>
+  expected &operator=(U &&v) {
+    if (has_value()) {
+      val() = std::forward<U>(v);
+    } else {
+      err().~unexpected<E>();
+      ::new (valptr()) T(std::forward<U>(v));
+      this->m_has_val = true;
+    }
+
+    return *this;
+  }
+
+  template <
+      class U = T, class G = T,
+      detail::enable_if_t<!std::is_nothrow_constructible<T, U &&>::value> * =
+          nullptr,
+      detail::enable_if_t<!std::is_void<U>::value> * = nullptr,
+      detail::enable_if_t<
+          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&
+           !detail::conjunction<std::is_scalar<T>,
+                                std::is_same<T, detail::decay_t<U>>>::value &&
+           std::is_constructible<T, U>::value &&
+           std::is_assignable<G &, U>::value &&
+           std::is_nothrow_move_constructible<E>::value)> * = nullptr>
+  expected &operator=(U &&v) {
+    if (has_value()) {
+      val() = std::forward<U>(v);
+    } else {
+      auto tmp = std::move(err());
+      err().~unexpected<E>();
+
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+      try {
+        ::new (valptr()) T(std::forward<U>(v));
+        this->m_has_val = true;
+      } catch (...) {
+        err() = std::move(tmp);
+        throw;
+      }
+#else
+      ::new (valptr()) T(std::forward<U>(v));
+      this->m_has_val = true;
+#endif
+    }
+
+    return *this;
+  }
+
+  template <class G = E,
+            detail::enable_if_t<std::is_nothrow_copy_constructible<G>::value &&
+                                std::is_assignable<G &, G>::value> * = nullptr>
+  expected &operator=(const unexpected<G> &rhs) {
+    if (!has_value()) {
+      err() = rhs;
+    } else {
+      this->destroy_val();
+      ::new (errptr()) unexpected<E>(rhs);
+      this->m_has_val = false;
+    }
+
+    return *this;
+  }
+
+  template <class G = E,
+            detail::enable_if_t<std::is_nothrow_move_constructible<G>::value &&
+                                std::is_move_assignable<G>::value> * = nullptr>
+  expected &operator=(unexpected<G> &&rhs) noexcept {
+    if (!has_value()) {
+      err() = std::move(rhs);
+    } else {
+      this->destroy_val();
+      ::new (errptr()) unexpected<E>(std::move(rhs));
+      this->m_has_val = false;
+    }
+
+    return *this;
+  }
+
+  template <class... Args, detail::enable_if_t<std::is_nothrow_constructible<
+                               T, Args &&...>::value> * = nullptr>
+  void emplace(Args &&...args) {
+    if (has_value()) {
+      val().~T();
+    } else {
+      err().~unexpected<E>();
+      this->m_has_val = true;
+    }
+    ::new (valptr()) T(std::forward<Args>(args)...);
+  }
+
+  template <class... Args, detail::enable_if_t<!std::is_nothrow_constructible<
+                               T, Args &&...>::value> * = nullptr>
+  void emplace(Args &&...args) {
+    if (has_value()) {
+      val().~T();
+      ::new (valptr()) T(std::forward<Args>(args)...);
+    } else {
+      auto tmp = std::move(err());
+      err().~unexpected<E>();
+
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+      try {
+        ::new (valptr()) T(std::forward<Args>(args)...);
+        this->m_has_val = true;
+      } catch (...) {
+        err() = std::move(tmp);
+        throw;
+      }
+#else
+      ::new (valptr()) T(std::forward<Args>(args)...);
+      this->m_has_val = true;
+#endif
+    }
+  }
+
+  template <class U, class... Args,
+            detail::enable_if_t<std::is_nothrow_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  void emplace(std::initializer_list<U> il, Args &&...args) {
+    if (has_value()) {
+      T t(il, std::forward<Args>(args)...);
+      val() = std::move(t);
+    } else {
+      err().~unexpected<E>();
+      ::new (valptr()) T(il, std::forward<Args>(args)...);
+      this->m_has_val = true;
+    }
+  }
+
+  template <class U, class... Args,
+            detail::enable_if_t<!std::is_nothrow_constructible<
+                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>
+  void emplace(std::initializer_list<U> il, Args &&...args) {
+    if (has_value()) {
+      T t(il, std::forward<Args>(args)...);
+      val() = std::move(t);
+    } else {
+      auto tmp = std::move(err());
+      err().~unexpected<E>();
+
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+      try {
+        ::new (valptr()) T(il, std::forward<Args>(args)...);
+        this->m_has_val = true;
+      } catch (...) {
+        err() = std::move(tmp);
+        throw;
+      }
+#else
+      ::new (valptr()) T(il, std::forward<Args>(args)...);
+      this->m_has_val = true;
+#endif
+    }
+  }
+
+ private:
+  using t_is_void = std::true_type;
+  using t_is_not_void = std::false_type;
+  using t_is_nothrow_move_constructible = std::true_type;
+  using move_constructing_t_can_throw = std::false_type;
+  using e_is_nothrow_move_constructible = std::true_type;
+  using move_constructing_e_can_throw = std::false_type;
+
+  void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept {
+    // swapping void is a no-op
+  }
+
+  void swap_where_both_have_value(expected &rhs, t_is_not_void) {
+    using std::swap;
+    swap(val(), rhs.val());
+  }
+
+  void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept(
+      std::is_nothrow_move_constructible<E>::value) {
+    ::new (errptr()) unexpected_type(std::move(rhs.err()));
+    rhs.err().~unexpected_type();
+    std::swap(this->m_has_val, rhs.m_has_val);
+  }
+
+  void swap_where_only_one_has_value(expected &rhs, t_is_not_void) {
+    swap_where_only_one_has_value_and_t_is_not_void(
+        rhs, typename std::is_nothrow_move_constructible<T>::type{},
+        typename std::is_nothrow_move_constructible<E>::type{});
+  }
+
+  void swap_where_only_one_has_value_and_t_is_not_void(
+      expected &rhs, t_is_nothrow_move_constructible,
+      e_is_nothrow_move_constructible) noexcept {
+    auto temp = std::move(val());
+    val().~T();
+    ::new (errptr()) unexpected_type(std::move(rhs.err()));
+    rhs.err().~unexpected_type();
+    ::new (rhs.valptr()) T(std::move(temp));
+    std::swap(this->m_has_val, rhs.m_has_val);
+  }
+
+  void swap_where_only_one_has_value_and_t_is_not_void(
+      expected &rhs, t_is_nothrow_move_constructible,
+      move_constructing_e_can_throw) {
+    auto temp = std::move(val());
+    val().~T();
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+    try {
+      ::new (errptr()) unexpected_type(std::move(rhs.err()));
+      rhs.err().~unexpected_type();
+      ::new (rhs.valptr()) T(std::move(temp));
+      std::swap(this->m_has_val, rhs.m_has_val);
+    } catch (...) {
+      val() = std::move(temp);
+      throw;
+    }
+#else
+    ::new (errptr()) unexpected_type(std::move(rhs.err()));
+    rhs.err().~unexpected_type();
+    ::new (rhs.valptr()) T(std::move(temp));
+    std::swap(this->m_has_val, rhs.m_has_val);
+#endif
+  }
+
+  void swap_where_only_one_has_value_and_t_is_not_void(
+      expected &rhs, move_constructing_t_can_throw,
+      e_is_nothrow_move_constructible) {
+    auto temp = std::move(rhs.err());
+    rhs.err().~unexpected_type();
+#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED
+    try {
+      ::new (rhs.valptr()) T(std::move(val()));
+      val().~T();
+      ::new (errptr()) unexpected_type(std::move(temp));
+      std::swap(this->m_has_val, rhs.m_has_val);
+    } catch (...) {
+      rhs.err() = std::move(temp);
+      throw;
+    }
+#else
+    ::new (rhs.valptr()) T(std::move(val()));
+    val().~T();
+    ::new (errptr()) unexpected_type(std::move(temp));
+    std::swap(this->m_has_val, rhs.m_has_val);
+#endif
+  }
+
+ public:
+  template <class OT = T, class OE = E>
+  detail::enable_if_t<detail::is_swappable<OT>::value &&
+                      detail::is_swappable<OE>::value &&
+                      (std::is_nothrow_move_constructible<OT>::value ||
+                       std::is_nothrow_move_constructible<OE>::value)>
+  swap(expected &rhs) noexcept(
+      std::is_nothrow_move_constructible<T>::value
+          &&detail::is_nothrow_swappable<T>::value
+              &&std::is_nothrow_move_constructible<E>::value
+                  &&detail::is_nothrow_swappable<E>::value) {
+    if (has_value() && rhs.has_value()) {
+      swap_where_both_have_value(rhs, typename std::is_void<T>::type{});
+    } else if (!has_value() && rhs.has_value()) {
+      rhs.swap(*this);
+    } else if (has_value()) {
+      swap_where_only_one_has_value(rhs, typename std::is_void<T>::type{});
+    } else {
+      using std::swap;
+      swap(err(), rhs.err());
+    }
+  }
+
+  constexpr const T *operator->() const {
+    TL_ASSERT(has_value());
+    return valptr();
+  }
+  TL_EXPECTED_11_CONSTEXPR T *operator->() {
+    TL_ASSERT(has_value());
+    return valptr();
+  }
+
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  constexpr const U &operator*() const & {
+    TL_ASSERT(has_value());
+    return val();
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR U &operator*() & {
+    TL_ASSERT(has_value());
+    return val();
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  constexpr const U &&operator*() const && {
+    TL_ASSERT(has_value());
+    return std::move(val());
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR U &&operator*() && {
+    TL_ASSERT(has_value());
+    return std::move(val());
+  }
+
+  constexpr bool has_value() const noexcept { return this->m_has_val; }
+  constexpr explicit operator bool() const noexcept { return this->m_has_val; }
+
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR const U &value() const & {
+    if (!has_value())
+      detail::throw_exception(bad_expected_access<E>(err().value()));
+    return val();
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR U &value() & {
+    if (!has_value())
+      detail::throw_exception(bad_expected_access<E>(err().value()));
+    return val();
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR const U &&value() const && {
+    if (!has_value())
+      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));
+    return std::move(val());
+  }
+  template <class U = T,
+            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>
+  TL_EXPECTED_11_CONSTEXPR U &&value() && {
+    if (!has_value())
+      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));
+    return std::move(val());
+  }
+
+  constexpr const E &error() const & {
+    TL_ASSERT(!has_value());
+    return err().value();
+  }
+  TL_EXPECTED_11_CONSTEXPR E &error() & {
+    TL_ASSERT(!has_value());
+    return err().value();
+  }
+  constexpr const E &&error() const && {
+    TL_ASSERT(!has_value());
+    return std::move(err().value());
+  }
+  TL_EXPECTED_11_CONSTEXPR E &&error() && {
+    TL_ASSERT(!has_value());
+    return std::move(err().value());
+  }
+
+  template <class U>
+  constexpr T value_or(U &&v) const & {
+    static_assert(std::is_copy_constructible<T>::value &&
+                      std::is_convertible<U &&, T>::value,
+                  "T must be copy-constructible and convertible to from U&&");
+    return bool(*this) ? **this : static_cast<T>(std::forward<U>(v));
+  }
+  template <class U>
+  TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && {
+    static_assert(std::is_move_constructible<T>::value &&
+                      std::is_convertible<U &&, T>::value,
+                  "T must be move-constructible and convertible to from U&&");
+    return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v));
+  }
+};
+
+namespace detail {
+template <class Exp>
+using exp_t = typename detail::decay_t<Exp>::value_type;
+template <class Exp>
+using err_t = typename detail::decay_t<Exp>::error_type;
+template <class Exp, class Ret>
+using ret_t = expected<Ret, err_t<Exp>>;
+
+#ifdef TL_EXPECTED_CXX14
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>()))>
+constexpr auto and_then_impl(Exp &&exp, F &&f) {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+
+  return exp.has_value()
+             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
+             : Ret(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>()))>
+constexpr auto and_then_impl(Exp &&exp, F &&f) {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+
+  return exp.has_value() ? detail::invoke(std::forward<F>(f))
+                         : Ret(unexpect, std::forward<Exp>(exp).error());
+}
+#else
+template <class>
+struct TC;
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>())),
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr>
+auto and_then_impl(Exp &&exp, F &&f) -> Ret {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+
+  return exp.has_value()
+             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
+             : Ret(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>())),
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr>
+constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+
+  return exp.has_value() ? detail::invoke(std::forward<F>(f))
+                         : Ret(unexpect, std::forward<Exp>(exp).error());
+}
+#endif
+
+#ifdef TL_EXPECTED_CXX14
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto expected_map_impl(Exp &&exp, F &&f) {
+  using result = ret_t<Exp, detail::decay_t<Ret>>;
+  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
+                                                 *std::forward<Exp>(exp)))
+                         : result(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto expected_map_impl(Exp &&exp, F &&f) {
+  using result = expected<void, err_t<Exp>>;
+  if (exp.has_value()) {
+    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
+    return result();
+  }
+
+  return result(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto expected_map_impl(Exp &&exp, F &&f) {
+  using result = ret_t<Exp, detail::decay_t<Ret>>;
+  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))
+                         : result(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto expected_map_impl(Exp &&exp, F &&f) {
+  using result = expected<void, err_t<Exp>>;
+  if (exp.has_value()) {
+    detail::invoke(std::forward<F>(f));
+    return result();
+  }
+
+  return result(unexpect, std::forward<Exp>(exp).error());
+}
+#else
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+
+constexpr auto expected_map_impl(Exp &&exp, F &&f)
+    -> ret_t<Exp, detail::decay_t<Ret>> {
+  using result = ret_t<Exp, detail::decay_t<Ret>>;
+
+  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
+                                                 *std::forward<Exp>(exp)))
+                         : result(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              *std::declval<Exp>())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+
+auto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> {
+  if (exp.has_value()) {
+    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
+    return {};
+  }
+
+  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+
+constexpr auto expected_map_impl(Exp &&exp, F &&f)
+    -> ret_t<Exp, detail::decay_t<Ret>> {
+  using result = ret_t<Exp, detail::decay_t<Ret>>;
+
+  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))
+                         : result(unexpect, std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+
+auto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> {
+  if (exp.has_value()) {
+    detail::invoke(std::forward<F>(f));
+    return {};
+  }
+
+  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());
+}
+#endif
+
+#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \
+    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto map_error_impl(Exp &&exp, F &&f) {
+  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
+  return exp.has_value()
+             ? result(*std::forward<Exp>(exp))
+             : result(unexpect, detail::invoke(std::forward<F>(f),
+                                               std::forward<Exp>(exp).error()));
+}
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto map_error_impl(Exp &&exp, F &&f) {
+  using result = expected<exp_t<Exp>, monostate>;
+  if (exp.has_value()) {
+    return result(*std::forward<Exp>(exp));
+  }
+
+  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
+  return result(unexpect, monostate{});
+}
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto map_error_impl(Exp &&exp, F &&f) {
+  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
+  return exp.has_value()
+             ? result()
+             : result(unexpect, detail::invoke(std::forward<F>(f),
+                                               std::forward<Exp>(exp).error()));
+}
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto map_error_impl(Exp &&exp, F &&f) {
+  using result = expected<exp_t<Exp>, monostate>;
+  if (exp.has_value()) {
+    return result();
+  }
+
+  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
+  return result(unexpect, monostate{});
+}
+#else
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto map_error_impl(Exp &&exp, F &&f)
+    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {
+  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
+
+  return exp.has_value()
+             ? result(*std::forward<Exp>(exp))
+             : result(unexpect, detail::invoke(std::forward<F>(f),
+                                               std::forward<Exp>(exp).error()));
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> {
+  using result = expected<exp_t<Exp>, monostate>;
+  if (exp.has_value()) {
+    return result(*std::forward<Exp>(exp));
+  }
+
+  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
+  return result(unexpect, monostate{});
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto map_error_impl(Exp &&exp, F &&f)
+    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {
+  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
+
+  return exp.has_value()
+             ? result()
+             : result(unexpect, detail::invoke(std::forward<F>(f),
+                                               std::forward<Exp>(exp).error()));
+}
+
+template <class Exp, class F,
+          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> {
+  using result = expected<exp_t<Exp>, monostate>;
+  if (exp.has_value()) {
+    return result();
+  }
+
+  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());
+  return result(unexpect, monostate{});
+}
+#endif
+
+#ifdef TL_EXPECTED_CXX14
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+constexpr auto or_else_impl(Exp &&exp, F &&f) {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+  return exp.has_value() ? std::forward<Exp>(exp)
+                         : detail::invoke(std::forward<F>(f),
+                                          std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+detail::decay_t<Exp> or_else_impl(Exp &&exp, F &&f) {
+  return exp.has_value() ? std::forward<Exp>(exp)
+                         : (detail::invoke(std::forward<F>(f),
+                                           std::forward<Exp>(exp).error()),
+                            std::forward<Exp>(exp));
+}
+#else
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
+auto or_else_impl(Exp &&exp, F &&f) -> Ret {
+  static_assert(detail::is_expected<Ret>::value, "F must return an expected");
+  return exp.has_value() ? std::forward<Exp>(exp)
+                         : detail::invoke(std::forward<F>(f),
+                                          std::forward<Exp>(exp).error());
+}
+
+template <class Exp, class F,
+          class Ret = decltype(detail::invoke(std::declval<F>(),
+                                              std::declval<Exp>().error())),
+          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
+detail::decay_t<Exp> or_else_impl(Exp &&exp, F &&f) {
+  return exp.has_value() ? std::forward<Exp>(exp)
+                         : (detail::invoke(std::forward<F>(f),
+                                           std::forward<Exp>(exp).error()),
+                            std::forward<Exp>(exp));
+}
+#endif
+}  // namespace detail
+
+template <class T, class E, class U, class F>
+constexpr bool operator==(const expected<T, E> &lhs,
+                          const expected<U, F> &rhs) {
+  return (lhs.has_value() != rhs.has_value())
+             ? false
+             : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs);
+}
+template <class T, class E, class U, class F>
+constexpr bool operator!=(const expected<T, E> &lhs,
+                          const expected<U, F> &rhs) {
+  return (lhs.has_value() != rhs.has_value())
+             ? true
+             : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs);
+}
+template <class E, class F>
+constexpr bool operator==(const expected<void, E> &lhs,
+                          const expected<void, F> &rhs) {
+  return (lhs.has_value() != rhs.has_value())
+             ? false
+             : (!lhs.has_value() ? lhs.error() == rhs.error() : true);
+}
+template <class E, class F>
+constexpr bool operator!=(const expected<void, E> &lhs,
+                          const expected<void, F> &rhs) {
+  return (lhs.has_value() != rhs.has_value())
+             ? true
+             : (!lhs.has_value() ? lhs.error() == rhs.error() : false);
+}
+
+template <class T, class E, class U>
+constexpr bool operator==(const expected<T, E> &x, const U &v) {
+  return x.has_value() ? *x == v : false;
+}
+template <class T, class E, class U>
+constexpr bool operator==(const U &v, const expected<T, E> &x) {
+  return x.has_value() ? *x == v : false;
+}
+template <class T, class E, class U>
+constexpr bool operator!=(const expected<T, E> &x, const U &v) {
+  return x.has_value() ? *x != v : true;
+}
+template <class T, class E, class U>
+constexpr bool operator!=(const U &v, const expected<T, E> &x) {
+  return x.has_value() ? *x != v : true;
+}
+
+template <class T, class E>
+constexpr bool operator==(const expected<T, E> &x, const unexpected<E> &e) {
+  return x.has_value() ? false : x.error() == e.value();
+}
+template <class T, class E>
+constexpr bool operator==(const unexpected<E> &e, const expected<T, E> &x) {
+  return x.has_value() ? false : x.error() == e.value();
+}
+template <class T, class E>
+constexpr bool operator!=(const expected<T, E> &x, const unexpected<E> &e) {
+  return x.has_value() ? true : x.error() != e.value();
+}
+template <class T, class E>
+constexpr bool operator!=(const unexpected<E> &e, const expected<T, E> &x) {
+  return x.has_value() ? true : x.error() != e.value();
+}
+
+template <class T, class E,
+          detail::enable_if_t<(std::is_void<T>::value ||
+                               std::is_move_constructible<T>::value) &&
+                              detail::is_swappable<T>::value &&
+                              std::is_move_constructible<E>::value &&
+                              detail::is_swappable<E>::value> * = nullptr>
+void swap(expected<T, E> &lhs,
+          expected<T, E> &rhs) noexcept(noexcept(lhs.swap(rhs))) {
+  lhs.swap(rhs);
+}
+}  // namespace tl
+
+#endif
diff --git a/include/ada/helpers.h b/include/ada/helpers.h
new file mode 100644 (file)
index 0000000..c500966
--- /dev/null
@@ -0,0 +1,232 @@
+/**
+ * @file helpers.h
+ * @brief Definitions for helper functions used within Ada.
+ */
+#ifndef ADA_HELPERS_H
+#define ADA_HELPERS_H
+
+#include "ada/common_defs.h"
+#include "ada/state.h"
+#include "ada/url_base.h"
+
+#include <string_view>
+#include <optional>
+
+/**
+ * These functions are not part of our public API and may
+ * change at any time.
+ *
+ * @private
+ * @namespace ada::helpers
+ * @brief Includes the definitions for helper functions
+ */
+namespace ada::helpers {
+
+/**
+ * @private
+ */
+template <typename out_iter>
+void encode_json(std::string_view view, out_iter out);
+
+/**
+ * @private
+ * This function is used to prune a fragment from a url, and returning the
+ * removed string if input has fragment.
+ *
+ * @details prune_hash seeks the first '#' and returns everything after it
+ * as a string_view, and modifies (in place) the input so that it points at
+ * everything before the '#'. If no '#' is found, the input is left unchanged
+ * and std::nullopt is returned.
+ *
+ * @attention The function is non-allocating and it does not throw.
+ * @returns Note that the returned string_view might be empty!
+ */
+ada_really_inline std::optional<std::string_view> prune_hash(
+    std::string_view& input) noexcept;
+
+/**
+ * @private
+ * Defined by the URL specification, shorten a URLs paths.
+ * @see https://url.spec.whatwg.org/#shorten-a-urls-path
+ * @returns Returns true if path is shortened.
+ */
+ada_really_inline bool shorten_path(std::string& path,
+                                    ada::scheme::type type) noexcept;
+
+/**
+ * @private
+ * Defined by the URL specification, shorten a URLs paths.
+ * @see https://url.spec.whatwg.org/#shorten-a-urls-path
+ * @returns Returns true if path is shortened.
+ */
+ada_really_inline bool shorten_path(std::string_view& path,
+                                    ada::scheme::type type) noexcept;
+
+/**
+ * @private
+ *
+ * Parse the path from the provided input and append to the existing
+ * (possibly empty) path. The input cannot contain tabs and spaces: it
+ * is the user's responsibility to check.
+ *
+ * The input is expected to be UTF-8.
+ *
+ * @see https://url.spec.whatwg.org/
+ */
+ada_really_inline void parse_prepared_path(std::string_view input,
+                                           ada::scheme::type type,
+                                           std::string& path);
+
+/**
+ * @private
+ * Remove and mutate all ASCII tab or newline characters from an input.
+ */
+ada_really_inline void remove_ascii_tab_or_newline(std::string& input) noexcept;
+
+/**
+ * @private
+ * Return the substring from input going from index pos to the end.
+ * This function cannot throw.
+ */
+ada_really_inline std::string_view substring(std::string_view input,
+                                             size_t pos) noexcept;
+
+/**
+ * @private
+ * Returns true if the string_view points within the string.
+ */
+bool overlaps(std::string_view input1, const std::string& input2) noexcept;
+
+/**
+ * @private
+ * Return the substring from input going from index pos1 to the pos2 (non
+ * included). The length of the substring is pos2 - pos1.
+ */
+ada_really_inline std::string_view substring(const std::string& input,
+                                             size_t pos1,
+                                             size_t pos2) noexcept {
+#if ADA_DEVELOPMENT_CHECKS
+  if (pos2 < pos1) {
+    std::cerr << "Negative-length substring: [" << pos1 << " to " << pos2 << ")"
+              << std::endl;
+    abort();
+  }
+#endif
+  return std::string_view(input.data() + pos1, pos2 - pos1);
+}
+
+/**
+ * @private
+ * Modify the string_view so that it has the new size pos, assuming that pos <=
+ * input.size(). This function cannot throw.
+ */
+ada_really_inline void resize(std::string_view& input, size_t pos) noexcept;
+
+/**
+ * @private
+ * Returns a host's delimiter location depending on the state of the instance,
+ * and whether a colon was found outside brackets. Used by the host parser.
+ */
+ada_really_inline std::pair<size_t, bool> get_host_delimiter_location(
+    const bool is_special, std::string_view& view) noexcept;
+
+/**
+ * @private
+ * Removes leading and trailing C0 control and whitespace characters from
+ * string.
+ */
+ada_really_inline void trim_c0_whitespace(std::string_view& input) noexcept;
+
+/**
+ * @private
+ * @see
+ * https://url.spec.whatwg.org/#potentially-strip-trailing-spaces-from-an-opaque-path
+ */
+template <class url_type>
+ada_really_inline void strip_trailing_spaces_from_opaque_path(
+    url_type& url) noexcept;
+
+/**
+ * @private
+ * Finds the delimiter of a view in authority state.
+ */
+ada_really_inline size_t
+find_authority_delimiter_special(std::string_view view) noexcept;
+
+/**
+ * @private
+ * Finds the delimiter of a view in authority state.
+ */
+ada_really_inline size_t
+find_authority_delimiter(std::string_view view) noexcept;
+
+/**
+ * @private
+ */
+template <typename T, typename... Args>
+inline void inner_concat(std::string& buffer, T t) {
+  buffer.append(t);
+}
+
+/**
+ * @private
+ */
+template <typename T, typename... Args>
+inline void inner_concat(std::string& buffer, T t, Args... args) {
+  buffer.append(t);
+  return inner_concat(buffer, args...);
+}
+
+/**
+ * @private
+ * Concatenate the arguments and return a string.
+ * @returns a string
+ */
+template <typename... Args>
+std::string concat(Args... args) {
+  std::string answer;
+  inner_concat(answer, args...);
+  return answer;
+}
+
+/**
+ * @private
+ * @return Number of leading zeroes.
+ */
+inline int leading_zeroes(uint32_t input_num) noexcept {
+#if ADA_REGULAR_VISUAL_STUDIO
+  unsigned long leading_zero(0);
+  unsigned long in(input_num);
+  return _BitScanReverse(&leading_zero, in) ? int(31 - leading_zero) : 32;
+#else
+  return __builtin_clz(input_num);
+#endif  // ADA_REGULAR_VISUAL_STUDIO
+}
+
+/**
+ * @private
+ * Counts the number of decimal digits necessary to represent x.
+ * faster than std::to_string(x).size().
+ * @return digit count
+ */
+inline int fast_digit_count(uint32_t x) noexcept {
+  auto int_log2 = [](uint32_t z) -> int {
+    return 31 - ada::helpers::leading_zeroes(z | 1);
+  };
+  // Compiles to very few instructions. Note that the
+  // table is static and thus effectively a constant.
+  // We leave it inside the function because it is meaningless
+  // outside of it (this comes at no performance cost).
+  const static uint64_t table[] = {
+      4294967296,  8589934582,  8589934582,  8589934582,  12884901788,
+      12884901788, 12884901788, 17179868184, 17179868184, 17179868184,
+      21474826480, 21474826480, 21474826480, 21474826480, 25769703776,
+      25769703776, 25769703776, 30063771072, 30063771072, 30063771072,
+      34349738368, 34349738368, 34349738368, 34349738368, 38554705664,
+      38554705664, 38554705664, 41949672960, 41949672960, 41949672960,
+      42949672960, 42949672960};
+  return int((x + table[int_log2(x)]) >> 32);
+}
+}  // namespace ada::helpers
+
+#endif  // ADA_HELPERS_H
diff --git a/include/ada/implementation.h b/include/ada/implementation.h
new file mode 100644 (file)
index 0000000..295ea03
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * @file implementation.h
+ * @brief Definitions for user facing functions for parsing URL and it's
+ * components.
+ */
+#ifndef ADA_IMPLEMENTATION_H
+#define ADA_IMPLEMENTATION_H
+
+#include <string>
+#include <optional>
+
+#include "ada/parser.h"
+#include "ada/common_defs.h"
+#include "ada/encoding_type.h"
+#include "ada/url.h"
+#include "ada/state.h"
+#include "ada/url_aggregator.h"
+
+namespace ada {
+enum class errors { generic_error };
+
+template <class result_type = ada::url_aggregator>
+using result = tl::expected<result_type, ada::errors>;
+
+/**
+ * The URL parser takes a scalar value string input, with an optional null or
+ * base URL base (default null). The parser assumes the input is a valid ASCII
+ * or UTF-8 string.
+ *
+ * @param input the string input to analyze (must be valid ASCII or UTF-8)
+ * @param base_url the optional URL input to use as a base url.
+ * @return a parsed URL.
+ */
+template <class result_type = ada::url_aggregator>
+ada_warn_unused ada::result<result_type> parse(
+    std::string_view input, const result_type* base_url = nullptr);
+
+extern template ada::result<url> parse<url>(std::string_view input,
+                                            const url* base_url);
+extern template ada::result<url_aggregator> parse<url_aggregator>(
+    std::string_view input, const url_aggregator* base_url);
+
+/**
+ * Verifies whether the URL strings can be parsed. The function assumes
+ * that the inputs are valid ASCII or UTF-8 strings.
+ * @see https://url.spec.whatwg.org/#dom-url-canparse
+ * @return If URL can be parsed or not.
+ */
+bool can_parse(std::string_view input,
+               const std::string_view* base_input = nullptr);
+
+/**
+ * Computes a href string from a file path. The function assumes
+ * that the input is a valid ASCII or UTF-8 string.
+ * @return a href string (starts with file:://)
+ */
+std::string href_from_file(std::string_view path);
+}  // namespace ada
+
+#endif  // ADA_IMPLEMENTATION_H
diff --git a/include/ada/log.h b/include/ada/log.h
new file mode 100644 (file)
index 0000000..5b00d36
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ * @file log.h
+ * @brief Includes the definitions for logging.
+ * @private Excluded from docs through the doxygen file.
+ */
+#ifndef ADA_LOG_H
+#define ADA_LOG_H
+#include "ada/common_defs.h"
+
+#include <iostream>
+// To enable logging, set ADA_LOGGING to 1:
+#ifndef ADA_LOGGING
+#define ADA_LOGGING 0
+#endif
+
+namespace ada {
+
+/**
+ * Private function used for logging messages.
+ * @private
+ */
+template <typename T>
+ada_really_inline void inner_log([[maybe_unused]] T t) {
+#if ADA_LOGGING
+  std::cout << t << std::endl;
+#endif
+}
+
+/**
+ * Private function used for logging messages.
+ * @private
+ */
+template <typename T, typename... Args>
+ada_really_inline void inner_log([[maybe_unused]] T t,
+                                 [[maybe_unused]] Args... args) {
+#if ADA_LOGGING
+  std::cout << t;
+  inner_log(args...);
+#endif
+}
+
+/**
+ * Log a message.
+ * @private
+ */
+template <typename T, typename... Args>
+ada_really_inline void log([[maybe_unused]] T t,
+                           [[maybe_unused]] Args... args) {
+#if ADA_LOGGING
+  std::cout << "ADA_LOG: " << t;
+  inner_log(args...);
+#endif
+}
+
+/**
+ * Log a message.
+ * @private
+ */
+template <typename T>
+ada_really_inline void log([[maybe_unused]] T t) {
+#if ADA_LOGGING
+  std::cout << "ADA_LOG: " << t << std::endl;
+#endif
+}
+}  // namespace ada
+
+#if ADA_LOGGING
+
+#ifndef ada_log
+#define ada_log(...)       \
+  do {                     \
+    ada::log(__VA_ARGS__); \
+  } while (0)
+#endif  // ada_log
+#else
+#define ada_log(...)
+#endif  // ADA_LOGGING
+
+#endif  // ADA_LOG_H
diff --git a/include/ada/parser.h b/include/ada/parser.h
new file mode 100644 (file)
index 0000000..4f81562
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * @file parser.h
+ * @brief Definitions for the parser.
+ */
+#ifndef ADA_PARSER_H
+#define ADA_PARSER_H
+
+#include "ada/state.h"
+#include "ada/encoding_type.h"
+#include "ada/expected.h"
+
+#include <optional>
+#include <string_view>
+
+/**
+ * @private
+ */
+namespace ada {
+struct url_aggregator;
+struct url;
+}  // namespace ada
+
+/**
+ * @namespace ada::parser
+ * @brief Includes the definitions for supported parsers
+ */
+namespace ada::parser {
+
+/**
+ * Parses a url.
+ */
+template <typename result_type = ada::url_aggregator>
+result_type parse_url(std::string_view user_input,
+                      const result_type* base_url = nullptr);
+
+extern template url_aggregator parse_url<url_aggregator>(
+    std::string_view user_input, const url_aggregator* base_url);
+extern template url parse_url<url>(std::string_view user_input,
+                                   const url* base_url);
+
+}  // namespace ada::parser
+
+#endif  // ADA_PARSER_H
diff --git a/include/ada/scheme-inl.h b/include/ada/scheme-inl.h
new file mode 100644 (file)
index 0000000..a327dd0
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * @file scheme-inl.h
+ * @brief Definitions for the URL scheme.
+ */
+#ifndef ADA_SCHEME_INL_H
+#define ADA_SCHEME_INL_H
+
+#include "ada/scheme.h"
+
+namespace ada::scheme {
+
+/**
+ * @namespace ada::scheme::details
+ * @brief Includes the definitions for scheme specific entities
+ */
+namespace details {
+// for use with is_special and get_special_port
+// Spaces, if present, are removed from URL.
+constexpr std::string_view is_special_list[] = {"http", " ",   "https", "ws",
+                                                "ftp",  "wss", "file",  " "};
+// for use with get_special_port
+constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0};
+}  // namespace details
+
+/****
+ * @private
+ * In is_special, get_scheme_type, and get_special_port, we
+ * use a standard hashing technique to find the index of the scheme in
+ * the is_special_list. The hashing technique is based on the size of
+ * the scheme and the first character of the scheme. It ensures that we
+ * do at most one string comparison per call. If the protocol is
+ * predictible (e.g., it is always "http"), we can get a better average
+ * performance by using a simpler approach where we loop and compare
+ * scheme with all possible protocols starting with the most likely
+ * protocol. Doing multiple comparisons may have a poor worst case
+ * performance, however. In this instance, we choose a potentially
+ * slightly lower best-case performance for a better worst-case
+ * performance. We can revisit this choice at any time.
+ *
+ * Reference:
+ * Schmidt, Douglas C. "Gperf: A perfect hash function generator."
+ * More C++ gems 17 (2000).
+ *
+ * Reference: https://en.wikipedia.org/wiki/Perfect_hash_function
+ *
+ * Reference: https://github.com/ada-url/ada/issues/617
+ ****/
+
+ada_really_inline constexpr bool is_special(std::string_view scheme) {
+  if (scheme.empty()) {
+    return false;
+  }
+  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
+  const std::string_view target = details::is_special_list[hash_value];
+  return (target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1));
+}
+constexpr uint16_t get_special_port(std::string_view scheme) noexcept {
+  if (scheme.empty()) {
+    return 0;
+  }
+  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
+  const std::string_view target = details::is_special_list[hash_value];
+  if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) {
+    return details::special_ports[hash_value];
+  } else {
+    return 0;
+  }
+}
+constexpr uint16_t get_special_port(ada::scheme::type type) noexcept {
+  return details::special_ports[int(type)];
+}
+constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept {
+  if (scheme.empty()) {
+    return ada::scheme::NOT_SPECIAL;
+  }
+  int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7;
+  const std::string_view target = details::is_special_list[hash_value];
+  if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) {
+    return ada::scheme::type(hash_value);
+  } else {
+    return ada::scheme::NOT_SPECIAL;
+  }
+}
+
+}  // namespace ada::scheme
+
+#endif  // ADA_SCHEME_INL_H
diff --git a/include/ada/scheme.h b/include/ada/scheme.h
new file mode 100644 (file)
index 0000000..f14a85e
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * @file scheme.h
+ * @brief Declarations for the URL scheme.
+ */
+#ifndef ADA_SCHEME_H
+#define ADA_SCHEME_H
+
+#include "ada/common_defs.h"
+
+#include <array>
+#include <optional>
+#include <string>
+
+/**
+ * @namespace ada::scheme
+ * @brief Includes the scheme declarations
+ */
+namespace ada::scheme {
+
+/**
+ * Type of the scheme as an enum.
+ * Using strings to represent a scheme type is not ideal because
+ * checking for types involves string comparisons. It is faster to use
+ * a simple integer.
+ * In C++11, we are allowed to specify the underlying type of the enum.
+ * We pick an 8-bit integer (which allows up to 256 types). Specifying the
+ * type of the enum may help integration with other systems if the type
+ * variable is exposed (since its value will not depend on the compiler).
+ */
+enum type : uint8_t {
+  HTTP = 0,
+  NOT_SPECIAL = 1,
+  HTTPS = 2,
+  WS = 3,
+  FTP = 4,
+  WSS = 5,
+  FILE = 6
+};
+
+/**
+ * A special scheme is an ASCII string that is listed in the first column of the
+ * following table. The default port for a special scheme is listed in the
+ * second column on the same row. The default port for any other ASCII string is
+ * null.
+ *
+ * @see https://url.spec.whatwg.org/#url-miscellaneous
+ * @param scheme
+ * @return If scheme is a special scheme
+ */
+ada_really_inline constexpr bool is_special(std::string_view scheme);
+
+/**
+ * A special scheme is an ASCII string that is listed in the first column of the
+ * following table. The default port for a special scheme is listed in the
+ * second column on the same row. The default port for any other ASCII string is
+ * null.
+ *
+ * @see https://url.spec.whatwg.org/#url-miscellaneous
+ * @param scheme
+ * @return The special port
+ */
+constexpr uint16_t get_special_port(std::string_view scheme) noexcept;
+
+/**
+ * Returns the port number of a special scheme.
+ * @see https://url.spec.whatwg.org/#special-scheme
+ */
+constexpr uint16_t get_special_port(ada::scheme::type type) noexcept;
+/**
+ * Returns the scheme of an input, or NOT_SPECIAL if it's not a special scheme
+ * defined by the spec.
+ */
+constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept;
+
+}  // namespace ada::scheme
+
+#endif  // ADA_SCHEME_H
diff --git a/include/ada/serializers.h b/include/ada/serializers.h
new file mode 100644 (file)
index 0000000..d8e0d3d
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * @file serializers.h
+ * @brief Definitions for the URL serializers.
+ */
+#ifndef ADA_SERIALIZERS_H
+#define ADA_SERIALIZERS_H
+
+#include "ada/common_defs.h"
+
+#include <array>
+#include <optional>
+#include <string>
+
+/**
+ * @namespace ada::serializers
+ * @brief Includes the definitions for URL serializers
+ */
+namespace ada::serializers {
+
+/**
+ * Finds and returns the longest sequence of 0 values in a ipv6 input.
+ */
+void find_longest_sequence_of_ipv6_pieces(
+    const std::array<uint16_t, 8>& address, size_t& compress,
+    size_t& compress_length) noexcept;
+
+/**
+ * Serializes an ipv6 address.
+ * @details An IPv6 address is a 128-bit unsigned integer that identifies a
+ * network address.
+ * @see https://url.spec.whatwg.org/#concept-ipv6-serializer
+ */
+std::string ipv6(const std::array<uint16_t, 8>& address) noexcept;
+
+/**
+ * Serializes an ipv4 address.
+ * @details An IPv4 address is a 32-bit unsigned integer that identifies a
+ * network address.
+ * @see https://url.spec.whatwg.org/#concept-ipv4-serializer
+ */
+std::string ipv4(uint64_t address) noexcept;
+
+}  // namespace ada::serializers
+
+#endif  // ADA_SERIALIZERS_H
diff --git a/include/ada/state.h b/include/ada/state.h
new file mode 100644 (file)
index 0000000..5384a6c
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * @file state.h
+ * @brief Definitions for the states of the URL state machine.
+ */
+#ifndef ADA_STATE_H
+#define ADA_STATE_H
+
+#include "ada/common_defs.h"
+
+#include <string>
+
+namespace ada {
+
+/**
+ * @see https://url.spec.whatwg.org/#url-parsing
+ */
+enum class state {
+  AUTHORITY,
+  SCHEME_START,
+  SCHEME,
+  HOST,
+  NO_SCHEME,
+  FRAGMENT,
+  RELATIVE_SCHEME,
+  RELATIVE_SLASH,
+  FILE,
+  FILE_HOST,
+  FILE_SLASH,
+  PATH_OR_AUTHORITY,
+  SPECIAL_AUTHORITY_IGNORE_SLASHES,
+  SPECIAL_AUTHORITY_SLASHES,
+  SPECIAL_RELATIVE_OR_AUTHORITY,
+  QUERY,
+  PATH,
+  PATH_START,
+  OPAQUE_PATH,
+  PORT,
+};
+
+/**
+ * Stringify a URL state machine state.
+ */
+ada_warn_unused std::string to_string(ada::state s);
+
+}  // namespace ada
+
+#endif  // ADA_STATE_H
diff --git a/include/ada/unicode-inl.h b/include/ada/unicode-inl.h
new file mode 100644 (file)
index 0000000..7bbbd8f
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * @file unicode-inl.h
+ * @brief Definitions for unicode operations.
+ */
+#ifndef ADA_UNICODE_INL_H
+#define ADA_UNICODE_INL_H
+#include <algorithm>
+#include "ada/unicode.h"
+
+/**
+ * Unicode operations. These functions are not part of our public API and may
+ * change at any time.
+ *
+ * private
+ * @namespace ada::unicode
+ * @brief Includes the declarations for unicode operations
+ */
+namespace ada::unicode {
+ada_really_inline size_t percent_encode_index(const std::string_view input,
+                                              const uint8_t character_set[]) {
+  return std::distance(
+      input.begin(),
+      std::find_if(input.begin(), input.end(), [character_set](const char c) {
+        return character_sets::bit_at(character_set, c);
+      }));
+}
+}  // namespace ada::unicode
+
+#endif  // ADA_UNICODE_INL_H
diff --git a/include/ada/unicode.h b/include/ada/unicode.h
new file mode 100644 (file)
index 0000000..278ad26
--- /dev/null
@@ -0,0 +1,232 @@
+/**
+ * @file unicode.h
+ * @brief Definitions for all unicode specific functions.
+ */
+#ifndef ADA_UNICODE_H
+#define ADA_UNICODE_H
+
+#include "ada/common_defs.h"
+#include "ada/ada_idna.h"
+
+#include <string>
+#include <optional>
+
+/**
+ * Unicode operations. These functions are not part of our public API and may
+ * change at any time.
+ *
+ * @private
+ * @namespace ada::unicode
+ * @brief Includes the definitions for unicode operations
+ */
+namespace ada::unicode {
+
+/**
+ * @private
+ * We receive a UTF-8 string representing a domain name.
+ * If the string is percent encoded, we apply percent decoding.
+ *
+ * Given a domain, we need to identify its labels.
+ * They are separated by label-separators:
+ *
+ * U+002E (.) FULL STOP
+ * U+FF0E FULLWIDTH FULL STOP
+ * U+3002 IDEOGRAPHIC FULL STOP
+ * U+FF61 HALFWIDTH IDEOGRAPHIC FULL STOP
+ *
+ * They are all mapped to U+002E.
+ *
+ * We process each label into a string that should not exceed 63 octets.
+ * If the string is already punycode (starts with "xn--"), then we must
+ * scan it to look for unallowed code points.
+ * Otherwise, if the string is not pure ASCII, we need to transcode it
+ * to punycode by following RFC 3454 which requires us to
+ * - Map characters  (see section 3),
+ * - Normalize (see section 4),
+ * - Reject forbidden characters,
+ * - Check for right-to-left characters and if so, check all requirements (see
+ * section 6),
+ * - Optionally reject based on unassigned code points (section 7).
+ *
+ * The Unicode standard provides a table of code points with a mapping, a list
+ * of forbidden code points and so forth. This table is subject to change and
+ * will vary based on the implementation. For Unicode 15, the table is at
+ * https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt
+ * If you use ICU, they parse this table and map it to code using a Python
+ * script.
+ *
+ * The resulting strings should not exceed 255 octets according to RFC 1035
+ * section 2.3.4. ICU checks for label size and domain size, but these errors
+ * are ignored.
+ *
+ * @see https://url.spec.whatwg.org/#concept-domain-to-ascii
+ *
+ */
+bool to_ascii(std::optional<std::string>& out, std::string_view plain,
+              size_t first_percent);
+
+/**
+ * @private
+ * @see https://www.unicode.org/reports/tr46/#ToUnicode
+ */
+std::string to_unicode(std::string_view input);
+
+/**
+ * @private
+ * Checks if the input has tab or newline characters.
+ *
+ * @attention The has_tabs_or_newline function is a bottleneck and it is simple
+ * enough that compilers like GCC can 'autovectorize it'.
+ */
+ada_really_inline bool has_tabs_or_newline(
+    std::string_view user_input) noexcept;
+
+/**
+ * @private
+ * Checks if the input is a forbidden host code point.
+ * @see https://url.spec.whatwg.org/#forbidden-host-code-point
+ */
+ada_really_inline constexpr bool is_forbidden_host_code_point(char c) noexcept;
+
+/**
+ * @private
+ * Checks if the input contains a forbidden domain code point.
+ * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
+ */
+ada_really_inline constexpr bool contains_forbidden_domain_code_point(
+    const char* input, size_t length) noexcept;
+
+/**
+ * @private
+ * Checks if the input contains a forbidden domain code point in which case
+ * the first bit is set to 1. If the input contains an upper case ASCII letter,
+ * then the second bit is set to 1.
+ * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
+ */
+ada_really_inline constexpr uint8_t
+contains_forbidden_domain_code_point_or_upper(const char* input,
+                                              size_t length) noexcept;
+
+/**
+ * @private
+ * Checks if the input is a forbidden domain code point.
+ * @see https://url.spec.whatwg.org/#forbidden-domain-code-point
+ */
+ada_really_inline constexpr bool is_forbidden_domain_code_point(
+    char c) noexcept;
+
+/**
+ * @private
+ * Checks if the input is alphanumeric, '+', '-' or '.'
+ */
+ada_really_inline constexpr bool is_alnum_plus(char c) noexcept;
+
+/**
+ * @private
+ * @details An ASCII hex digit is an ASCII upper hex digit or ASCII lower hex
+ * digit. An ASCII upper hex digit is an ASCII digit or a code point in the
+ * range U+0041 (A) to U+0046 (F), inclusive. An ASCII lower hex digit is an
+ * ASCII digit or a code point in the range U+0061 (a) to U+0066 (f), inclusive.
+ */
+ada_really_inline constexpr bool is_ascii_hex_digit(char c) noexcept;
+
+/**
+ * @private
+ * Checks if the input is a C0 control or space character.
+ *
+ * @details A C0 control or space is a C0 control or U+0020 SPACE.
+ * A C0 control is a code point in the range U+0000 NULL to U+001F INFORMATION
+ * SEPARATOR ONE, inclusive.
+ */
+ada_really_inline constexpr bool is_c0_control_or_space(char c) noexcept;
+
+/**
+ * @private
+ * Checks if the input is a ASCII tab or newline character.
+ *
+ * @details An ASCII tab or newline is U+0009 TAB, U+000A LF, or U+000D CR.
+ */
+ada_really_inline constexpr bool is_ascii_tab_or_newline(char c) noexcept;
+
+/**
+ * @private
+ * @details A double-dot path segment must be ".." or an ASCII case-insensitive
+ * match for ".%2e", "%2e.", or "%2e%2e".
+ */
+ada_really_inline ada_constexpr bool is_double_dot_path_segment(
+    std::string_view input) noexcept;
+
+/**
+ * @private
+ * @details A single-dot path segment must be "." or an ASCII case-insensitive
+ * match for "%2e".
+ */
+ada_really_inline constexpr bool is_single_dot_path_segment(
+    std::string_view input) noexcept;
+
+/**
+ * @private
+ * @details ipv4 character might contain 0-9 or a-f character ranges.
+ */
+ada_really_inline constexpr bool is_lowercase_hex(char c) noexcept;
+
+/**
+ * @private
+ * @details Convert hex to binary. Caller is responsible to ensure that
+ * the parameter is an hexadecimal digit (0-9, A-F, a-f).
+ */
+ada_really_inline unsigned constexpr convert_hex_to_binary(char c) noexcept;
+
+/**
+ * @private
+ * first_percent should be  = input.find('%')
+ *
+ * @todo It would be faster as noexcept maybe, but it could be unsafe since.
+ * @author Node.js
+ * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L245
+ * @see https://encoding.spec.whatwg.org/#utf-8-decode-without-bom
+ */
+std::string percent_decode(std::string_view input, size_t first_percent);
+
+/**
+ * @private
+ * Returns a percent-encoding string whether percent encoding was needed or not.
+ * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
+ */
+std::string percent_encode(std::string_view input,
+                           const uint8_t character_set[]);
+/**
+ * @private
+ * Returns a percent-encoded string version of input, while starting the percent
+ * encoding at the provided index.
+ * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
+ */
+std::string percent_encode(std::string_view input,
+                           const uint8_t character_set[], size_t index);
+/**
+ * @private
+ * Returns true if percent encoding was needed, in which case, we store
+ * the percent-encoded content in 'out'. If the boolean 'append' is set to
+ * true, the content is appended to 'out'.
+ * If percent encoding is not needed, out is left unchanged.
+ * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226
+ */
+template <bool append>
+bool percent_encode(std::string_view input, const uint8_t character_set[],
+                    std::string& out);
+/**
+ * @private
+ * Returns the index at which percent encoding should start, or (equivalently),
+ * the length of the prefix that does not require percent encoding.
+ */
+ada_really_inline size_t percent_encode_index(std::string_view input,
+                                              const uint8_t character_set[]);
+/**
+ * @private
+ * Lowers the string in-place, assuming that the content is ASCII.
+ * Return true if the content was ASCII.
+ */
+constexpr bool to_lower_ascii(char* input, size_t length) noexcept;
+}  // namespace ada::unicode
+
+#endif  // ADA_UNICODE_H
diff --git a/include/ada/url-inl.h b/include/ada/url-inl.h
new file mode 100644 (file)
index 0000000..8b23379
--- /dev/null
@@ -0,0 +1,248 @@
+/**
+ * @file url-inl.h
+ * @brief Definitions for the URL
+ */
+#ifndef ADA_URL_INL_H
+#define ADA_URL_INL_H
+
+#include "ada/checkers.h"
+#include "ada/url.h"
+#include "ada/url_components.h"
+
+#include <optional>
+#include <string>
+#if ADA_REGULAR_VISUAL_STUDIO
+#include <intrin.h>
+#endif  // ADA_REGULAR_VISUAL_STUDIO
+
+namespace ada {
+[[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept {
+  return !username.empty() || !password.empty();
+}
+[[nodiscard]] ada_really_inline bool url::has_port() const noexcept {
+  return port.has_value();
+}
+[[nodiscard]] inline bool url::cannot_have_credentials_or_port() const {
+  return !host.has_value() || host.value().empty() ||
+         type == ada::scheme::type::FILE;
+}
+[[nodiscard]] inline bool url::has_empty_hostname() const noexcept {
+  if (!host.has_value()) {
+    return false;
+  }
+  return host.value().empty();
+}
+[[nodiscard]] inline bool url::has_hostname() const noexcept {
+  return host.has_value();
+}
+inline std::ostream &operator<<(std::ostream &out, const ada::url &u) {
+  return out << u.to_string();
+}
+
+[[nodiscard]] size_t url::get_pathname_length() const noexcept {
+  return path.size();
+}
+
+[[nodiscard]] ada_really_inline ada::url_components url::get_components()
+    const noexcept {
+  url_components out{};
+
+  // protocol ends with ':'. for example: "https:"
+  out.protocol_end = uint32_t(get_protocol().size());
+
+  // Trailing index is always the next character of the current one.
+  size_t running_index = out.protocol_end;
+
+  if (host.has_value()) {
+    // 2 characters for "//" and 1 character for starting index
+    out.host_start = out.protocol_end + 2;
+
+    if (has_credentials()) {
+      out.username_end = uint32_t(out.host_start + username.size());
+
+      out.host_start += uint32_t(username.size());
+
+      if (!password.empty()) {
+        out.host_start += uint32_t(password.size() + 1);
+      }
+
+      out.host_end = uint32_t(out.host_start + host.value().size());
+    } else {
+      out.username_end = out.host_start;
+
+      // Host does not start with "@" if it does not include credentials.
+      out.host_end = uint32_t(out.host_start + host.value().size()) - 1;
+    }
+
+    running_index = out.host_end + 1;
+  } else {
+    // Update host start and end date to the same index, since it does not
+    // exist.
+    out.host_start = out.protocol_end;
+    out.host_end = out.host_start;
+
+    if (!has_opaque_path && checkers::begins_with(path, "//")) {
+      // If url's host is null, url does not have an opaque path, url's path's
+      // size is greater than 1, and url's path[0] is the empty string, then
+      // append U+002F (/) followed by U+002E (.) to output.
+      running_index = out.protocol_end + 2;
+    } else {
+      running_index = out.protocol_end;
+    }
+  }
+
+  if (port.has_value()) {
+    out.port = *port;
+    running_index += helpers::fast_digit_count(*port) + 1;  // Port omits ':'
+  }
+
+  out.pathname_start = uint32_t(running_index);
+
+  running_index += path.size();
+
+  if (query.has_value()) {
+    out.search_start = uint32_t(running_index);
+    running_index += get_search().size();
+    if (get_search().empty()) {
+      running_index++;
+    }
+  }
+
+  if (hash.has_value()) {
+    out.hash_start = uint32_t(running_index);
+  }
+
+  return out;
+}
+
+inline void url::update_base_hostname(std::string_view input) { host = input; }
+
+inline void url::update_unencoded_base_hash(std::string_view input) {
+  // We do the percent encoding
+  hash = unicode::percent_encode(input,
+                                 ada::character_sets::FRAGMENT_PERCENT_ENCODE);
+}
+
+inline void url::update_base_search(std::string_view input,
+                                    const uint8_t query_percent_encode_set[]) {
+  query = ada::unicode::percent_encode(input, query_percent_encode_set);
+}
+
+inline void url::update_base_search(std::optional<std::string> input) {
+  query = input;
+}
+
+inline void url::update_base_pathname(const std::string_view input) {
+  path = input;
+}
+
+inline void url::update_base_username(const std::string_view input) {
+  username = input;
+}
+
+inline void url::update_base_password(const std::string_view input) {
+  password = input;
+}
+
+inline void url::update_base_port(std::optional<uint16_t> input) {
+  port = input;
+}
+
+inline void url::clear_pathname() { path.clear(); }
+
+inline void url::clear_search() { query = std::nullopt; }
+
+[[nodiscard]] inline bool url::has_hash() const noexcept {
+  return hash.has_value();
+}
+
+[[nodiscard]] inline bool url::has_search() const noexcept {
+  return query.has_value();
+}
+
+inline void url::set_protocol_as_file() { type = ada::scheme::type::FILE; }
+
+inline void url::set_scheme(std::string &&new_scheme) noexcept {
+  type = ada::scheme::get_scheme_type(new_scheme);
+  // We only move the 'scheme' if it is non-special.
+  if (!is_special()) {
+    non_special_scheme = new_scheme;
+  }
+}
+
+inline void url::copy_scheme(ada::url &&u) noexcept {
+  non_special_scheme = u.non_special_scheme;
+  type = u.type;
+}
+
+inline void url::copy_scheme(const ada::url &u) {
+  non_special_scheme = u.non_special_scheme;
+  type = u.type;
+}
+
+[[nodiscard]] ada_really_inline std::string url::get_href() const noexcept {
+  std::string output = get_protocol();
+
+  if (host.has_value()) {
+    output += "//";
+    if (has_credentials()) {
+      output += username;
+      if (!password.empty()) {
+        output += ":" + get_password();
+      }
+      output += "@";
+    }
+    output += host.value();
+    if (port.has_value()) {
+      output += ":" + get_port();
+    }
+  } else if (!has_opaque_path && checkers::begins_with(path, "//")) {
+    // If url's host is null, url does not have an opaque path, url's path's
+    // size is greater than 1, and url's path[0] is the empty string, then
+    // append U+002F (/) followed by U+002E (.) to output.
+    output += "/.";
+  }
+  output += path;
+  if (query.has_value()) {
+    output += "?" + query.value();
+  }
+  if (hash.has_value()) {
+    output += "#" + hash.value();
+  }
+  return output;
+}
+
+ada_really_inline size_t url::parse_port(std::string_view view,
+                                         bool check_trailing_content) noexcept {
+  ada_log("parse_port('", view, "') ", view.size());
+  uint16_t parsed_port{};
+  auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port);
+  if (r.ec == std::errc::result_out_of_range) {
+    ada_log("parse_port: std::errc::result_out_of_range");
+    is_valid = false;
+    return 0;
+  }
+  ada_log("parse_port: ", parsed_port);
+  const size_t consumed = size_t(r.ptr - view.data());
+  ada_log("parse_port: consumed ", consumed);
+  if (check_trailing_content) {
+    is_valid &=
+        (consumed == view.size() || view[consumed] == '/' ||
+         view[consumed] == '?' || (is_special() && view[consumed] == '\\'));
+  }
+  ada_log("parse_port: is_valid = ", is_valid);
+  if (is_valid) {
+    // scheme_default_port can return 0, and we should allow 0 as a base port.
+    auto default_port = scheme_default_port();
+    bool is_port_valid = (default_port == 0 && parsed_port == 0) ||
+                         (default_port != parsed_port);
+    port = (r.ec == std::errc() && is_port_valid)
+               ? std::optional<uint16_t>(parsed_port)
+               : std::nullopt;
+  }
+  return consumed;
+}
+
+}  // namespace ada
+
+#endif  // ADA_URL_H
diff --git a/include/ada/url.h b/include/ada/url.h
new file mode 100644 (file)
index 0000000..c30fca2
--- /dev/null
@@ -0,0 +1,412 @@
+/**
+ * @file url.h
+ * @brief Declaration for the URL
+ */
+#ifndef ADA_URL_H
+#define ADA_URL_H
+
+#include "ada/checkers.h"
+#include "ada/common_defs.h"
+#include "ada/log.h"
+#include "ada/scheme.h"
+#include "ada/serializers.h"
+#include "ada/unicode.h"
+#include "ada/url_base.h"
+#include "ada/url_components.h"
+
+#include <algorithm>
+#include <charconv>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace ada {
+
+/**
+ * @brief Generic URL struct reliant on std::string instantiation.
+ *
+ * @details To disambiguate from a valid URL string it can also be referred to
+ * as a URL record. A URL is a struct that represents a universal identifier.
+ * Unlike the url_aggregator, the ada::url represents the different components
+ * of a parsed URL as independent std::string instances. This makes the
+ * structure heavier and more reliant on memory allocations. When getting
+ * components from the parsed URL, a new std::string is typically constructed.
+ *
+ * @see https://url.spec.whatwg.org/#url-representation
+ */
+struct url : url_base {
+  url() = default;
+  url(const url &u) = default;
+  url(url &&u) noexcept = default;
+  url &operator=(url &&u) noexcept = default;
+  url &operator=(const url &u) = default;
+  ~url() override = default;
+
+  /**
+   * @private
+   * A URL's username is an ASCII string identifying a username. It is initially
+   * the empty string.
+   */
+  std::string username{};
+
+  /**
+   * @private
+   * A URL's password is an ASCII string identifying a password. It is initially
+   * the empty string.
+   */
+  std::string password{};
+
+  /**
+   * @private
+   * A URL's host is null or a host. It is initially null.
+   */
+  std::optional<std::string> host{};
+
+  /**
+   * @private
+   * A URL's port is either null or a 16-bit unsigned integer that identifies a
+   * networking port. It is initially null.
+   */
+  std::optional<uint16_t> port{};
+
+  /**
+   * @private
+   * A URL's path is either an ASCII string or a list of zero or more ASCII
+   * strings, usually identifying a location.
+   */
+  std::string path{};
+
+  /**
+   * @private
+   * A URL's query is either null or an ASCII string. It is initially null.
+   */
+  std::optional<std::string> query{};
+
+  /**
+   * @private
+   * A URL's fragment is either null or an ASCII string that can be used for
+   * further processing on the resource the URL's other components identify. It
+   * is initially null.
+   */
+  std::optional<std::string> hash{};
+
+  /** @return true if it has an host but it is the empty string */
+  [[nodiscard]] inline bool has_empty_hostname() const noexcept;
+  /** @return true if the URL has a (non default) port */
+  [[nodiscard]] inline bool has_port() const noexcept;
+  /** @return true if it has a host (included an empty host) */
+  [[nodiscard]] inline bool has_hostname() const noexcept;
+  [[nodiscard]] bool has_valid_domain() const noexcept override;
+
+  /**
+   * Returns a JSON string representation of this URL.
+   */
+  [[nodiscard]] std::string to_string() const override;
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-url-href
+   * @see https://url.spec.whatwg.org/#concept-url-serializer
+   */
+  [[nodiscard]] ada_really_inline std::string get_href() const noexcept;
+
+  /**
+   * The origin getter steps are to return the serialization of this's URL's
+   * origin. [HTML]
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#concept-url-origin
+   */
+  [[nodiscard]] std::string get_origin() const noexcept override;
+
+  /**
+   * The protocol getter steps are to return this's URL's scheme, followed by
+   * U+003A (:).
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#dom-url-protocol
+   */
+  [[nodiscard]] std::string get_protocol() const noexcept;
+
+  /**
+   * Return url's host, serialized, followed by U+003A (:) and url's port,
+   * serialized.
+   * When there is no host, this function returns the empty string.
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#dom-url-host
+   */
+  [[nodiscard]] std::string get_host() const noexcept;
+
+  /**
+   * Return this's URL's host, serialized.
+   * When there is no host, this function returns the empty string.
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#dom-url-hostname
+   */
+  [[nodiscard]] std::string get_hostname() const noexcept;
+
+  /**
+   * The pathname getter steps are to return the result of URL path serializing
+   * this's URL.
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#dom-url-pathname
+   */
+  [[nodiscard]] std::string_view get_pathname() const noexcept;
+
+  /**
+   * Compute the pathname length in bytes without instantiating a view or a
+   * string.
+   * @return size of the pathname in bytes
+   * @see https://url.spec.whatwg.org/#dom-url-pathname
+   */
+  [[nodiscard]] ada_really_inline size_t get_pathname_length() const noexcept;
+
+  /**
+   * Return U+003F (?), followed by this's URL's query.
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#dom-url-search
+   */
+  [[nodiscard]] std::string get_search() const noexcept;
+
+  /**
+   * The username getter steps are to return this's URL's username.
+   * @return a constant reference to the underlying string.
+   * @see https://url.spec.whatwg.org/#dom-url-username
+   */
+  [[nodiscard]] const std::string &get_username() const noexcept;
+
+  /**
+   * @return Returns true on successful operation.
+   * @see https://url.spec.whatwg.org/#dom-url-username
+   */
+  bool set_username(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-password
+   */
+  bool set_password(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-port
+   */
+  bool set_port(std::string_view input);
+
+  /**
+   * This function always succeeds.
+   * @see https://url.spec.whatwg.org/#dom-url-hash
+   */
+  void set_hash(std::string_view input);
+
+  /**
+   * This function always succeeds.
+   * @see https://url.spec.whatwg.org/#dom-url-search
+   */
+  void set_search(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-search
+   */
+  bool set_pathname(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-host
+   */
+  bool set_host(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-hostname
+   */
+  bool set_hostname(std::string_view input);
+
+  /**
+   * @return Returns true on success.
+   * @see https://url.spec.whatwg.org/#dom-url-protocol
+   */
+  bool set_protocol(std::string_view input);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-url-href
+   */
+  bool set_href(std::string_view input);
+
+  /**
+   * The password getter steps are to return this's URL's password.
+   * @return a constant reference to the underlying string.
+   * @see https://url.spec.whatwg.org/#dom-url-password
+   */
+  [[nodiscard]] const std::string &get_password() const noexcept;
+
+  /**
+   * Return this's URL's port, serialized.
+   * @return a newly constructed string representing the port.
+   * @see https://url.spec.whatwg.org/#dom-url-port
+   */
+  [[nodiscard]] std::string get_port() const noexcept;
+
+  /**
+   * Return U+0023 (#), followed by this's URL's fragment.
+   * @return a newly constructed string representing the hash.
+   * @see https://url.spec.whatwg.org/#dom-url-hash
+   */
+  [[nodiscard]] std::string get_hash() const noexcept;
+
+  /**
+   * A URL includes credentials if its username or password is not the empty
+   * string.
+   */
+  [[nodiscard]] ada_really_inline bool has_credentials() const noexcept;
+
+  /**
+   * Useful for implementing efficient serialization for the URL.
+   *
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   *
+   * Inspired after servo/url
+   *
+   * @return a newly constructed component.
+   *
+   * @see
+   * https://github.com/servo/rust-url/blob/b65a45515c10713f6d212e6726719a020203cc98/url/src/quirks.rs#L31
+   */
+  [[nodiscard]] ada_really_inline ada::url_components get_components()
+      const noexcept;
+  /** @return true if the URL has a hash component */
+  [[nodiscard]] inline bool has_hash() const noexcept override;
+  /** @return true if the URL has a search component */
+  [[nodiscard]] inline bool has_search() const noexcept override;
+
+ private:
+  friend ada::url ada::parser::parse_url<ada::url>(std::string_view,
+                                                   const ada::url *);
+  friend ada::url_aggregator ada::parser::parse_url<ada::url_aggregator>(
+      std::string_view, const ada::url_aggregator *);
+  friend void ada::helpers::strip_trailing_spaces_from_opaque_path<ada::url>(
+      ada::url &url) noexcept;
+
+  inline void update_unencoded_base_hash(std::string_view input);
+  inline void update_base_hostname(std::string_view input);
+  inline void update_base_search(std::string_view input);
+  inline void update_base_search(std::string_view input,
+                                 const uint8_t query_percent_encode_set[]);
+  inline void update_base_search(std::optional<std::string> input);
+  inline void update_base_pathname(std::string_view input);
+  inline void update_base_username(std::string_view input);
+  inline void update_base_password(std::string_view input);
+  inline void update_base_port(std::optional<uint16_t> input);
+
+  /**
+   * Sets the host or hostname according to override condition.
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#hostname-state
+   */
+  template <bool override_hostname = false>
+  bool set_host_or_hostname(std::string_view input);
+
+  /**
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#concept-ipv4-parser
+   */
+  [[nodiscard]] bool parse_ipv4(std::string_view input);
+
+  /**
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#concept-ipv6-parser
+   */
+  [[nodiscard]] bool parse_ipv6(std::string_view input);
+
+  /**
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#concept-opaque-host-parser
+   */
+  [[nodiscard]] bool parse_opaque_host(std::string_view input);
+
+  /**
+   * A URL's scheme is an ASCII string that identifies the type of URL and can
+   * be used to dispatch a URL for further processing after parsing. It is
+   * initially the empty string. We only set non_special_scheme when the scheme
+   * is non-special, otherwise we avoid constructing string.
+   *
+   * Special schemes are stored in ada::scheme::details::is_special_list so we
+   * typically do not need to store them in each url instance.
+   */
+  std::string non_special_scheme{};
+
+  /**
+   * A URL cannot have a username/password/port if its host is null or the empty
+   * string, or its scheme is "file".
+   */
+  [[nodiscard]] inline bool cannot_have_credentials_or_port() const;
+
+  ada_really_inline size_t parse_port(
+      std::string_view view, bool check_trailing_content) noexcept override;
+
+  ada_really_inline size_t parse_port(std::string_view view) noexcept override {
+    return this->parse_port(view, false);
+  }
+
+  /**
+   * Take the scheme from another URL. The scheme string is copied from the
+   * provided url.
+   */
+  inline void copy_scheme(const ada::url &u);
+
+  /**
+   * Parse the host from the provided input. We assume that
+   * the input does not contain spaces or tabs. Control
+   * characters and spaces are not trimmed (they should have
+   * been removed if needed).
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#host-parsing
+   */
+  [[nodiscard]] ada_really_inline bool parse_host(std::string_view input);
+
+  template <bool has_state_override = false>
+  [[nodiscard]] ada_really_inline bool parse_scheme(std::string_view input);
+
+  inline void clear_pathname() override;
+  inline void clear_search() override;
+  inline void set_protocol_as_file();
+
+  /**
+   * Parse the path from the provided input.
+   * Return true on success. Control characters not
+   * trimmed from the ends (they should have
+   * been removed if needed).
+   *
+   * The input is expected to be UTF-8.
+   *
+   * @see https://url.spec.whatwg.org/
+   */
+  ada_really_inline void parse_path(std::string_view input);
+
+  /**
+   * Set the scheme for this URL. The provided scheme should be a valid
+   * scheme string, be lower-cased, not contain spaces or tabs. It should
+   * have no spurious trailing or leading content.
+   */
+  inline void set_scheme(std::string &&new_scheme) noexcept;
+
+  /**
+   * Take the scheme from another URL. The scheme string is moved from the
+   * provided url.
+   */
+  inline void copy_scheme(ada::url &&u) noexcept;
+
+};  // struct url
+
+inline std::ostream &operator<<(std::ostream &out, const ada::url &u);
+}  // namespace ada
+
+#endif  // ADA_URL_H
diff --git a/include/ada/url_aggregator-inl.h b/include/ada/url_aggregator-inl.h
new file mode 100644 (file)
index 0000000..af66997
--- /dev/null
@@ -0,0 +1,923 @@
+/**
+ * @file url_aggregator-inl.h
+ * @brief Inline functions for url aggregator
+ */
+#ifndef ADA_URL_AGGREGATOR_INL_H
+#define ADA_URL_AGGREGATOR_INL_H
+
+#include "ada/character_sets.h"
+#include "ada/character_sets-inl.h"
+#include "ada/checkers-inl.h"
+#include "ada/helpers.h"
+#include "ada/unicode.h"
+#include "ada/unicode-inl.h"
+#include "ada/url_aggregator.h"
+#include "ada/url_components.h"
+#include "ada/scheme.h"
+#include "ada/log.h"
+
+#include <optional>
+#include <string_view>
+
+namespace ada {
+
+inline void url_aggregator::update_base_authority(
+    std::string_view base_buffer, const ada::url_components &base) {
+  std::string_view input = base_buffer.substr(
+      base.protocol_end, base.host_start - base.protocol_end);
+  ada_log("url_aggregator::update_base_authority ", input);
+
+  bool input_starts_with_dash = checkers::begins_with(input, "//");
+  uint32_t diff = components.host_start - components.protocol_end;
+
+  buffer.erase(components.protocol_end,
+               components.host_start - components.protocol_end);
+  components.username_end = components.protocol_end;
+
+  if (input_starts_with_dash) {
+    input.remove_prefix(2);
+    diff += 2;  // add "//"
+    buffer.insert(components.protocol_end, "//");
+    components.username_end += 2;
+  }
+
+  size_t password_delimiter = input.find(':');
+
+  // Check if input contains both username and password by checking the
+  // delimiter: ":" A typical input that contains authority would be "user:pass"
+  if (password_delimiter != std::string_view::npos) {
+    // Insert both username and password
+    std::string_view username = input.substr(0, password_delimiter);
+    std::string_view password = input.substr(password_delimiter + 1);
+
+    buffer.insert(components.protocol_end + diff, username);
+    diff += uint32_t(username.size());
+    buffer.insert(components.protocol_end + diff, ":");
+    components.username_end = components.protocol_end + diff;
+    buffer.insert(components.protocol_end + diff + 1, password);
+    diff += uint32_t(password.size()) + 1;
+  } else if (!input.empty()) {
+    // Insert only username
+    buffer.insert(components.protocol_end + diff, input);
+    components.username_end =
+        components.protocol_end + diff + uint32_t(input.size());
+    diff += uint32_t(input.size());
+  }
+
+  components.host_start += diff;
+
+  if (buffer.size() > base.host_start && buffer[base.host_start] != '@') {
+    buffer.insert(components.host_start, "@");
+    diff++;
+  }
+  components.host_end += diff;
+  components.pathname_start += diff;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += diff;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += diff;
+  }
+}
+
+inline void url_aggregator::update_unencoded_base_hash(std::string_view input) {
+  ada_log("url_aggregator::update_unencoded_base_hash ", input, " [",
+          input.size(), " bytes], buffer is '", buffer, "' [", buffer.size(),
+          " bytes] components.hash_start = ", components.hash_start);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (components.hash_start != url_components::omitted) {
+    buffer.resize(components.hash_start);
+  }
+  components.hash_start = uint32_t(buffer.size());
+  buffer += "#";
+  bool encoding_required = unicode::percent_encode<true>(
+      input, ada::character_sets::FRAGMENT_PERCENT_ENCODE, buffer);
+  // When encoding_required is false, then buffer is left unchanged, and percent
+  // encoding was not deemed required.
+  if (!encoding_required) {
+    buffer.append(input);
+  }
+  ada_log("url_aggregator::update_unencoded_base_hash final buffer is '",
+          buffer, "' [", buffer.size(), " bytes]");
+  ADA_ASSERT_TRUE(validate());
+}
+
+ada_really_inline uint32_t url_aggregator::replace_and_resize(
+    uint32_t start, uint32_t end, std::string_view input) {
+  uint32_t current_length = end - start;
+  uint32_t input_size = uint32_t(input.size());
+  uint32_t new_difference = input_size - current_length;
+
+  if (current_length == 0) {
+    buffer.insert(start, input);
+  } else if (input_size == current_length) {
+    buffer.replace(start, input_size, input);
+  } else if (input_size < current_length) {
+    buffer.erase(start, current_length - input_size);
+    buffer.replace(start, input_size, input);
+  } else {
+    buffer.replace(start, current_length, input.substr(0, current_length));
+    buffer.insert(start + current_length, input.substr(current_length));
+  }
+
+  return new_difference;
+}
+
+inline void url_aggregator::update_base_hostname(const std::string_view input) {
+  ada_log("url_aggregator::update_base_hostname ", input, " [", input.size(),
+          " bytes], buffer is '", buffer, "' [", buffer.size(), " bytes]");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+
+  // This next line is required for when parsing a URL like `foo://`
+  add_authority_slashes_if_needed();
+
+  bool has_credentials = components.protocol_end + 2 < components.host_start;
+  uint32_t new_difference =
+      replace_and_resize(components.host_start, components.host_end, input);
+
+  if (has_credentials) {
+    buffer.insert(components.host_start, "@");
+    new_difference++;
+  }
+  components.host_end += new_difference;
+  components.pathname_start += new_difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += new_difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += new_difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+[[nodiscard]] ada_really_inline uint32_t
+url_aggregator::get_pathname_length() const noexcept {
+  ada_log("url_aggregator::get_pathname_length");
+  uint32_t ending_index = uint32_t(buffer.size());
+  if (components.search_start != url_components::omitted) {
+    ending_index = components.search_start;
+  } else if (components.hash_start != url_components::omitted) {
+    ending_index = components.hash_start;
+  }
+  return ending_index - components.pathname_start;
+}
+
+[[nodiscard]] ada_really_inline bool url_aggregator::is_at_path()
+    const noexcept {
+  return buffer.size() == components.pathname_start;
+}
+
+inline void url_aggregator::update_base_search(std::string_view input) {
+  ada_log("url_aggregator::update_base_search ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (input.empty()) {
+    clear_search();
+    return;
+  }
+
+  if (input[0] == '?') {
+    input.remove_prefix(1);
+  }
+
+  if (components.hash_start == url_components::omitted) {
+    if (components.search_start == url_components::omitted) {
+      components.search_start = uint32_t(buffer.size());
+      buffer += "?";
+    } else {
+      buffer.resize(components.search_start + 1);
+    }
+
+    buffer.append(input);
+  } else {
+    if (components.search_start == url_components::omitted) {
+      components.search_start = components.hash_start;
+    } else {
+      buffer.erase(components.search_start,
+                   components.hash_start - components.search_start);
+      components.hash_start = components.search_start;
+    }
+
+    buffer.insert(components.search_start, "?");
+    buffer.insert(components.search_start + 1, input);
+    components.hash_start += uint32_t(input.size() + 1);  // Do not forget `?`
+  }
+
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::update_base_search(
+    std::string_view input, const uint8_t query_percent_encode_set[]) {
+  ada_log("url_aggregator::update_base_search ", input,
+          " with encoding parameter ", to_string(), "\n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+
+  if (components.hash_start == url_components::omitted) {
+    if (components.search_start == url_components::omitted) {
+      components.search_start = uint32_t(buffer.size());
+      buffer += "?";
+    } else {
+      buffer.resize(components.search_start + 1);
+    }
+
+    bool encoding_required =
+        unicode::percent_encode<true>(input, query_percent_encode_set, buffer);
+    // When encoding_required is false, then buffer is left unchanged, and
+    // percent encoding was not deemed required.
+    if (!encoding_required) {
+      buffer.append(input);
+    }
+  } else {
+    if (components.search_start == url_components::omitted) {
+      components.search_start = components.hash_start;
+    } else {
+      buffer.erase(components.search_start,
+                   components.hash_start - components.search_start);
+      components.hash_start = components.search_start;
+    }
+
+    buffer.insert(components.search_start, "?");
+    size_t idx =
+        ada::unicode::percent_encode_index(input, query_percent_encode_set);
+    if (idx == input.size()) {
+      buffer.insert(components.search_start + 1, input);
+      components.hash_start += uint32_t(input.size() + 1);  // Do not forget `?`
+    } else {
+      buffer.insert(components.search_start + 1, input, 0, idx);
+      input.remove_prefix(idx);
+      // We only create a temporary string if we need percent encoding and
+      // we attempt to create as small a temporary string as we can.
+      std::string encoded =
+          ada::unicode::percent_encode(input, query_percent_encode_set);
+      buffer.insert(components.search_start + idx + 1, encoded);
+      components.hash_start +=
+          uint32_t(encoded.size() + idx + 1);  // Do not forget `?`
+    }
+  }
+
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::update_base_pathname(const std::string_view input) {
+  ada_log("url_aggregator::update_base_pathname '", input, "' [", input.size(),
+          " bytes] \n", to_diagram());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  ADA_ASSERT_TRUE(validate());
+
+  const bool begins_with_dashdash = checkers::begins_with(input, "//");
+  if (!begins_with_dashdash && has_dash_dot()) {
+    ada_log("url_aggregator::update_base_pathname has /.: \n", to_diagram());
+    // We must delete the ./
+    delete_dash_dot();
+  }
+
+  if (begins_with_dashdash && !has_opaque_path && !has_authority() &&
+      !has_dash_dot()) {
+    // If url's host is null, url does not have an opaque path, url's path's
+    // size is greater than 1, then append U+002F (/) followed by U+002E (.) to
+    // output.
+    buffer.insert(components.pathname_start, "/.");
+    components.pathname_start += 2;
+  }
+
+  uint32_t difference = replace_and_resize(
+      components.pathname_start,
+      components.pathname_start + get_pathname_length(), input);
+  if (components.search_start != url_components::omitted) {
+    components.search_start += difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += difference;
+  }
+  ada_log("url_aggregator::update_base_pathname end '", input, "' [",
+          input.size(), " bytes] \n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::append_base_pathname(const std::string_view input) {
+  ada_log("url_aggregator::append_base_pathname ", input, " ", to_string(),
+          "\n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+#if ADA_DEVELOPMENT_CHECKS
+  // computing the expected password.
+  std::string path_expected(get_pathname());
+  path_expected.append(input);
+#endif  // ADA_DEVELOPMENT_CHECKS
+  uint32_t ending_index = uint32_t(buffer.size());
+  if (components.search_start != url_components::omitted) {
+    ending_index = components.search_start;
+  } else if (components.hash_start != url_components::omitted) {
+    ending_index = components.hash_start;
+  }
+  buffer.insert(ending_index, input);
+
+  if (components.search_start != url_components::omitted) {
+    components.search_start += uint32_t(input.size());
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += uint32_t(input.size());
+  }
+#if ADA_DEVELOPMENT_CHECKS
+  std::string path_after = std::string(get_pathname());
+  ADA_ASSERT_EQUAL(
+      path_expected, path_after,
+      "append_base_pathname problem after inserting " + std::string(input));
+#endif  // ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::update_base_username(const std::string_view input) {
+  ada_log("url_aggregator::update_base_username '", input, "' ", to_string(),
+          "\n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+
+  add_authority_slashes_if_needed();
+
+  bool has_password = has_non_empty_password();
+  bool host_starts_with_at = buffer.size() > components.host_start &&
+                             buffer[components.host_start] == '@';
+  uint32_t diff = replace_and_resize(components.protocol_end + 2,
+                                     components.username_end, input);
+
+  components.username_end += diff;
+  components.host_start += diff;
+
+  if (!input.empty() && !host_starts_with_at) {
+    buffer.insert(components.host_start, "@");
+    diff++;
+  } else if (input.empty() && host_starts_with_at && !has_password) {
+    // Input is empty, there is no password, and we need to remove "@" from
+    // hostname
+    buffer.erase(components.host_start, 1);
+    diff--;
+  }
+
+  components.host_end += diff;
+  components.pathname_start += diff;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += diff;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += diff;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::append_base_username(const std::string_view input) {
+  ada_log("url_aggregator::append_base_username ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+#if ADA_DEVELOPMENT_CHECKS
+  // computing the expected password.
+  std::string username_expected(get_username());
+  username_expected.append(input);
+#endif  // ADA_DEVELOPMENT_CHECKS
+  add_authority_slashes_if_needed();
+
+  // If input is empty, do nothing.
+  if (input.empty()) {
+    return;
+  }
+
+  uint32_t difference = uint32_t(input.size());
+  buffer.insert(components.username_end, input);
+  components.username_end += difference;
+  components.host_start += difference;
+
+  if (buffer[components.host_start] != '@' &&
+      components.host_start != components.host_end) {
+    buffer.insert(components.host_start, "@");
+    difference++;
+  }
+
+  components.host_end += difference;
+  components.pathname_start += difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += difference;
+  }
+#if ADA_DEVELOPMENT_CHECKS
+  std::string username_after(get_username());
+  ADA_ASSERT_EQUAL(
+      username_expected, username_after,
+      "append_base_username problem after inserting " + std::string(input));
+#endif  // ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::clear_password() {
+  ada_log("url_aggregator::clear_password ", to_string(), "\n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+  if (!has_password()) {
+    return;
+  }
+
+  uint32_t diff = components.host_start - components.username_end;
+  buffer.erase(components.username_end, diff);
+  components.host_start -= diff;
+  components.host_end -= diff;
+  components.pathname_start -= diff;
+  if (components.search_start != url_components::omitted) {
+    components.search_start -= diff;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start -= diff;
+  }
+}
+
+inline void url_aggregator::update_base_password(const std::string_view input) {
+  ada_log("url_aggregator::update_base_password ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+
+  add_authority_slashes_if_needed();
+
+  // TODO: Optimization opportunity. Merge the following removal functions.
+  if (input.empty()) {
+    clear_password();
+
+    // Remove username too, if it is empty.
+    if (!has_non_empty_username()) {
+      update_base_username("");
+    }
+
+    return;
+  }
+
+  bool password_exists = has_password();
+  uint32_t difference = uint32_t(input.size());
+
+  if (password_exists) {
+    uint32_t current_length =
+        components.host_start - components.username_end - 1;
+    buffer.erase(components.username_end + 1, current_length);
+    difference -= current_length;
+  } else {
+    buffer.insert(components.username_end, ":");
+    difference++;
+  }
+
+  buffer.insert(components.username_end + 1, input);
+  components.host_start += difference;
+
+  // The following line is required to add "@" to hostname. When updating
+  // password if hostname does not start with "@", it is "update_base_password"s
+  // responsibility to set it.
+  if (buffer[components.host_start] != '@') {
+    buffer.insert(components.host_start, "@");
+    difference++;
+  }
+
+  components.host_end += difference;
+  components.pathname_start += difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::append_base_password(const std::string_view input) {
+  ada_log("url_aggregator::append_base_password ", input, " ", to_string(),
+          "\n", to_diagram());
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+#if ADA_DEVELOPMENT_CHECKS
+  // computing the expected password.
+  std::string password_expected = std::string(get_password());
+  password_expected.append(input);
+#endif  // ADA_DEVELOPMENT_CHECKS
+  add_authority_slashes_if_needed();
+
+  // If input is empty, do nothing.
+  if (input.empty()) {
+    return;
+  }
+
+  uint32_t difference = uint32_t(input.size());
+  if (has_password()) {
+    buffer.insert(components.host_start, input);
+  } else {
+    difference++;  // Increment for ":"
+    buffer.insert(components.username_end, ":");
+    buffer.insert(components.username_end + 1, input);
+  }
+  components.host_start += difference;
+
+  // The following line is required to add "@" to hostname. When updating
+  // password if hostname does not start with "@", it is "append_base_password"s
+  // responsibility to set it.
+  if (buffer[components.host_start] != '@') {
+    buffer.insert(components.host_start, "@");
+    difference++;
+  }
+
+  components.host_end += difference;
+  components.pathname_start += difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += difference;
+  }
+#if ADA_DEVELOPMENT_CHECKS
+  std::string password_after(get_password());
+  ADA_ASSERT_EQUAL(
+      password_expected, password_after,
+      "append_base_password problem after inserting " + std::string(input));
+#endif  // ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::update_base_port(uint32_t input) {
+  ada_log("url_aggregator::update_base_port");
+  ADA_ASSERT_TRUE(validate());
+  if (input == url_components::omitted) {
+    clear_port();
+    return;
+  }
+  // calling std::to_string(input.value()) is unfortunate given that the port
+  // value is probably already available as a string.
+  std::string value = helpers::concat(":", std::to_string(input));
+  uint32_t difference = uint32_t(value.size());
+
+  if (components.port != url_components::omitted) {
+    difference -= components.pathname_start - components.host_end;
+    buffer.erase(components.host_end,
+                 components.pathname_start - components.host_end);
+  }
+
+  buffer.insert(components.host_end, value);
+  components.pathname_start += difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += difference;
+  }
+  components.port = input;
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::clear_port() {
+  ada_log("url_aggregator::clear_port");
+  ADA_ASSERT_TRUE(validate());
+  if (components.port == url_components::omitted) {
+    return;
+  }
+  uint32_t length = components.pathname_start - components.host_end;
+  buffer.erase(components.host_end, length);
+  components.pathname_start -= length;
+  if (components.search_start != url_components::omitted) {
+    components.search_start -= length;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start -= length;
+  }
+  components.port = url_components::omitted;
+  ADA_ASSERT_TRUE(validate());
+}
+
+[[nodiscard]] inline uint32_t url_aggregator::retrieve_base_port() const {
+  ada_log("url_aggregator::retrieve_base_port");
+  return components.port;
+}
+
+inline void url_aggregator::clear_search() {
+  ada_log("url_aggregator::clear_search");
+  ADA_ASSERT_TRUE(validate());
+  if (components.search_start == url_components::omitted) {
+    return;
+  }
+
+  if (components.hash_start == url_components::omitted) {
+    buffer.resize(components.search_start);
+  } else {
+    buffer.erase(components.search_start,
+                 components.hash_start - components.search_start);
+    components.hash_start = components.search_start;
+  }
+
+  components.search_start = url_components::omitted;
+
+#if ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_EQUAL(get_search(), "",
+                   "search should have been cleared on buffer=" + buffer +
+                       " with " + components.to_string() + "\n" + to_diagram());
+#endif
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::clear_hash() {
+  ada_log("url_aggregator::clear_hash");
+  ADA_ASSERT_TRUE(validate());
+  if (components.hash_start == url_components::omitted) {
+    return;
+  }
+  buffer.resize(components.hash_start);
+  components.hash_start = url_components::omitted;
+
+#if ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_EQUAL(get_hash(), "",
+                   "hash should have been cleared on buffer=" + buffer +
+                       " with " + components.to_string() + "\n" + to_diagram());
+#endif
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::clear_pathname() {
+  ada_log("url_aggregator::clear_pathname");
+  ADA_ASSERT_TRUE(validate());
+  uint32_t ending_index = uint32_t(buffer.size());
+  if (components.search_start != url_components::omitted) {
+    ending_index = components.search_start;
+  } else if (components.hash_start != url_components::omitted) {
+    ending_index = components.hash_start;
+  }
+  uint32_t pathname_length = ending_index - components.pathname_start;
+  buffer.erase(components.pathname_start, pathname_length);
+  uint32_t difference = pathname_length;
+  if (components.pathname_start == components.host_end + 2 &&
+      buffer[components.host_end] == '/' &&
+      buffer[components.host_end + 1] == '.') {
+    components.pathname_start -= 2;
+    buffer.erase(components.host_end, 2);
+    difference += 2;
+  }
+  if (components.search_start != url_components::omitted) {
+    components.search_start -= difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start -= difference;
+  }
+  ada_log("url_aggregator::clear_pathname completed, running checks...");
+#if ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_EQUAL(get_pathname(), "",
+                   "pathname should have been cleared on buffer=" + buffer +
+                       " with " + components.to_string() + "\n" + to_diagram());
+#endif
+  ADA_ASSERT_TRUE(validate());
+  ada_log("url_aggregator::clear_pathname completed, running checks... ok");
+}
+
+inline void url_aggregator::clear_hostname() {
+  ada_log("url_aggregator::clear_hostname");
+  ADA_ASSERT_TRUE(validate());
+  if (!has_authority()) {
+    return;
+  }
+  ADA_ASSERT_TRUE(has_authority());
+
+  uint32_t hostname_length = components.host_end - components.host_start;
+  uint32_t start = components.host_start;
+
+  // If hostname starts with "@", we should not remove that character.
+  if (hostname_length > 0 && buffer[start] == '@') {
+    start++;
+    hostname_length--;
+  }
+  buffer.erase(start, hostname_length);
+  components.host_end = start;
+  components.pathname_start -= hostname_length;
+  if (components.search_start != url_components::omitted) {
+    components.search_start -= hostname_length;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start -= hostname_length;
+  }
+#if ADA_DEVELOPMENT_CHECKS
+  ADA_ASSERT_EQUAL(get_hostname(), "",
+                   "hostname should have been cleared on buffer=" + buffer +
+                       " with " + components.to_string() + "\n" + to_diagram());
+#endif
+  ADA_ASSERT_TRUE(has_authority());
+  ADA_ASSERT_EQUAL(has_empty_hostname(), true,
+                   "hostname should have been cleared on buffer=" + buffer +
+                       " with " + components.to_string() + "\n" + to_diagram());
+  ADA_ASSERT_TRUE(validate());
+}
+
+[[nodiscard]] inline bool url_aggregator::has_hash() const noexcept {
+  ada_log("url_aggregator::has_hash");
+  return components.hash_start != url_components::omitted;
+}
+
+[[nodiscard]] inline bool url_aggregator::has_search() const noexcept {
+  ada_log("url_aggregator::has_search");
+  return components.search_start != url_components::omitted;
+}
+
+ada_really_inline bool url_aggregator::has_credentials() const noexcept {
+  ada_log("url_aggregator::has_credentials");
+  return has_non_empty_username() || has_non_empty_password();
+}
+
+inline bool url_aggregator::cannot_have_credentials_or_port() const {
+  ada_log("url_aggregator::cannot_have_credentials_or_port");
+  return type == ada::scheme::type::FILE ||
+         components.host_start == components.host_end;
+}
+
+[[nodiscard]] ada_really_inline const ada::url_components &
+url_aggregator::get_components() const noexcept {
+  return components;
+}
+
+[[nodiscard]] inline bool ada::url_aggregator::has_authority() const noexcept {
+  ada_log("url_aggregator::has_authority");
+  // Performance: instead of doing this potentially expensive check, we could
+  // have a boolean in the struct.
+  return components.protocol_end + 2 <= components.host_start &&
+         helpers::substring(buffer, components.protocol_end,
+                            components.protocol_end + 2) == "//";
+}
+
+inline void ada::url_aggregator::add_authority_slashes_if_needed() noexcept {
+  ada_log("url_aggregator::add_authority_slashes_if_needed");
+  ADA_ASSERT_TRUE(validate());
+  // Protocol setter will insert `http:` to the URL. It is up to hostname setter
+  // to insert
+  // `//` initially to the buffer, since it depends on the hostname existence.
+  if (has_authority()) {
+    return;
+  }
+  // Performance: the common case is components.protocol_end == buffer.size()
+  // Optimization opportunity: in many cases, the "//" is part of the input and
+  // the insert could be fused with another insert.
+  buffer.insert(components.protocol_end, "//");
+  components.username_end += 2;
+  components.host_start += 2;
+  components.host_end += 2;
+  components.pathname_start += 2;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += 2;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += 2;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void ada::url_aggregator::reserve(uint32_t capacity) {
+  buffer.reserve(capacity);
+}
+
+inline bool url_aggregator::has_non_empty_username() const noexcept {
+  ada_log("url_aggregator::has_non_empty_username");
+  return components.protocol_end + 2 < components.username_end;
+}
+
+inline bool url_aggregator::has_non_empty_password() const noexcept {
+  ada_log("url_aggregator::has_non_empty_password");
+  return components.host_start - components.username_end > 0;
+}
+
+inline bool url_aggregator::has_password() const noexcept {
+  ada_log("url_aggregator::has_password");
+  // This function does not care about the length of the password
+  return components.host_start > components.username_end &&
+         buffer[components.username_end] == ':';
+}
+
+inline bool url_aggregator::has_empty_hostname() const noexcept {
+  if (!has_hostname()) {
+    return false;
+  }
+  if (components.host_start == components.host_end) {
+    return true;
+  }
+  if (components.host_end > components.host_start + 1) {
+    return false;
+  }
+  return components.username_end != components.host_start;
+}
+
+inline bool url_aggregator::has_hostname() const noexcept {
+  return has_authority();
+}
+
+inline bool url_aggregator::has_port() const noexcept {
+  ada_log("url_aggregator::has_port");
+  // A URL cannot have a username/password/port if its host is null or the empty
+  // string, or its scheme is "file".
+  return has_hostname() && components.pathname_start != components.host_end;
+}
+
+[[nodiscard]] inline bool url_aggregator::has_dash_dot() const noexcept {
+  // If url's host is null, url does not have an opaque path, url's path's size
+  // is greater than 1, and url's path[0] is the empty string, then append
+  // U+002F (/) followed by U+002E (.) to output.
+  ada_log("url_aggregator::has_dash_dot");
+#if ADA_DEVELOPMENT_CHECKS
+  // If pathname_start and host_end are exactly two characters apart, then we
+  // either have a one-digit port such as http://test.com:5?param=1 or else we
+  // have a /.: sequence such as "non-spec:/.//". We test that this is the case.
+  if (components.pathname_start == components.host_end + 2) {
+    ADA_ASSERT_TRUE((buffer[components.host_end] == '/' &&
+                     buffer[components.host_end + 1] == '.') ||
+                    (buffer[components.host_end] == ':' &&
+                     checkers::is_digit(buffer[components.host_end + 1])));
+  }
+  if (components.pathname_start == components.host_end + 2 &&
+      buffer[components.host_end] == '/' &&
+      buffer[components.host_end + 1] == '.') {
+    ADA_ASSERT_TRUE(components.pathname_start + 1 < buffer.size());
+    ADA_ASSERT_TRUE(buffer[components.pathname_start] == '/');
+    ADA_ASSERT_TRUE(buffer[components.pathname_start + 1] == '/');
+  }
+#endif
+  // Performance: it should be uncommon for components.pathname_start ==
+  // components.host_end + 2 to be true. So we put this check first in the
+  // sequence. Most times, we do not have an opaque path. Checking for '/.' is
+  // more expensive, but should be uncommon.
+  return components.pathname_start == components.host_end + 2 &&
+         !has_opaque_path && buffer[components.host_end] == '/' &&
+         buffer[components.host_end + 1] == '.';
+}
+
+[[nodiscard]] inline std::string_view url_aggregator::get_href()
+    const noexcept {
+  ada_log("url_aggregator::get_href");
+  return buffer;
+}
+
+ada_really_inline size_t url_aggregator::parse_port(
+    std::string_view view, bool check_trailing_content) noexcept {
+  ada_log("url_aggregator::parse_port('", view, "') ", view.size());
+  uint16_t parsed_port{};
+  auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port);
+  if (r.ec == std::errc::result_out_of_range) {
+    ada_log("parse_port: std::errc::result_out_of_range");
+    is_valid = false;
+    return 0;
+  }
+  ada_log("parse_port: ", parsed_port);
+  const size_t consumed = size_t(r.ptr - view.data());
+  ada_log("parse_port: consumed ", consumed);
+  if (check_trailing_content) {
+    is_valid &=
+        (consumed == view.size() || view[consumed] == '/' ||
+         view[consumed] == '?' || (is_special() && view[consumed] == '\\'));
+  }
+  ada_log("parse_port: is_valid = ", is_valid);
+  if (is_valid) {
+    ada_log("parse_port", r.ec == std::errc());
+    // scheme_default_port can return 0, and we should allow 0 as a base port.
+    auto default_port = scheme_default_port();
+    bool is_port_valid = (default_port == 0 && parsed_port == 0) ||
+                         (default_port != parsed_port);
+    if (r.ec == std::errc() && is_port_valid) {
+      update_base_port(parsed_port);
+    } else {
+      clear_port();
+    }
+  }
+  return consumed;
+}
+
+inline void url_aggregator::set_protocol_as_file() {
+  ada_log("url_aggregator::set_protocol_as_file ");
+  ADA_ASSERT_TRUE(validate());
+  type = ada::scheme::type::FILE;
+  // next line could overflow but unsigned arithmetic has well-defined
+  // overflows.
+  uint32_t new_difference = 5 - components.protocol_end;
+
+  if (buffer.empty()) {
+    buffer.append("file:");
+  } else {
+    buffer.erase(0, components.protocol_end);
+    buffer.insert(0, "file:");
+  }
+  components.protocol_end = 5;
+
+  // Update the rest of the components.
+  components.username_end += new_difference;
+  components.host_start += new_difference;
+  components.host_end += new_difference;
+  components.pathname_start += new_difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += new_difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += new_difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline std::ostream &operator<<(std::ostream &out,
+                                const ada::url_aggregator &u) {
+  return out << u.to_string();
+}
+}  // namespace ada
+
+#endif  // ADA_URL_AGGREGATOR_INL_H
diff --git a/include/ada/url_aggregator.h b/include/ada/url_aggregator.h
new file mode 100644 (file)
index 0000000..2c269a3
--- /dev/null
@@ -0,0 +1,309 @@
+/**
+ * @file url_aggregator.h
+ * @brief Declaration for the basic URL definitions
+ */
+#ifndef ADA_URL_AGGREGATOR_H
+#define ADA_URL_AGGREGATOR_H
+
+#include "ada/common_defs.h"
+#include "ada/url_base.h"
+#include "ada/url_components.h"
+
+#include <string>
+#include <string_view>
+
+namespace ada {
+
+/**
+ * @brief Lightweight URL struct.
+ *
+ * @details The url_aggregator class aims to minimize temporary memory
+ * allocation while representing a parsed URL. Internally, it contains a single
+ * normalized URL (the href), and it makes available the components, mostly
+ * using std::string_view.
+ */
+struct url_aggregator : url_base {
+  url_aggregator() = default;
+  url_aggregator(const url_aggregator &u) = default;
+  url_aggregator(url_aggregator &&u) noexcept = default;
+  url_aggregator &operator=(url_aggregator &&u) noexcept = default;
+  url_aggregator &operator=(const url_aggregator &u) = default;
+  ~url_aggregator() override = default;
+
+  bool set_href(std::string_view input);
+  bool set_host(std::string_view input);
+  bool set_hostname(std::string_view input);
+  bool set_protocol(std::string_view input);
+  bool set_username(std::string_view input);
+  bool set_password(std::string_view input);
+  bool set_port(std::string_view input);
+  bool set_pathname(std::string_view input);
+  void set_search(std::string_view input);
+  void set_hash(std::string_view input);
+
+  [[nodiscard]] bool has_valid_domain() const noexcept override;
+  /**
+   * The origin getter steps are to return the serialization of this's URL's
+   * origin. [HTML]
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#concept-url-origin
+   */
+  [[nodiscard]] std::string get_origin() const noexcept override;
+  /**
+   * Return the normalized string.
+   * This function does not allocate memory.
+   * It is highly efficient.
+   * @return a constant reference to the underlying normalized URL.
+   * @see https://url.spec.whatwg.org/#dom-url-href
+   * @see https://url.spec.whatwg.org/#concept-url-serializer
+   */
+  [[nodiscard]] inline std::string_view get_href() const noexcept;
+  /**
+   * The username getter steps are to return this's URL's username.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-username
+   */
+  [[nodiscard]] std::string_view get_username() const noexcept;
+  /**
+   * The password getter steps are to return this's URL's password.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-password
+   */
+  [[nodiscard]] std::string_view get_password() const noexcept;
+  /**
+   * Return this's URL's port, serialized.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-port
+   */
+  [[nodiscard]] std::string_view get_port() const noexcept;
+  /**
+   * Return U+0023 (#), followed by this's URL's fragment.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view..
+   * @see https://url.spec.whatwg.org/#dom-url-hash
+   */
+  [[nodiscard]] std::string_view get_hash() const noexcept;
+  /**
+   * Return url's host, serialized, followed by U+003A (:) and url's port,
+   * serialized.
+   * This function does not allocate memory.
+   * When there is no host, this function returns the empty view.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-host
+   */
+  [[nodiscard]] std::string_view get_host() const noexcept;
+  /**
+   * Return this's URL's host, serialized.
+   * This function does not allocate memory.
+   * When there is no host, this function returns the empty view.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-hostname
+   */
+  [[nodiscard]] std::string_view get_hostname() const noexcept;
+  /**
+   * The pathname getter steps are to return the result of URL path serializing
+   * this's URL.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-pathname
+   */
+  [[nodiscard]] std::string_view get_pathname() const noexcept;
+  /**
+   * Compute the pathname length in bytes without instantiating a view or a
+   * string.
+   * @return size of the pathname in bytes
+   * @see https://url.spec.whatwg.org/#dom-url-pathname
+   */
+  [[nodiscard]] ada_really_inline uint32_t get_pathname_length() const noexcept;
+  /**
+   * Return U+003F (?), followed by this's URL's query.
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-search
+   */
+  [[nodiscard]] std::string_view get_search() const noexcept;
+  /**
+   * The protocol getter steps are to return this's URL's scheme, followed by
+   * U+003A (:).
+   * This function does not allocate memory.
+   * @return a lightweight std::string_view.
+   * @see https://url.spec.whatwg.org/#dom-url-protocol
+   */
+  [[nodiscard]] std::string_view get_protocol() const noexcept;
+
+  /**
+   * A URL includes credentials if its username or password is not the empty
+   * string.
+   */
+  [[nodiscard]] ada_really_inline bool has_credentials() const noexcept;
+
+  /**
+   * Useful for implementing efficient serialization for the URL.
+   *
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   *
+   * Inspired after servo/url
+   *
+   * @return a constant reference to the underlying component attribute.
+   *
+   * @see
+   * https://github.com/servo/rust-url/blob/b65a45515c10713f6d212e6726719a020203cc98/url/src/quirks.rs#L31
+   */
+  [[nodiscard]] ada_really_inline const ada::url_components &get_components()
+      const noexcept;
+  /**
+   * Returns a string representation of this URL.
+   */
+  [[nodiscard]] std::string to_string() const override;
+  /**
+   * Returns a string diagram of this URL.
+   */
+  [[nodiscard]] std::string to_diagram() const;
+
+  /**
+   * Verifies that the parsed URL could be valid. Useful for debugging purposes.
+   * @return true if the URL is valid, otherwise return true of the offsets are
+   * possible.
+   */
+  [[nodiscard]] bool validate() const noexcept;
+
+  /** @return true if it has an host but it is the empty string */
+  [[nodiscard]] inline bool has_empty_hostname() const noexcept;
+  /** @return true if it has a host (included an empty host) */
+  [[nodiscard]] inline bool has_hostname() const noexcept;
+  /** @return true if the URL has a non-empty username */
+  [[nodiscard]] inline bool has_non_empty_username() const noexcept;
+  /** @return true if the URL has a non-empty password */
+  [[nodiscard]] inline bool has_non_empty_password() const noexcept;
+  /** @return true if the URL has a (non default) port */
+  [[nodiscard]] inline bool has_port() const noexcept;
+  /** @return true if the URL has a password */
+  [[nodiscard]] inline bool has_password() const noexcept;
+  /** @return true if the URL has a hash component */
+  [[nodiscard]] inline bool has_hash() const noexcept override;
+  /** @return true if the URL has a search component */
+  [[nodiscard]] inline bool has_search() const noexcept override;
+
+  inline void clear_port();
+  inline void clear_hash();
+  inline void clear_search() override;
+
+ private:
+  friend ada::url_aggregator ada::parser::parse_url<ada::url_aggregator>(
+      std::string_view, const ada::url_aggregator *);
+  friend void ada::helpers::strip_trailing_spaces_from_opaque_path<
+      ada::url_aggregator>(ada::url_aggregator &url) noexcept;
+
+  std::string buffer{};
+  url_components components{};
+
+  /**
+   * Returns true if neither the search, nor the hash nor the pathname
+   * have been set.
+   * @return true if the buffer is ready to receive the path.
+   */
+  [[nodiscard]] ada_really_inline bool is_at_path() const noexcept;
+
+  inline void add_authority_slashes_if_needed() noexcept;
+
+  /**
+   * To optimize performance, you may indicate how much memory to allocate
+   * within this instance.
+   */
+  inline void reserve(uint32_t capacity);
+
+  ada_really_inline size_t parse_port(
+      std::string_view view, bool check_trailing_content) noexcept override;
+
+  ada_really_inline size_t parse_port(std::string_view view) noexcept override {
+    return this->parse_port(view, false);
+  }
+
+  /**
+   * Return true on success. The 'in_place' parameter indicates whether the
+   * the string_view input is pointing in the buffer. When in_place is false,
+   * we must nearly always update the buffer.
+   * @see https://url.spec.whatwg.org/#concept-ipv4-parser
+   */
+  [[nodiscard]] bool parse_ipv4(std::string_view input, bool in_place);
+
+  /**
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#concept-ipv6-parser
+   */
+  [[nodiscard]] bool parse_ipv6(std::string_view input);
+
+  /**
+   * Return true on success.
+   * @see https://url.spec.whatwg.org/#concept-opaque-host-parser
+   */
+  [[nodiscard]] bool parse_opaque_host(std::string_view input);
+
+  ada_really_inline void parse_path(std::string_view input);
+
+  /**
+   * A URL cannot have a username/password/port if its host is null or the empty
+   * string, or its scheme is "file".
+   */
+  [[nodiscard]] inline bool cannot_have_credentials_or_port() const;
+
+  template <bool override_hostname = false>
+  bool set_host_or_hostname(std::string_view input);
+
+  ada_really_inline bool parse_host(std::string_view input);
+
+  inline void update_base_authority(std::string_view base_buffer,
+                                    const ada::url_components &base);
+  inline void update_unencoded_base_hash(std::string_view input);
+  inline void update_base_hostname(std::string_view input);
+  inline void update_base_search(std::string_view input);
+  inline void update_base_search(std::string_view input,
+                                 const uint8_t *query_percent_encode_set);
+  inline void update_base_pathname(std::string_view input);
+  inline void update_base_username(std::string_view input);
+  inline void append_base_username(std::string_view input);
+  inline void update_base_password(std::string_view input);
+  inline void append_base_password(std::string_view input);
+  inline void update_base_port(uint32_t input);
+  inline void append_base_pathname(std::string_view input);
+  [[nodiscard]] inline uint32_t retrieve_base_port() const;
+  inline void clear_hostname();
+  inline void clear_password();
+  inline void clear_pathname() override;
+  [[nodiscard]] inline bool has_dash_dot() const noexcept;
+  void delete_dash_dot();
+  inline void consume_prepared_path(std::string_view input);
+  template <bool has_state_override = false>
+  [[nodiscard]] ada_really_inline bool parse_scheme_with_colon(
+      std::string_view input);
+  ada_really_inline uint32_t replace_and_resize(uint32_t start, uint32_t end,
+                                                std::string_view input);
+  [[nodiscard]] inline bool has_authority() const noexcept;
+  inline void set_protocol_as_file();
+  inline void set_scheme(std::string_view new_scheme) noexcept;
+  /**
+   * Fast function to set the scheme from a view with a colon in the
+   * buffer, does not change type.
+   */
+  inline void set_scheme_from_view_with_colon(
+      std::string_view new_scheme_with_colon) noexcept;
+  inline void copy_scheme(const url_aggregator &u) noexcept;
+
+};  // url_aggregator
+
+inline std::ostream &operator<<(std::ostream &out, const ada::url &u);
+}  // namespace ada
+
+#endif
diff --git a/include/ada/url_base-inl.h b/include/ada/url_base-inl.h
new file mode 100644 (file)
index 0000000..26ba108
--- /dev/null
@@ -0,0 +1,39 @@
+/**
+ * @file url_base-inl.h
+ * @brief Inline functions for url base
+ */
+#ifndef ADA_URL_BASE_INL_H
+#define ADA_URL_BASE_INL_H
+
+#include "ada/url_aggregator.h"
+#include "ada/url_components.h"
+#include "ada/scheme.h"
+#include "ada/scheme-inl.h"
+#include "ada/log.h"
+#include "ada/checkers.h"
+#include "ada/url.h"
+
+#include <optional>
+#include <string>
+#if ADA_REGULAR_VISUAL_STUDIO
+#include <intrin.h>
+#endif  // ADA_REGULAR_VISUAL_STUDIO
+
+namespace ada {
+
+[[nodiscard]] ada_really_inline bool url_base::is_special() const noexcept {
+  return type != ada::scheme::NOT_SPECIAL;
+}
+
+[[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept {
+  return ada::scheme::get_special_port(type);
+}
+
+[[nodiscard]] ada_really_inline uint16_t
+url_base::scheme_default_port() const noexcept {
+  return scheme::get_special_port(type);
+}
+
+}  // namespace ada
+
+#endif  // ADA_URL_BASE_INL_H
diff --git a/include/ada/url_base.h b/include/ada/url_base.h
new file mode 100644 (file)
index 0000000..de4068d
--- /dev/null
@@ -0,0 +1,142 @@
+/**
+ * @file url_base.h
+ * @brief Declaration for the basic URL definitions
+ */
+#ifndef ADA_URL_BASE_H
+#define ADA_URL_BASE_H
+
+#include "ada/common_defs.h"
+#include "ada/url_components.h"
+#include "ada/scheme.h"
+
+#include <string_view>
+
+namespace ada {
+
+/**
+ * Type of URL host as an enum.
+ */
+enum url_host_type : uint8_t {
+  /**
+   * Represents common URLs such as "https://www.google.com"
+   */
+  DEFAULT = 0,
+  /**
+   * Represents ipv4 addresses such as "http://127.0.0.1"
+   */
+  IPV4 = 1,
+  /**
+   * Represents ipv6 addresses such as
+   * "http://[2001:db8:3333:4444:5555:6666:7777:8888]"
+   */
+  IPV6 = 2,
+};
+
+/**
+ * @brief Base class of URL implementations
+ *
+ * @details A url_base contains a few attributes: is_valid, has_opaque_path and
+ * type. All non-trivial implementation details are in derived classes such as
+ * ada::url and ada::url_aggregator.
+ *
+ * It is an abstract class that cannot be instantiated directly.
+ */
+struct url_base {
+  virtual ~url_base() = default;
+
+  /**
+   * Used for returning the validity from the result of the URL parser.
+   */
+  bool is_valid{true};
+
+  /**
+   * A URL has an opaque path if its path is a string.
+   */
+  bool has_opaque_path{false};
+
+  /**
+   * URL hosts type
+   */
+  url_host_type host_type = url_host_type::DEFAULT;
+
+  /**
+   * @private
+   */
+  ada::scheme::type type{ada::scheme::type::NOT_SPECIAL};
+
+  /**
+   * A URL is special if its scheme is a special scheme. A URL is not special if
+   * its scheme is not a special scheme.
+   */
+  [[nodiscard]] ada_really_inline bool is_special() const noexcept;
+
+  /**
+   * The origin getter steps are to return the serialization of this's URL's
+   * origin. [HTML]
+   * @return a newly allocated string.
+   * @see https://url.spec.whatwg.org/#concept-url-origin
+   */
+  [[nodiscard]] virtual std::string get_origin() const noexcept = 0;
+
+  /**
+   * Returns true if this URL has a valid domain as per RFC 1034 and
+   * corresponding specifications. Among other things, it requires
+   * that the domain string has fewer than 255 octets.
+   */
+  [[nodiscard]] virtual bool has_valid_domain() const noexcept = 0;
+
+  /**
+   * @private
+   *
+   * Return the 'special port' if the URL is special and not 'file'.
+   * Returns 0 otherwise.
+   */
+  [[nodiscard]] inline uint16_t get_special_port() const noexcept;
+
+  /**
+   * @private
+   *
+   * Get the default port if the url's scheme has one, returns 0 otherwise.
+   */
+  [[nodiscard]] ada_really_inline uint16_t scheme_default_port() const noexcept;
+
+  /**
+   * @private
+   *
+   * Parse a port (16-bit decimal digit) from the provided input.
+   * We assume that the input does not contain spaces or tabs
+   * within the ASCII digits.
+   * It returns how many bytes were consumed when a number is successfully
+   * parsed.
+   * @return On failure, it returns zero.
+   * @see https://url.spec.whatwg.org/#host-parsing
+   */
+  virtual size_t parse_port(std::string_view view,
+                            bool check_trailing_content) noexcept = 0;
+
+  virtual ada_really_inline size_t parse_port(std::string_view view) noexcept {
+    return this->parse_port(view, false);
+  }
+
+  /**
+   * Returns a JSON string representation of this URL.
+   */
+  [[nodiscard]] virtual std::string to_string() const = 0;
+
+  /** @private */
+  virtual inline void clear_pathname() = 0;
+
+  /** @private */
+  virtual inline void clear_search() = 0;
+
+  /** @private */
+  [[nodiscard]] virtual inline bool has_hash() const noexcept = 0;
+
+  /** @private */
+  [[nodiscard]] virtual inline bool has_search() const noexcept = 0;
+
+};  // url_base
+
+}  // namespace ada
+
+#endif
diff --git a/include/ada/url_components.h b/include/ada/url_components.h
new file mode 100644 (file)
index 0000000..1596c5c
--- /dev/null
@@ -0,0 +1,80 @@
+/**
+ * @file url_components.h
+ * @brief Declaration for the URL Components
+ */
+#ifndef ADA_URL_COMPONENTS_H
+#define ADA_URL_COMPONENTS_H
+
+#include "ada/common_defs.h"
+
+#include <optional>
+#include <string_view>
+
+namespace ada {
+
+/**
+ * @brief URL Component representations using offsets.
+ *
+ * @details We design the url_components struct so that it is as small
+ * and simple as possible. This version uses 32 bytes.
+ *
+ * This struct is used to extract components from a single 'href'.
+ */
+struct url_components {
+  constexpr static uint32_t omitted = uint32_t(-1);
+
+  url_components() = default;
+  url_components(const url_components &u) = default;
+  url_components(url_components &&u) noexcept = default;
+  url_components &operator=(url_components &&u) noexcept = default;
+  url_components &operator=(const url_components &u) = default;
+  ~url_components() = default;
+
+  /*
+   * By using 32-bit integers, we implicitly assume that the URL string
+   * cannot exceed 4 GB.
+   *
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   */
+  uint32_t protocol_end{0};
+  /**
+   * Username end is not `omitted` by default to make username and password
+   * getters less costly to implement.
+   */
+  uint32_t username_end{0};
+  uint32_t host_start{0};
+  uint32_t host_end{0};
+  uint32_t port{omitted};
+  uint32_t pathname_start{0};
+  uint32_t search_start{omitted};
+  uint32_t hash_start{omitted};
+
+  /**
+   * Check the following conditions:
+   * protocol_end < username_end < ... < hash_start,
+   * expect when a value is omitted. It also computes
+   * a lower bound on  the possible string length that may match these
+   * offsets.
+   * @return true if the offset values are
+   *  consistent with a possible URL string
+   */
+  [[nodiscard]] bool check_offset_consistency() const noexcept;
+
+  /**
+   * Converts a url_components to JSON stringified version.
+   */
+  [[nodiscard]] std::string to_string() const;
+
+};  // struct url_components
+
+}  // namespace ada
+#endif
diff --git a/include/ada/url_search_params-inl.h b/include/ada/url_search_params-inl.h
new file mode 100644 (file)
index 0000000..acb1750
--- /dev/null
@@ -0,0 +1,222 @@
+/**
+ * @file url_search_params-inl.h
+ * @brief Inline declarations for the URL Search Params
+ */
+#ifndef ADA_URL_SEARCH_PARAMS_INL_H
+#define ADA_URL_SEARCH_PARAMS_INL_H
+
+#include "ada.h"
+#include "ada/character_sets-inl.h"
+#include "ada/unicode.h"
+#include "ada/url_search_params.h"
+
+#include <algorithm>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace ada {
+
+// A default, empty url_search_params for use with empty iterators.
+template <typename T, ada::url_search_params_iter_type Type>
+url_search_params url_search_params_iter<T, Type>::EMPTY;
+
+inline void url_search_params::initialize(std::string_view input) {
+  if (!input.empty() && input.front() == '?') {
+    input.remove_prefix(1);
+  }
+
+  auto process_key_value = [&](const std::string_view current) {
+    auto equal = current.find('=');
+
+    if (equal == std::string_view::npos) {
+      std::string name(current);
+      std::replace(name.begin(), name.end(), '+', ' ');
+      params.emplace_back(unicode::percent_decode(name, name.find('%')), "");
+    } else {
+      std::string name(current.substr(0, equal));
+      std::string value(current.substr(equal + 1));
+
+      std::replace(name.begin(), name.end(), '+', ' ');
+      std::replace(value.begin(), value.end(), '+', ' ');
+
+      params.emplace_back(unicode::percent_decode(name, name.find('%')),
+                          unicode::percent_decode(value, value.find('%')));
+    }
+  };
+
+  while (!input.empty()) {
+    auto ampersand_index = input.find('&');
+
+    if (ampersand_index == std::string_view::npos) {
+      if (!input.empty()) {
+        process_key_value(input);
+      }
+      break;
+    } else if (ampersand_index != 0) {
+      process_key_value(input.substr(0, ampersand_index));
+    }
+
+    input.remove_prefix(ampersand_index + 1);
+  }
+}
+
+inline void url_search_params::append(const std::string_view key,
+                                      const std::string_view value) {
+  params.emplace_back(key, value);
+}
+
+inline size_t url_search_params::size() const noexcept { return params.size(); }
+
+inline std::optional<std::string_view> url_search_params::get(
+    const std::string_view key) {
+  auto entry = std::find_if(params.begin(), params.end(),
+                            [&key](auto &param) { return param.first == key; });
+
+  if (entry == params.end()) {
+    return std::nullopt;
+  }
+
+  return entry->second;
+}
+
+inline std::vector<std::string> url_search_params::get_all(
+    const std::string_view key) {
+  std::vector<std::string> out{};
+
+  for (auto &param : params) {
+    if (param.first == key) {
+      out.emplace_back(param.second);
+    }
+  }
+
+  return out;
+}
+
+inline bool url_search_params::has(const std::string_view key) noexcept {
+  auto entry = std::find_if(params.begin(), params.end(),
+                            [&key](auto &param) { return param.first == key; });
+  return entry != params.end();
+}
+
+inline bool url_search_params::has(std::string_view key,
+                                   std::string_view value) noexcept {
+  auto entry =
+      std::find_if(params.begin(), params.end(), [&key, &value](auto &param) {
+        return param.first == key && param.second == value;
+      });
+  return entry != params.end();
+}
+
+inline std::string url_search_params::to_string() const {
+  auto character_set = ada::character_sets::WWW_FORM_URLENCODED_PERCENT_ENCODE;
+  std::string out{};
+  for (size_t i = 0; i < params.size(); i++) {
+    auto key = ada::unicode::percent_encode(params[i].first, character_set);
+    auto value = ada::unicode::percent_encode(params[i].second, character_set);
+
+    // Performance optimization: Move this inside percent_encode.
+    std::replace(key.begin(), key.end(), ' ', '+');
+    std::replace(value.begin(), value.end(), ' ', '+');
+
+    if (i != 0) {
+      out += "&";
+    }
+    out.append(key);
+    out += "=";
+    out.append(value);
+  }
+  return out;
+}
+
+inline void url_search_params::set(const std::string_view key,
+                                   const std::string_view value) {
+  const auto find = [&key](auto &param) { return param.first == key; };
+
+  auto it = std::find_if(params.begin(), params.end(), find);
+
+  if (it == params.end()) {
+    params.emplace_back(key, value);
+  } else {
+    it->second = value;
+    params.erase(std::remove_if(std::next(it), params.end(), find),
+                 params.end());
+  }
+}
+
+inline void url_search_params::remove(const std::string_view key) {
+  params.erase(
+      std::remove_if(params.begin(), params.end(),
+                     [&key](auto &param) { return param.first == key; }),
+      params.end());
+}
+
+inline void url_search_params::remove(const std::string_view key,
+                                      const std::string_view value) {
+  params.erase(std::remove_if(params.begin(), params.end(),
+                              [&key, &value](auto &param) {
+                                return param.first == key &&
+                                       param.second == value;
+                              }),
+               params.end());
+}
+
+inline void url_search_params::sort() {
+  std::stable_sort(params.begin(), params.end(),
+                   [](const key_value_pair &lhs, const key_value_pair &rhs) {
+                     return lhs.first < rhs.first;
+                   });
+}
+
+inline url_search_params_keys_iter url_search_params::get_keys() {
+  return url_search_params_keys_iter(*this);
+}
+
+/**
+ * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+ */
+inline url_search_params_values_iter url_search_params::get_values() {
+  return url_search_params_values_iter(*this);
+}
+
+/**
+ * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+ */
+inline url_search_params_entries_iter url_search_params::get_entries() {
+  return url_search_params_entries_iter(*this);
+}
+
+template <typename T, url_search_params_iter_type Type>
+inline bool url_search_params_iter<T, Type>::has_next() {
+  return pos < params.params.size();
+}
+
+template <>
+inline std::optional<std::string_view> url_search_params_keys_iter::next() {
+  if (!has_next()) {
+    return std::nullopt;
+  }
+  return params.params[pos++].first;
+}
+
+template <>
+inline std::optional<std::string_view> url_search_params_values_iter::next() {
+  if (!has_next()) {
+    return std::nullopt;
+  }
+  return params.params[pos++].second;
+}
+
+template <>
+inline std::optional<key_value_view_pair>
+url_search_params_entries_iter::next() {
+  if (!has_next()) {
+    return std::nullopt;
+  }
+  return params.params[pos++];
+}
+
+}  // namespace ada
+
+#endif  // ADA_URL_SEARCH_PARAMS_INL_H
diff --git a/include/ada/url_search_params.h b/include/ada/url_search_params.h
new file mode 100644 (file)
index 0000000..b9397ad
--- /dev/null
@@ -0,0 +1,180 @@
+/**
+ * @file url_search_params.h
+ * @brief Declaration for the URL Search Params
+ */
+#ifndef ADA_URL_SEARCH_PARAMS_H
+#define ADA_URL_SEARCH_PARAMS_H
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace ada {
+
+enum class url_search_params_iter_type {
+  KEYS,
+  VALUES,
+  ENTRIES,
+};
+
+template <typename T, url_search_params_iter_type Type>
+struct url_search_params_iter;
+
+typedef std::pair<std::string_view, std::string_view> key_value_view_pair;
+
+using url_search_params_keys_iter =
+    url_search_params_iter<std::string_view, url_search_params_iter_type::KEYS>;
+using url_search_params_values_iter =
+    url_search_params_iter<std::string_view,
+                           url_search_params_iter_type::VALUES>;
+using url_search_params_entries_iter =
+    url_search_params_iter<key_value_view_pair,
+                           url_search_params_iter_type::ENTRIES>;
+
+/**
+ * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+ */
+struct url_search_params {
+  url_search_params() = default;
+
+  /**
+   * @see
+   * https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js
+   */
+  url_search_params(const std::string_view input) { initialize(input); }
+
+  url_search_params(const url_search_params &u) = default;
+  url_search_params(url_search_params &&u) noexcept = default;
+  url_search_params &operator=(url_search_params &&u) noexcept = default;
+  url_search_params &operator=(const url_search_params &u) = default;
+  ~url_search_params() = default;
+
+  [[nodiscard]] inline size_t size() const noexcept;
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append
+   */
+  inline void append(std::string_view key, std::string_view value);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-delete
+   */
+  inline void remove(std::string_view key);
+  inline void remove(std::string_view key, std::string_view value);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-get
+   */
+  inline std::optional<std::string_view> get(std::string_view key);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-getall
+   */
+  inline std::vector<std::string> get_all(std::string_view key);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-has
+   */
+  inline bool has(std::string_view key) noexcept;
+  inline bool has(std::string_view key, std::string_view value) noexcept;
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set
+   */
+  inline void set(std::string_view key, std::string_view value);
+
+  /**
+   * @see https://url.spec.whatwg.org/#dom-urlsearchparams-sort
+   */
+  inline void sort();
+
+  /**
+   * @see https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior
+   */
+  inline std::string to_string() const;
+
+  /**
+   * Returns a simple JS-style iterator over all of the keys in this
+   * url_search_params. The keys in the iterator are not unique. The valid
+   * lifespan of the iterator is tied to the url_search_params. The iterator
+   * must be freed when you're done with it.
+   * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+   */
+  inline url_search_params_keys_iter get_keys();
+
+  /**
+   * Returns a simple JS-style iterator over all of the values in this
+   * url_search_params. The valid lifespan of the iterator is tied to the
+   * url_search_params. The iterator must be freed when you're done with it.
+   * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+   */
+  inline url_search_params_values_iter get_values();
+
+  /**
+   * Returns a simple JS-style iterator over all of the entries in this
+   * url_search_params. The entries are pairs of keys and corresponding values.
+   * The valid lifespan of the iterator is tied to the url_search_params. The
+   * iterator must be freed when you're done with it.
+   * @see https://url.spec.whatwg.org/#interface-urlsearchparams
+   */
+  inline url_search_params_entries_iter get_entries();
+
+  /**
+   * C++ style conventional iterator support. const only because we
+   * do not really want the params to be modified via the iterator.
+   */
+  inline auto begin() const { return params.begin(); }
+  inline auto end() const { return params.end(); }
+  inline auto front() const { return params.front(); }
+  inline auto back() const { return params.back(); }
+  inline auto operator[](size_t index) const { return params[index]; }
+
+ private:
+  typedef std::pair<std::string, std::string> key_value_pair;
+  std::vector<key_value_pair> params{};
+
+  /**
+   * @see https://url.spec.whatwg.org/#concept-urlencoded-parser
+   */
+  void initialize(std::string_view init);
+
+  template <typename T, url_search_params_iter_type Type>
+  friend struct url_search_params_iter;
+};  // url_search_params
+
+/**
+ * Implements a non-conventional iterator pattern that is closer in style to
+ * JavaScript's definition of an iterator.
+ *
+ * @see https://webidl.spec.whatwg.org/#idl-iterable
+ */
+template <typename T, url_search_params_iter_type Type>
+struct url_search_params_iter {
+  inline url_search_params_iter() : params(EMPTY) {}
+  url_search_params_iter(const url_search_params_iter &u) = default;
+  url_search_params_iter(url_search_params_iter &&u) noexcept = default;
+  url_search_params_iter &operator=(url_search_params_iter &&u) noexcept =
+      default;
+  url_search_params_iter &operator=(const url_search_params_iter &u) = default;
+  ~url_search_params_iter() = default;
+
+  /**
+   * Return the next item in the iterator or std::nullopt if done.
+   */
+  inline std::optional<T> next();
+
+  inline bool has_next();
+
+ private:
+  static url_search_params EMPTY;
+  inline url_search_params_iter(url_search_params &params_) : params(params_) {}
+
+  url_search_params &params;
+  size_t pos = 0;
+
+  friend struct url_search_params;
+};
+
+}  // namespace ada
+#endif
diff --git a/include/ada_c.h b/include/ada_c.h
new file mode 100644 (file)
index 0000000..173e27b
--- /dev/null
@@ -0,0 +1,185 @@
+/**
+ * @file ada_c.h
+ * @brief Includes the C definitions for Ada. This is a C file, not C++.
+ */
+#ifndef ADA_C_H
+#define ADA_C_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+// This is a reference to ada::url_components::omitted
+// It represents "uint32_t(-1)"
+#define ada_url_omitted 0xffffffff
+
+// string that is owned by the ada_url instance
+typedef struct {
+  const char* data;
+  size_t length;
+} ada_string;
+
+// string that must be freed by the caller
+typedef struct {
+  const char* data;
+  size_t length;
+} ada_owned_string;
+
+typedef struct {
+  uint32_t protocol_end;
+  uint32_t username_end;
+  uint32_t host_start;
+  uint32_t host_end;
+  uint32_t port;
+  uint32_t pathname_start;
+  uint32_t search_start;
+  uint32_t hash_start;
+} ada_url_components;
+
+typedef void* ada_url;
+
+// input should be a null terminated C string (ASCII or UTF-8)
+// you must call ada_free on the returned pointer
+ada_url ada_parse(const char* input, size_t length);
+ada_url ada_parse_with_base(const char* input, size_t input_length,
+                            const char* base, size_t base_length);
+
+// input and base should be a null terminated C strings
+bool ada_can_parse(const char* input, size_t length);
+bool ada_can_parse_with_base(const char* input, size_t input_length,
+                             const char* base, size_t base_length);
+
+void ada_free(ada_url result);
+void ada_free_owned_string(ada_owned_string owned);
+ada_url ada_copy(ada_url input);
+
+bool ada_is_valid(ada_url result);
+
+// url_aggregator getters
+// if ada_is_valid(result)) is false, an empty string is returned
+ada_owned_string ada_get_origin(ada_url result);
+ada_string ada_get_href(ada_url result);
+ada_string ada_get_username(ada_url result);
+ada_string ada_get_password(ada_url result);
+ada_string ada_get_port(ada_url result);
+ada_string ada_get_hash(ada_url result);
+ada_string ada_get_host(ada_url result);
+ada_string ada_get_hostname(ada_url result);
+ada_string ada_get_pathname(ada_url result);
+ada_string ada_get_search(ada_url result);
+ada_string ada_get_protocol(ada_url result);
+uint8_t ada_get_host_type(ada_url result);
+uint8_t ada_get_scheme_type(ada_url result);
+
+// url_aggregator setters
+// if ada_is_valid(result)) is false, the setters have no effect
+// input should be a null terminated C string
+bool ada_set_href(ada_url result, const char* input, size_t length);
+bool ada_set_host(ada_url result, const char* input, size_t length);
+bool ada_set_hostname(ada_url result, const char* input, size_t length);
+bool ada_set_protocol(ada_url result, const char* input, size_t length);
+bool ada_set_username(ada_url result, const char* input, size_t length);
+bool ada_set_password(ada_url result, const char* input, size_t length);
+bool ada_set_port(ada_url result, const char* input, size_t length);
+bool ada_set_pathname(ada_url result, const char* input, size_t length);
+void ada_set_search(ada_url result, const char* input, size_t length);
+void ada_set_hash(ada_url result, const char* input, size_t length);
+
+// url_aggregator clear methods
+void ada_clear_port(ada_url result);
+void ada_clear_hash(ada_url result);
+void ada_clear_search(ada_url result);
+
+// url_aggregator functions
+// if ada_is_valid(result) is false, functions below will return false
+bool ada_has_credentials(ada_url result);
+bool ada_has_empty_hostname(ada_url result);
+bool ada_has_hostname(ada_url result);
+bool ada_has_non_empty_username(ada_url result);
+bool ada_has_non_empty_password(ada_url result);
+bool ada_has_port(ada_url result);
+bool ada_has_password(ada_url result);
+bool ada_has_hash(ada_url result);
+bool ada_has_search(ada_url result);
+
+// returns a pointer to the internal url_aggregator::url_components
+const ada_url_components* ada_get_components(ada_url result);
+
+// idna methods
+ada_owned_string ada_idna_to_unicode(const char* input, size_t length);
+ada_owned_string ada_idna_to_ascii(const char* input, size_t length);
+
+// url search params
+typedef void* ada_url_search_params;
+
+// Represents an std::vector<std::string>
+typedef void* ada_strings;
+typedef void* ada_url_search_params_keys_iter;
+typedef void* ada_url_search_params_values_iter;
+
+typedef struct {
+  ada_string key;
+  ada_string value;
+} ada_string_pair;
+
+typedef void* ada_url_search_params_entries_iter;
+
+ada_url_search_params ada_parse_search_params(const char* input, size_t length);
+void ada_free_search_params(ada_url_search_params result);
+
+size_t ada_search_params_size(ada_url_search_params result);
+void ada_search_params_sort(ada_url_search_params result);
+ada_owned_string ada_search_params_to_string(ada_url_search_params result);
+
+void ada_search_params_append(ada_url_search_params result, const char* key,
+                              size_t key_length, const char* value,
+                              size_t value_length);
+void ada_search_params_set(ada_url_search_params result, const char* key,
+                           size_t key_length, const char* value,
+                           size_t value_length);
+void ada_search_params_remove(ada_url_search_params result, const char* key,
+                              size_t key_length);
+void ada_search_params_remove_value(ada_url_search_params result,
+                                    const char* key, size_t key_length,
+                                    const char* value, size_t value_length);
+bool ada_search_params_has(ada_url_search_params result, const char* key,
+                           size_t key_length);
+bool ada_search_params_has_value(ada_url_search_params result, const char* key,
+                                 size_t key_length, const char* value,
+                                 size_t value_length);
+ada_string ada_search_params_get(ada_url_search_params result, const char* key,
+                                 size_t key_length);
+ada_strings ada_search_params_get_all(ada_url_search_params result,
+                                      const char* key, size_t key_length);
+ada_url_search_params_keys_iter ada_search_params_get_keys(
+    ada_url_search_params result);
+ada_url_search_params_values_iter ada_search_params_get_values(
+    ada_url_search_params result);
+ada_url_search_params_entries_iter ada_search_params_get_entries(
+    ada_url_search_params result);
+
+void ada_free_strings(ada_strings result);
+size_t ada_strings_size(ada_strings result);
+ada_string ada_strings_get(ada_strings result, size_t index);
+
+void ada_free_search_params_keys_iter(ada_url_search_params_keys_iter result);
+ada_string ada_search_params_keys_iter_next(
+    ada_url_search_params_keys_iter result);
+bool ada_search_params_keys_iter_has_next(
+    ada_url_search_params_keys_iter result);
+
+void ada_free_search_params_values_iter(
+    ada_url_search_params_values_iter result);
+ada_string ada_search_params_values_iter_next(
+    ada_url_search_params_values_iter result);
+bool ada_search_params_values_iter_has_next(
+    ada_url_search_params_values_iter result);
+
+void ada_free_search_params_entries_iter(
+    ada_url_search_params_entries_iter result);
+ada_string_pair ada_search_params_entries_iter_next(
+    ada_url_search_params_entries_iter result);
+bool ada_search_params_entries_iter_has_next(
+    ada_url_search_params_entries_iter result);
+
+#endif  // ADA_C_H
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..67cb6f2
--- /dev/null
@@ -0,0 +1,29 @@
+[tool.ruff]
+select = [
+  "C90",    # McCabe cyclomatic complexity
+  "E",      # pycodestyle
+  "F",      # Pyflakes
+  "ICN",    # flake8-import-conventions
+  "INT",    # flake8-gettext
+  "PLC",    # Pylint conventions
+  "PLE",    # Pylint errors
+  "PLR09",  # Pylint refactoring: max-args, max-branches, max returns, max-statements
+  "PYI",    # flake8-pyi
+  "RSE",    # flake8-raise
+  "RUF",    # Ruff-specific rules
+  "T10",    # flake8-debugger
+  "TCH",    # flake8-type-checking
+  "TID",    # flake8-tidy-imports
+  "W",      # pycodestyle
+  "YTT",    # flake8-2020
+  "ANN"     # flake8-annotations
+]
+exclude = [
+  "docs",
+  "tests",
+]
+ignore = [
+  "E722"   # Do not use bare `except`
+]
+line-length = 120
+target-version = "py37"
diff --git a/singleheader/CMakeLists.txt b/singleheader/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9dc62a4
--- /dev/null
@@ -0,0 +1,66 @@
+#
+# Amalgamation
+#
+set(SINGLEHEADER_FILES
+  ${CMAKE_CURRENT_BINARY_DIR}/ada.cpp
+  ${CMAKE_CURRENT_BINARY_DIR}/ada.h
+  ${CMAKE_CURRENT_BINARY_DIR}/ada_c.h
+  ${CMAKE_CURRENT_BINARY_DIR}/demo.cpp
+  ${CMAKE_CURRENT_BINARY_DIR}/demo.c
+  ${CMAKE_CURRENT_BINARY_DIR}/README.md
+)
+set_source_files_properties(${SINGLEHEADER_FILES} PROPERTIES GENERATED TRUE)
+
+# In theory, this is unneeded, because the tests module does the same test:
+find_package (Python3 COMPONENTS Interpreter)
+
+if (Python3_Interpreter_FOUND)
+  MESSAGE( STATUS "Python found, we are going to amalgamate.py." )
+
+  add_custom_command(
+    OUTPUT ${SINGLEHEADER_FILES}
+    COMMAND ${CMAKE_COMMAND} -E env
+      AMALGAMATE_SOURCE_PATH=${PROJECT_SOURCE_DIR}/src
+      AMALGAMATE_INPUT_PATH=${PROJECT_SOURCE_DIR}/include
+      AMALGAMATE_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
+      ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/amalgamate.py
+      #
+      # This is the best way I could find to make amalgamation trigger whenever source files or
+      # header files change: since the "ada" library has to get rebuilt when that happens, we
+      # take a dependency on the generated library file (even though we're not using it). Depending
+      # on ada-source doesn't do the trick because DEPENDS here can only depend on an
+      # *artifact*--it won't scan source and include files the way a concrete library or executable
+      # will.
+      #
+      # It sucks that we have to build the actual library to make it happen, but it's better than\
+      # nothing!
+      #
+      DEPENDS amalgamate.py ada
+  )
+  add_custom_target(ada-singleheader-files DEPENDS ${SINGLEHEADER_FILES})
+
+  #
+  # Include this if you intend to #include "ada.cpp" in your own .cpp files.
+  #
+  add_library(ada-singleheader-include-source INTERFACE)
+  target_include_directories(ada-singleheader-include-source INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
+  add_dependencies(ada-singleheader-include-source ada-singleheader-files)
+
+  add_library(ada-singleheader-source INTERFACE)
+  target_sources(ada-singleheader-source INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ada.cpp>)
+  target_link_libraries(ada-singleheader-source INTERFACE ada-singleheader-include-source)
+  add_library(ada-singleheader-lib STATIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ada.cpp>)
+
+  if (ADA_TESTING)
+    add_executable(demo $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/demo.cpp>)
+    target_link_libraries(demo ada-singleheader-include-source)
+
+    add_test(demo demo)
+
+    add_executable(cdemo $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/demo.c>)
+    target_link_libraries(cdemo ada-singleheader-lib)
+    add_test(cdemo cdemo)
+  endif()
+else()
+  MESSAGE( STATUS "Python not found, we are unable to test amalgamate.py." )
+endif()
diff --git a/singleheader/README.md b/singleheader/README.md
new file mode 100644 (file)
index 0000000..6bb7277
--- /dev/null
@@ -0,0 +1,36 @@
+## Amalgamation demo
+
+While in the ada main directory, using Python 3, type:
+
+```
+python singleheader/amalgamate.py
+```
+
+This will create two new files (ada.h and ada.cpp).
+
+You can then compile the demo file as follows:
+
+```
+c++ -std=c++17 -c demo.cpp
+```
+
+It will produce a binary file (e.g., demo.o) which contains ada.cpp.
+
+```
+c++ -std=c++17 -o demo demo.cpp
+./demo
+```
+
+You may build and link using CMake (--target demo), because CMake can configure all the necessary flags.
+
+
+### C Demo
+
+You may also build a C executable.
+
+```
+c++ -c ada.cpp -std=c++17
+cc -c demo.c
+c++ demo.o ada.o -o cdemo
+./cdemo
+```
diff --git a/singleheader/amalgamate.py b/singleheader/amalgamate.py
new file mode 100755 (executable)
index 0000000..91069f1
--- /dev/null
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+#
+# Creates the amalgamated source files.
+#
+
+import zipfile
+import sys
+import os.path
+import subprocess
+import os
+import re
+import shutil
+import datetime
+if sys.version_info[0] < 3:
+    sys.stdout.write("Sorry, requires Python 3.x or better\n")
+    sys.exit(1)
+
+SCRIPTPATH = os.path.dirname(os.path.abspath(sys.argv[0]))
+PROJECTPATH = os.path.dirname(SCRIPTPATH)
+print(f"SCRIPTPATH={SCRIPTPATH} PROJECTPATH={PROJECTPATH}")
+
+if "AMALGAMATE_SOURCE_PATH" not in os.environ:
+    AMALGAMATE_SOURCE_PATH = os.path.join(PROJECTPATH, "src")
+else:
+    AMALGAMATE_SOURCE_PATH = os.environ["AMALGAMATE_SOURCE_PATH"]
+if "AMALGAMATE_INCLUDE_PATH" not in os.environ:
+    AMALGAMATE_INCLUDE_PATH = os.path.join(PROJECTPATH, "include")
+else:
+    AMALGAMATE_INCLUDE_PATH = os.environ["AMALGAMATE_INCLUDE_PATH"]
+if "AMALGAMATE_OUTPUT_PATH" not in os.environ:
+    AMALGAMATE_OUTPUT_PATH = os.path.join(SCRIPTPATH)
+else:
+    AMALGAMATE_OUTPUT_PATH = os.environ["AMALGAMATE_OUTPUT_PATH"]
+
+# this list excludes the "src/generic headers"
+ALLCFILES = ["ada.cpp"]
+
+# order matters
+ALLCHEADERS = ["ada.h"]
+
+found_includes = []
+
+current_implementation=''
+
+def doinclude(fid: str, file: str, line: str, origin: str) -> None:
+
+    p = os.path.join(AMALGAMATE_INCLUDE_PATH, file)
+    pi = os.path.join(AMALGAMATE_SOURCE_PATH, file)
+
+    if os.path.exists(p):
+        if file not in found_includes:
+            found_includes.append(file)
+            dofile(fid, AMALGAMATE_INCLUDE_PATH, file)
+        else:
+            pass
+    elif os.path.exists(pi):
+        if file not in found_includes:
+            found_includes.append(file)
+            dofile(fid, AMALGAMATE_SOURCE_PATH, file)
+        else:
+            pass
+    else:
+        # If we don't recognize it, just emit the #include
+        print("unrecognized:", file, " from ", line, " in ", origin)
+        print(line, file=fid)
+
+def dofile(fid: str, prepath: str, filename: str) -> None:
+    file = os.path.join(prepath, filename)
+    RELFILE = os.path.relpath(file, PROJECTPATH)
+    # Last lines are always ignored. Files should end by an empty lines.
+    print(f"/* begin file {RELFILE} */", file=fid)
+    includepattern = re.compile('\\s*#\\s*include "(.*)"')
+    with open(file, 'r') as fid2:
+        for line in fid2:
+            line = line.rstrip('\n')
+            s = includepattern.search(line)
+            if s:
+                includedfile = s.group(1)
+                if includedfile == "ada.h" and filename == "ada.cpp":
+                    print(line, file=fid)
+                    continue
+
+                if includedfile.startswith('../'):
+                    includedfile = includedfile[2:]
+                # we explicitly include ada headers, one time each
+                doinclude(fid, includedfile, line, filename)
+            else:
+                print(line, file=fid)
+    print(f"/* end file {RELFILE} */", file=fid)
+
+
+# Get the generation date from git, so the output is reproducible.
+# The %ci specifier gives the unambiguous ISO 8601 format, and
+# does not change with locale and timezone at time of generation.
+# Forcing it to be UTC is difficult, because it needs to be portable
+# between gnu date and busybox date.
+try:
+    timestamp = subprocess.run(['git', 'show', '-s', '--format=%ci', 'HEAD'],
+                           stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
+except:
+    print("git not found, timestamp based on current time")
+    timestamp = str(datetime.datetime.now())
+print(f"timestamp is {timestamp}")
+
+os.makedirs(AMALGAMATE_OUTPUT_PATH, exist_ok=True)
+AMAL_H = os.path.join(AMALGAMATE_OUTPUT_PATH, "ada.h")
+AMAL_C = os.path.join(AMALGAMATE_OUTPUT_PATH, "ada.cpp")
+DEMOCPP = os.path.join(AMALGAMATE_OUTPUT_PATH, "cpp")
+README = os.path.join(AMALGAMATE_OUTPUT_PATH, "README.md")
+
+print(f"Creating {AMAL_H}")
+amal_h = open(AMAL_H, 'w')
+print(f"/* auto-generated on {timestamp}. Do not edit! */", file=amal_h)
+for h in ALLCHEADERS:
+    doinclude(amal_h, h, f"ERROR {h} not found", h)
+
+amal_h.close()
+print()
+print()
+print(f"Creating {AMAL_C}")
+amal_c = open(AMAL_C, 'w')
+print(f"/* auto-generated on {timestamp}. Do not edit! */", file=amal_c)
+for c in ALLCFILES:
+    doinclude(amal_c, c, f"ERROR {c} not found", c)
+
+amal_c.close()
+
+# copy the README and DEMOCPP
+if SCRIPTPATH != AMALGAMATE_OUTPUT_PATH:
+  shutil.copy2(os.path.join(SCRIPTPATH,"demo.cpp"),AMALGAMATE_OUTPUT_PATH)
+  shutil.copy2(os.path.join(SCRIPTPATH,"demo.c"),AMALGAMATE_OUTPUT_PATH)
+  shutil.copy2(os.path.join(SCRIPTPATH,"README.md"),AMALGAMATE_OUTPUT_PATH)
+
+shutil.copy2(os.path.join(AMALGAMATE_INCLUDE_PATH,"ada_c.h"),AMALGAMATE_OUTPUT_PATH)
+
+zf = zipfile.ZipFile(os.path.join(AMALGAMATE_OUTPUT_PATH,'singleheader.zip'), 'w', zipfile.ZIP_DEFLATED)
+zf.write(os.path.join(AMALGAMATE_OUTPUT_PATH,"ada.cpp"), "ada.cpp")
+zf.write(os.path.join(AMALGAMATE_OUTPUT_PATH,"ada.h"), "ada.h")
+zf.write(os.path.join(AMALGAMATE_INCLUDE_PATH,"ada_c.h"), "ada_c.h")
+
+
+print("Done with all files generation.")
+
+print(f"Files have been written to directory: {AMALGAMATE_OUTPUT_PATH}/")
+print("Done with all files generation.")
+
diff --git a/singleheader/demo.c b/singleheader/demo.c
new file mode 100644 (file)
index 0000000..3a2c29e
--- /dev/null
@@ -0,0 +1,41 @@
+#include "ada_c.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+static void ada_print(ada_string string) {
+  printf("%.*s\n", (int)string.length, string.data);
+}
+
+int main(int c, char* arg[]) {
+  const char* input =
+      "https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists";
+  ada_url url = ada_parse(input, strlen(input));
+  if (!ada_is_valid(url)) {
+    puts("failure");
+    return EXIT_FAILURE;
+  }
+  ada_print(ada_get_href(
+      url));  // prints
+              // https://username:password@host:8080/pathname?query=true#hash-exists
+  ada_print(ada_get_protocol(url));  // prints https:
+  ada_print(ada_get_username(url));  // prints username
+  ada_set_href(url, "https://www.yagiz.co", strlen("https://www.yagiz.co"));
+  if (!ada_is_valid(url)) {
+    puts("failure");
+    return EXIT_FAILURE;
+  }
+  ada_set_hash(url, "new-hash", strlen("new-hash"));
+  ada_set_hostname(url, "new-host", strlen("new-host"));
+  ada_set_host(url, "changed-host:9090", strlen("changed-host:9090"));
+  ada_set_pathname(url, "new-pathname", strlen("new-pathname"));
+  ada_set_search(url, "new-search", strlen("new-search"));
+  ada_set_protocol(url, "wss", 3);
+  ada_print(ada_get_href(
+      url));  // will print
+              // wss://changed-host:9090/new-pathname?new-search#new-hash
+  ada_free(url);
+  return EXIT_SUCCESS;
+}
diff --git a/singleheader/demo.cpp b/singleheader/demo.cpp
new file mode 100644 (file)
index 0000000..c70fa75
--- /dev/null
@@ -0,0 +1,15 @@
+#include "ada.cpp"
+#include "ada.h"
+#include <iostream>
+
+int main(int, char *[]) {
+  auto url = ada::parse<ada::url>("https://www.google.com");
+  if (!url) {
+    std::cout << "failure" << std::endl;
+    return EXIT_FAILURE;
+  }
+  url->set_protocol("http");
+  std::cout << url->get_protocol() << std::endl;
+  std::cout << url->get_host() << std::endl;
+  return EXIT_SUCCESS;
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..4991407
--- /dev/null
@@ -0,0 +1,56 @@
+
+message(STATUS "Compiler ID : " ${CMAKE_CXX_COMPILER_ID})
+message(STATUS "CMAKE_BUILD_TYPE : " ${CMAKE_BUILD_TYPE})
+
+add_library(ada-include-source INTERFACE)
+target_include_directories(ada-include-source INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+add_library(ada-source INTERFACE)
+target_sources(ada-source INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/ada.cpp)
+target_link_libraries(ada-source INTERFACE ada-include-source)
+add_library(ada ada.cpp)
+target_include_directories(ada PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> )
+target_include_directories(ada PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+
+if(MSVC)
+  if("${MSVC_TOOLSET_VERSION}" STREQUAL "140")
+    target_compile_options(ada INTERFACE /W0 /sdl)
+    set(ADA_LEGACY_VISUAL_STUDIO TRUE)
+  else()
+    target_compile_options(ada PRIVATE /WX /W3 /sdl /w34714) # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4714?view=vs-2019
+  endif()
+else(MSVC)
+  if(NOT WIN32)
+    target_compile_options(ada INTERFACE -fPIC)
+  endif()
+  message(STATUS "Assuming GCC-like compiler.")
+  target_compile_options(ada PRIVATE -Wall -Wextra -Weffc++)
+  if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    target_compile_options(ada PRIVATE -Wsuggest-override)
+  endif()
+  if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))
+    target_compile_options(ada PRIVATE -Winconsistent-missing-override)
+  endif()
+  target_compile_options(ada PRIVATE -Wfatal-errors -Wsign-compare -Wshadow -Wwrite-strings -Wpointer-arith -Winit-self -Wconversion -Wno-sign-conversion)
+endif(MSVC)
+
+# workaround for GNU GCC poor AVX load/store code generation
+if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(i.86|x86(_64)?)$"))
+  target_compile_options(ada PRIVATE -mno-avx256-split-unaligned-load -mno-avx256-split-unaligned-store)
+endif()
+if(ADA_DEVELOPMENT_CHECKS)
+  target_compile_definitions(ada PUBLIC ADA_DEVELOPMENT_CHECKS=1)
+endif()
+if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND (CMAKE_BUILD_TYPE STREQUAL "Debug"))
+  message(STATUS "Enabling _GLIBCXX_DEBUG")
+  target_compile_definitions(ada PRIVATE _GLIBCXX_DEBUG=1)
+endif()
+
+if(ADA_SANITIZE)
+  target_compile_options(ada INTERFACE -fsanitize=address  -fno-omit-frame-pointer -fno-sanitize-recover=all)
+  target_compile_definitions(ada INTERFACE ASAN_OPTIONS=detect_leaks=1)
+  target_link_libraries(ada INTERFACE -fsanitize=address  -fno-omit-frame-pointer -fno-sanitize-recover=all)
+endif()
+
+if(ADA_LOGGING)
+  target_compile_definitions(ada PRIVATE ADA_LOGGING=1)
+endif()
\ No newline at end of file
diff --git a/src/ada.cpp b/src/ada.cpp
new file mode 100644 (file)
index 0000000..164f37d
--- /dev/null
@@ -0,0 +1,13 @@
+#include "ada.h"
+#include "checkers.cpp"
+#include "unicode.cpp"
+#include "serializers.cpp"
+#include "implementation.cpp"
+#include "helpers.cpp"
+#include "url.cpp"
+#include "url-getters.cpp"
+#include "url-setters.cpp"
+#include "parser.cpp"
+#include "url_components.cpp"
+#include "url_aggregator.cpp"
+#include "ada_c.cpp"
\ No newline at end of file
diff --git a/src/ada_c.cpp b/src/ada_c.cpp
new file mode 100644 (file)
index 0000000..ae41f1b
--- /dev/null
@@ -0,0 +1,750 @@
+#include "ada.h"
+
+ada::result<ada::url_aggregator>& get_instance(void* result) noexcept {
+  return *(ada::result<ada::url_aggregator>*)result;
+}
+
+extern "C" {
+typedef void* ada_url;
+typedef void* ada_url_search_params;
+typedef void* ada_strings;
+typedef void* ada_url_search_params_keys_iter;
+typedef void* ada_url_search_params_values_iter;
+typedef void* ada_url_search_params_entries_iter;
+
+struct ada_string {
+  const char* data;
+  size_t length;
+};
+
+struct ada_owned_string {
+  const char* data;
+  size_t length;
+};
+
+struct ada_string_pair {
+  ada_string key;
+  ada_string value;
+};
+
+ada_string ada_string_create(const char* data, size_t length) {
+  ada_string out{};
+  out.data = data;
+  out.length = length;
+  return out;
+}
+
+struct ada_url_components {
+  /*
+   * By using 32-bit integers, we implicitly assume that the URL string
+   * cannot exceed 4 GB.
+   *
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   */
+  uint32_t protocol_end;
+  /**
+   * Username end is not `omitted` by default (-1) to make username and password
+   * getters less costly to implement.
+   */
+  uint32_t username_end;
+  uint32_t host_start;
+  uint32_t host_end;
+  uint32_t port;
+  uint32_t pathname_start;
+  uint32_t search_start;
+  uint32_t hash_start;
+};
+
+ada_url ada_parse(const char* input, size_t length) noexcept {
+  return new ada::result<ada::url_aggregator>(
+      ada::parse<ada::url_aggregator>(std::string_view(input, length)));
+}
+
+ada_url ada_parse_with_base(const char* input, size_t input_length,
+                            const char* base, size_t base_length) noexcept {
+  auto base_out =
+      ada::parse<ada::url_aggregator>(std::string_view(base, base_length));
+
+  if (!base_out) {
+    return new ada::result<ada::url_aggregator>(base_out);
+  }
+
+  return new ada::result<ada::url_aggregator>(ada::parse<ada::url_aggregator>(
+      std::string_view(input, input_length), &base_out.value()));
+}
+
+bool ada_can_parse(const char* input, size_t length) noexcept {
+  return ada::can_parse(std::string_view(input, length));
+}
+
+bool ada_can_parse_with_base(const char* input, size_t input_length,
+                             const char* base, size_t base_length) noexcept {
+  std::string_view base_view(base, base_length);
+  return ada::can_parse(std::string_view(input, input_length), &base_view);
+}
+
+void ada_free(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>* r =
+      (ada::result<ada::url_aggregator>*)result;
+  delete r;
+}
+
+ada_url ada_copy(ada_url input) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(input);
+  return new ada::result<ada::url_aggregator>(r);
+}
+
+bool ada_is_valid(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  return r.has_value();
+}
+
+// caller must free the result with ada_free_owned_string
+ada_owned_string ada_get_origin(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  ada_owned_string owned;
+  if (!r) {
+    owned.data = nullptr;
+    owned.length = 0;
+    return owned;
+  }
+  std::string out = r->get_origin();
+  owned.length = out.size();
+  owned.data = new char[owned.length];
+  memcpy((void*)owned.data, out.data(), owned.length);
+  return owned;
+}
+
+void ada_free_owned_string(ada_owned_string owned) noexcept {
+  delete[] owned.data;
+  owned.data = nullptr;
+  owned.length = 0;
+}
+
+ada_string ada_get_href(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_href();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_username(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_username();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_password(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_password();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_port(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_port();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_hash(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_hash();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_host(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_host();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_hostname(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_hostname();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_pathname(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_pathname();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_search(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_search();
+  return ada_string_create(out.data(), out.length());
+}
+
+ada_string ada_get_protocol(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view out = r->get_protocol();
+  return ada_string_create(out.data(), out.length());
+}
+
+uint8_t ada_get_host_type(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return 0;
+  }
+  return r->host_type;
+}
+
+uint8_t ada_get_scheme_type(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return 0;
+  }
+  return r->type;
+}
+
+bool ada_set_href(ada_url result, const char* input, size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_href(std::string_view(input, length));
+}
+
+bool ada_set_host(ada_url result, const char* input, size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_host(std::string_view(input, length));
+}
+
+bool ada_set_hostname(ada_url result, const char* input,
+                      size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_hostname(std::string_view(input, length));
+}
+
+bool ada_set_protocol(ada_url result, const char* input,
+                      size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_protocol(std::string_view(input, length));
+}
+
+bool ada_set_username(ada_url result, const char* input,
+                      size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_username(std::string_view(input, length));
+}
+
+bool ada_set_password(ada_url result, const char* input,
+                      size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_password(std::string_view(input, length));
+}
+
+bool ada_set_port(ada_url result, const char* input, size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_port(std::string_view(input, length));
+}
+
+bool ada_set_pathname(ada_url result, const char* input,
+                      size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->set_pathname(std::string_view(input, length));
+}
+
+/**
+ * Update the search/query of the URL.
+ *
+ * If a URL has `?` as the search value, passing empty string to this function
+ * does not remove the attribute. If you need to remove it, please use
+ * `ada_clear_search` method.
+ */
+void ada_set_search(ada_url result, const char* input, size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (r) {
+    r->set_search(std::string_view(input, length));
+  }
+}
+
+/**
+ * Update the hash/fragment of the URL.
+ *
+ * If a URL has `#` as the hash value, passing empty string to this function
+ * does not remove the attribute. If you need to remove it, please use
+ * `ada_clear_hash` method.
+ */
+void ada_set_hash(ada_url result, const char* input, size_t length) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (r) {
+    r->set_hash(std::string_view(input, length));
+  }
+}
+
+void ada_clear_port(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (r) {
+    r->clear_port();
+  }
+}
+
+/**
+ * Removes the hash of the URL.
+ *
+ * Despite `ada_set_hash` method, this function allows the complete
+ * removal of the hash attribute, even if it has a value of `#`.
+ */
+void ada_clear_hash(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (r) {
+    r->clear_hash();
+  }
+}
+
+/**
+ * Removes the search of the URL.
+ *
+ * Despite `ada_set_search` method, this function allows the complete
+ * removal of the search attribute, even if it has a value of `?`.
+ */
+void ada_clear_search(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (r) {
+    r->clear_search();
+  }
+}
+
+bool ada_has_credentials(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_credentials();
+}
+
+bool ada_has_empty_hostname(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_empty_hostname();
+}
+
+bool ada_has_hostname(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_hostname();
+}
+
+bool ada_has_non_empty_username(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_non_empty_username();
+}
+
+bool ada_has_non_empty_password(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_non_empty_password();
+}
+
+bool ada_has_port(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_port();
+}
+
+bool ada_has_password(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_password();
+}
+
+bool ada_has_hash(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_hash();
+}
+
+bool ada_has_search(ada_url result) noexcept {
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return false;
+  }
+  return r->has_search();
+}
+
+// returns a pointer to the internal url_aggregator::url_components
+const ada_url_components* ada_get_components(ada_url result) noexcept {
+  static_assert(sizeof(ada_url_components) == sizeof(ada::url_components));
+  ada::result<ada::url_aggregator>& r = get_instance(result);
+  if (!r) {
+    return nullptr;
+  }
+  return reinterpret_cast<const ada_url_components*>(&r->get_components());
+}
+
+ada_owned_string ada_idna_to_unicode(const char* input, size_t length) {
+  std::string out = ada::idna::to_unicode(std::string_view(input, length));
+  ada_owned_string owned{};
+  owned.length = out.length();
+  owned.data = new char[owned.length];
+  memcpy((void*)owned.data, out.data(), owned.length);
+  return owned;
+}
+
+ada_owned_string ada_idna_to_ascii(const char* input, size_t length) {
+  std::string out = ada::idna::to_ascii(std::string_view(input, length));
+  ada_owned_string owned{};
+  owned.length = out.size();
+  owned.data = new char[owned.length];
+  memcpy((void*)owned.data, out.data(), owned.length);
+  return owned;
+}
+
+ada_url_search_params ada_parse_search_params(const char* input,
+                                              size_t length) {
+  return new ada::result<ada::url_search_params>(
+      ada::url_search_params(std::string_view(input, length)));
+}
+
+void ada_free_search_params(ada_url_search_params result) {
+  ada::result<ada::url_search_params>* r =
+      (ada::result<ada::url_search_params>*)result;
+  delete r;
+}
+
+ada_owned_string ada_search_params_to_string(ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) return ada_owned_string{NULL, 0};
+  std::string out = r->to_string();
+  ada_owned_string owned{};
+  owned.length = out.size();
+  owned.data = new char[owned.length];
+  memcpy((void*)owned.data, out.data(), owned.length);
+  return owned;
+}
+
+size_t ada_search_params_size(ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return 0;
+  }
+  return r->size();
+}
+
+void ada_search_params_sort(ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (r) {
+    r->sort();
+  }
+}
+
+void ada_search_params_append(ada_url_search_params result, const char* key,
+                              size_t key_length, const char* value,
+                              size_t value_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (r) {
+    r->append(std::string_view(key, key_length),
+              std::string_view(value, value_length));
+  }
+}
+
+void ada_search_params_set(ada_url_search_params result, const char* key,
+                           size_t key_length, const char* value,
+                           size_t value_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (r) {
+    r->set(std::string_view(key, key_length),
+           std::string_view(value, value_length));
+  }
+}
+
+void ada_search_params_remove(ada_url_search_params result, const char* key,
+                              size_t key_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (r) {
+    r->remove(std::string_view(key, key_length));
+  }
+}
+
+void ada_search_params_remove_value(ada_url_search_params result,
+                                    const char* key, size_t key_length,
+                                    const char* value, size_t value_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (r) {
+    r->remove(std::string_view(key, key_length),
+              std::string_view(value, value_length));
+  }
+}
+
+bool ada_search_params_has(ada_url_search_params result, const char* key,
+                           size_t key_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return false;
+  }
+  return r->has(std::string_view(key, key_length));
+}
+
+bool ada_search_params_has_value(ada_url_search_params result, const char* key,
+                                 size_t key_length, const char* value,
+                                 size_t value_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return false;
+  }
+  return r->has(std::string_view(key, key_length),
+                std::string_view(value, value_length));
+}
+
+ada_string ada_search_params_get(ada_url_search_params result, const char* key,
+                                 size_t key_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  auto found = r->get(std::string_view(key, key_length));
+  if (!found.has_value()) {
+    return ada_string_create(NULL, 0);
+  }
+  return ada_string_create(found->data(), found->length());
+}
+
+ada_strings ada_search_params_get_all(ada_url_search_params result,
+                                      const char* key, size_t key_length) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return new ada::result<std::vector<std::string>>(
+        std::vector<std::string>());
+  }
+  return new ada::result<std::vector<std::string>>(
+      r->get_all(std::string_view(key, key_length)));
+}
+
+ada_url_search_params_keys_iter ada_search_params_get_keys(
+    ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return new ada::result<ada::url_search_params_keys_iter>(
+        ada::url_search_params_keys_iter());
+  }
+  return new ada::result<ada::url_search_params_keys_iter>(r->get_keys());
+}
+
+ada_url_search_params_values_iter ada_search_params_get_values(
+    ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return new ada::result<ada::url_search_params_values_iter>(
+        ada::url_search_params_values_iter());
+  }
+  return new ada::result<ada::url_search_params_values_iter>(r->get_values());
+}
+
+ada_url_search_params_entries_iter ada_search_params_get_entries(
+    ada_url_search_params result) {
+  ada::result<ada::url_search_params>& r =
+      *(ada::result<ada::url_search_params>*)result;
+  if (!r) {
+    return new ada::result<ada::url_search_params_entries_iter>(
+        ada::url_search_params_entries_iter());
+  }
+  return new ada::result<ada::url_search_params_entries_iter>(r->get_entries());
+}
+
+void ada_free_strings(ada_strings result) {
+  ada::result<std::vector<std::string>>* r =
+      (ada::result<std::vector<std::string>>*)result;
+  delete r;
+}
+
+size_t ada_strings_size(ada_strings result) {
+  ada::result<std::vector<std::string>>* r =
+      (ada::result<std::vector<std::string>>*)result;
+  if (!r) {
+    return 0;
+  }
+  return (*r)->size();
+}
+
+ada_string ada_strings_get(ada_strings result, size_t index) {
+  ada::result<std::vector<std::string>>* r =
+      (ada::result<std::vector<std::string>>*)result;
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  std::string_view view = (*r)->at(index);
+  return ada_string_create(view.data(), view.length());
+}
+
+void ada_free_search_params_keys_iter(ada_url_search_params_keys_iter result) {
+  ada::result<ada::url_search_params_keys_iter>* r =
+      (ada::result<ada::url_search_params_keys_iter>*)result;
+  delete r;
+}
+
+ada_string ada_search_params_keys_iter_next(
+    ada_url_search_params_keys_iter result) {
+  ada::result<ada::url_search_params_keys_iter>* r =
+      (ada::result<ada::url_search_params_keys_iter>*)result;
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  auto next = (*r)->next();
+  if (!next.has_value()) {
+    return ada_string_create(NULL, 0);
+  }
+  return ada_string_create(next->data(), next->length());
+}
+
+bool ada_search_params_keys_iter_has_next(
+    ada_url_search_params_keys_iter result) {
+  ada::result<ada::url_search_params_keys_iter>* r =
+      (ada::result<ada::url_search_params_keys_iter>*)result;
+  if (!r) {
+    return false;
+  }
+  return (*r)->has_next();
+}
+
+void ada_free_search_params_values_iter(
+    ada_url_search_params_values_iter result) {
+  ada::result<ada::url_search_params_values_iter>* r =
+      (ada::result<ada::url_search_params_values_iter>*)result;
+  delete r;
+}
+
+ada_string ada_search_params_values_iter_next(
+    ada_url_search_params_values_iter result) {
+  ada::result<ada::url_search_params_values_iter>* r =
+      (ada::result<ada::url_search_params_values_iter>*)result;
+  if (!r) {
+    return ada_string_create(NULL, 0);
+  }
+  auto next = (*r)->next();
+  if (!next.has_value()) {
+    return ada_string_create(NULL, 0);
+  }
+  return ada_string_create(next->data(), next->length());
+}
+
+bool ada_search_params_values_iter_has_next(
+    ada_url_search_params_values_iter result) {
+  ada::result<ada::url_search_params_values_iter>* r =
+      (ada::result<ada::url_search_params_values_iter>*)result;
+  if (!r) {
+    return false;
+  }
+  return (*r)->has_next();
+}
+
+void ada_free_search_params_entries_iter(
+    ada_url_search_params_entries_iter result) {
+  ada::result<ada::url_search_params_entries_iter>* r =
+      (ada::result<ada::url_search_params_entries_iter>*)result;
+  delete r;
+}
+
+ada_string_pair ada_search_params_entries_iter_next(
+    ada_url_search_params_entries_iter result) {
+  ada::result<ada::url_search_params_entries_iter>* r =
+      (ada::result<ada::url_search_params_entries_iter>*)result;
+  if (!r) return {ada_string_create(NULL, 0), ada_string_create(NULL, 0)};
+  auto next = (*r)->next();
+  if (!next.has_value()) {
+    return {ada_string_create(NULL, 0), ada_string_create(NULL, 0)};
+  }
+  return ada_string_pair{
+      ada_string_create(next->first.data(), next->first.length()),
+      ada_string_create(next->second.data(), next->second.length())};
+}
+
+bool ada_search_params_entries_iter_has_next(
+    ada_url_search_params_entries_iter result) {
+  ada::result<ada::url_search_params_entries_iter>* r =
+      (ada::result<ada::url_search_params_entries_iter>*)result;
+  if (!r) {
+    return false;
+  }
+  return (*r)->has_next();
+}
+
+}  // extern "C"
diff --git a/src/ada_idna.cpp b/src/ada_idna.cpp
new file mode 100644 (file)
index 0000000..7a77757
--- /dev/null
@@ -0,0 +1,9650 @@
+/* auto-generated on 2023-09-19 15:58:51 -0400. Do not edit! */
+/* begin file src/idna.cpp */
+/* begin file src/unicode_transcoding.cpp */
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+
+namespace ada::idna {
+
+size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output) {
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(buf);
+  size_t pos = 0;
+  char32_t* start{utf32_output};
+  while (pos < len) {
+    // try to convert the next block of 16 ASCII bytes
+    if (pos + 16 <= len) {  // if it is safe to read 16 more
+                            // bytes, check that they are ascii
+      uint64_t v1;
+      std::memcpy(&v1, data + pos, sizeof(uint64_t));
+      uint64_t v2;
+      std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
+      uint64_t v{v1 | v2};
+      if ((v & 0x8080808080808080) == 0) {
+        size_t final_pos = pos + 16;
+        while (pos < final_pos) {
+          *utf32_output++ = char32_t(buf[pos]);
+          pos++;
+        }
+        continue;
+      }
+    }
+    uint8_t leading_byte = data[pos];  // leading byte
+    if (leading_byte < 0b10000000) {
+      // converting one ASCII byte !!!
+      *utf32_output++ = char32_t(leading_byte);
+      pos++;
+    } else if ((leading_byte & 0b11100000) == 0b11000000) {
+      // We have a two-byte UTF-8
+      if (pos + 1 >= len) {
+        return 0;
+      }  // minimal bound checking
+      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+      // range check
+      uint32_t code_point =
+          (leading_byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
+      if (code_point < 0x80 || 0x7ff < code_point) {
+        return 0;
+      }
+      *utf32_output++ = char32_t(code_point);
+      pos += 2;
+    } else if ((leading_byte & 0b11110000) == 0b11100000) {
+      // We have a three-byte UTF-8
+      if (pos + 2 >= len) {
+        return 0;
+      }  // minimal bound checking
+
+      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+      if ((data[pos + 2] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+      // range check
+      uint32_t code_point = (leading_byte & 0b00001111) << 12 |
+                            (data[pos + 1] & 0b00111111) << 6 |
+                            (data[pos + 2] & 0b00111111);
+      if (code_point < 0x800 || 0xffff < code_point ||
+          (0xd7ff < code_point && code_point < 0xe000)) {
+        return 0;
+      }
+      *utf32_output++ = char32_t(code_point);
+      pos += 3;
+    } else if ((leading_byte & 0b11111000) == 0b11110000) {  // 0b11110000
+      // we have a 4-byte UTF-8 word.
+      if (pos + 3 >= len) {
+        return 0;
+      }  // minimal bound checking
+      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+      if ((data[pos + 2] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+      if ((data[pos + 3] & 0b11000000) != 0b10000000) {
+        return 0;
+      }
+
+      // range check
+      uint32_t code_point = (leading_byte & 0b00000111) << 18 |
+                            (data[pos + 1] & 0b00111111) << 12 |
+                            (data[pos + 2] & 0b00111111) << 6 |
+                            (data[pos + 3] & 0b00111111);
+      if (code_point <= 0xffff || 0x10ffff < code_point) {
+        return 0;
+      }
+      *utf32_output++ = char32_t(code_point);
+      pos += 4;
+    } else {
+      return 0;
+    }
+  }
+  return utf32_output - start;
+}
+
+size_t utf8_length_from_utf32(const char32_t* buf, size_t len) {
+  // We are not BOM aware.
+  const uint32_t* p = reinterpret_cast<const uint32_t*>(buf);
+  size_t counter{0};
+  for (size_t i = 0; i != len; ++i) {
+    ++counter;                                      // ASCII
+    counter += static_cast<size_t>(p[i] > 0x7F);    // two-byte
+    counter += static_cast<size_t>(p[i] > 0x7FF);   // three-byte
+    counter += static_cast<size_t>(p[i] > 0xFFFF);  // four-bytes
+  }
+  return counter;
+}
+
+size_t utf32_length_from_utf8(const char* buf, size_t len) {
+  const int8_t* p = reinterpret_cast<const int8_t*>(buf);
+  return std::count_if(p, std::next(p, len), [](int8_t c) {
+    // -65 is 0b10111111, anything larger in two-complement's
+    // should start a new code point.
+    return c > -65;
+  });
+}
+
+size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output) {
+  const uint32_t* data = reinterpret_cast<const uint32_t*>(buf);
+  size_t pos = 0;
+  char* start{utf8_output};
+  while (pos < len) {
+    // try to convert the next block of 2 ASCII characters
+    if (pos + 2 <= len) {  // if it is safe to read 8 more
+                           // bytes, check that they are ascii
+      uint64_t v;
+      std::memcpy(&v, data + pos, sizeof(uint64_t));
+      if ((v & 0xFFFFFF80FFFFFF80) == 0) {
+        *utf8_output++ = char(buf[pos]);
+        *utf8_output++ = char(buf[pos + 1]);
+        pos += 2;
+        continue;
+      }
+    }
+    uint32_t word = data[pos];
+    if ((word & 0xFFFFFF80) == 0) {
+      // will generate one UTF-8 bytes
+      *utf8_output++ = char(word);
+      pos++;
+    } else if ((word & 0xFFFFF800) == 0) {
+      // will generate two UTF-8 bytes
+      // we have 0b110XXXXX 0b10XXXXXX
+      *utf8_output++ = char((word >> 6) | 0b11000000);
+      *utf8_output++ = char((word & 0b111111) | 0b10000000);
+      pos++;
+    } else if ((word & 0xFFFF0000) == 0) {
+      // will generate three UTF-8 bytes
+      // we have 0b1110XXXX 0b10XXXXXX 0b10XXXXXX
+      if (word >= 0xD800 && word <= 0xDFFF) {
+        return 0;
+      }
+      *utf8_output++ = char((word >> 12) | 0b11100000);
+      *utf8_output++ = char(((word >> 6) & 0b111111) | 0b10000000);
+      *utf8_output++ = char((word & 0b111111) | 0b10000000);
+      pos++;
+    } else {
+      // will generate four UTF-8 bytes
+      // we have 0b11110XXX 0b10XXXXXX 0b10XXXXXX
+      // 0b10XXXXXX
+      if (word > 0x10FFFF) {
+        return 0;
+      }
+      *utf8_output++ = char((word >> 18) | 0b11110000);
+      *utf8_output++ = char(((word >> 12) & 0b111111) | 0b10000000);
+      *utf8_output++ = char(((word >> 6) & 0b111111) | 0b10000000);
+      *utf8_output++ = char((word & 0b111111) | 0b10000000);
+      pos++;
+    }
+  }
+  return utf8_output - start;
+}
+}  // namespace ada::idna
+/* end file src/unicode_transcoding.cpp */
+/* begin file src/mapping.cpp */
+
+#include <algorithm>
+#include <array>
+#include <string>
+
+/* begin file src/mapping_tables.cpp */
+// IDNA  15.0.0
+
+// clang-format off
+#ifndef ADA_IDNA_TABLES_H
+#define ADA_IDNA_TABLES_H
+#include <cstdint>
+
+namespace ada::idna {
+
+const uint32_t mappings[5164] =
+{
+       97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+       114, 115, 116, 117, 118, 119, 120, 121, 122, 32, 32, 776, 32, 772, 50, 51, 32, 769,
+       956, 32, 807, 49, 49, 8260, 52, 49, 8260, 50, 51, 8260, 52, 224, 225, 226, 227,
+       228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243,
+       244, 245, 246, 248, 249, 250, 251, 252, 253, 254, 257, 259, 261, 263, 265, 267,
+       269, 271, 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, 293, 295, 297, 299,
+       301, 303, 105, 775, 309, 311, 314, 316, 318, 108, 183, 322, 324, 326, 328, 700,
+       110, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, 353, 355, 357, 359,
+       361, 363, 365, 367, 369, 371, 373, 375, 255, 378, 380, 382, 595, 387, 389, 596,
+       392, 598, 599, 396, 477, 601, 603, 402, 608, 611, 617, 616, 409, 623, 626, 629,
+       417, 419, 421, 640, 424, 643, 429, 648, 432, 650, 651, 436, 438, 658, 441, 445,
+       100, 382, 108, 106, 110, 106, 462, 464, 466, 468, 470, 472, 474, 476, 479, 481,
+       483, 485, 487, 489, 491, 493, 495, 100, 122, 501, 405, 447, 505, 507, 509, 511,
+       513, 515, 517, 519, 521, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543,
+       414, 547, 549, 551, 553, 555, 557, 559, 561, 563, 11365, 572, 410, 11366, 578, 384,
+       649, 652, 583, 585, 587, 589, 591, 614, 633, 635, 641, 32, 774, 32, 775, 32, 778,
+       32, 808, 32, 771, 32, 779, 661, 768, 787, 776, 769, 953, 881, 883, 697, 887, 32,
+       953, 59, 1011, 32, 776, 769, 940, 941, 942, 943, 972, 973, 974, 945, 946, 947, 948,
+       949, 950, 951, 952, 954, 955, 957, 958, 959, 960, 961, 963, 964, 965, 966, 967,
+       968, 969, 970, 971, 983, 985, 987, 989, 991, 993, 995, 997, 999, 1001, 1003, 1005,
+       1007, 1016, 1019, 891, 892, 893, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111,
+       1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1072, 1073, 1074, 1075, 1076, 1077,
+       1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091,
+       1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1121, 1123,
+       1125, 1127, 1129, 1131, 1133, 1135, 1137, 1139, 1141, 1143, 1145, 1147, 1149, 1151,
+       1153, 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1183, 1185, 1187,
+       1189, 1191, 1193, 1195, 1197, 1199, 1201, 1203, 1205, 1207, 1209, 1211, 1213, 1215,
+       1218, 1220, 1222, 1224, 1226, 1228, 1230, 1233, 1235, 1237, 1239, 1241, 1243, 1245,
+       1247, 1249, 1251, 1253, 1255, 1257, 1259, 1261, 1263, 1265, 1267, 1269, 1271, 1273,
+       1275, 1277, 1279, 1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1297, 1299, 1301,
+       1303, 1305, 1307, 1309, 1311, 1313, 1315, 1317, 1319, 1321, 1323, 1325, 1327, 1377,
+       1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391,
+       1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405,
+       1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1381, 1410, 1575, 1652, 1608,
+       1652, 1735, 1652, 1610, 1652, 2325, 2364, 2326, 2364, 2327, 2364, 2332, 2364, 2337,
+       2364, 2338, 2364, 2347, 2364, 2351, 2364, 2465, 2492, 2466, 2492, 2479, 2492, 2610,
+       2620, 2616, 2620, 2582, 2620, 2583, 2620, 2588, 2620, 2603, 2620, 2849, 2876, 2850,
+       2876, 3661, 3634, 3789, 3762, 3755, 3737, 3755, 3745, 3851, 3906, 4023, 3916, 4023,
+       3921, 4023, 3926, 4023, 3931, 4023, 3904, 4021, 3953, 3954, 3953, 3956, 4018, 3968,
+       4018, 3953, 3968, 4019, 3968, 4019, 3953, 3968, 3986, 4023, 3996, 4023, 4001, 4023,
+       4006, 4023, 4011, 4023, 3984, 4021, 11559, 11565, 4316, 5104, 5105, 5106, 5107,
+       5108, 5109, 42571, 4304, 4305, 4306, 4307, 4308, 4309, 4310, 4311, 4312, 4313, 4314,
+       4315, 4317, 4318, 4319, 4320, 4321, 4322, 4323, 4324, 4325, 4326, 4327, 4328, 4329,
+       4330, 4331, 4332, 4333, 4334, 4335, 4336, 4337, 4338, 4339, 4340, 4341, 4342, 4343,
+       4344, 4345, 4346, 4349, 4350, 4351, 592, 593, 7426, 604, 7446, 7447, 7453, 7461,
+       594, 597, 607, 609, 613, 618, 7547, 669, 621, 7557, 671, 625, 624, 627, 628, 632,
+       642, 427, 7452, 656, 657, 7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697,
+       7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, 7717, 7719, 7721, 7723, 7725,
+       7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, 7753,
+       7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781,
+       7783, 7785, 7787, 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809,
+       7811, 7813, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, 97, 702, 115, 115, 7841,
+       7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, 7861, 7863, 7865, 7867, 7869,
+       7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, 7897,
+       7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925,
+       7927, 7929, 7931, 7933, 7935, 7936, 7937, 7938, 7939, 7940, 7941, 7942, 7943, 7952,
+       7953, 7954, 7955, 7956, 7957, 7968, 7969, 7970, 7971, 7972, 7973, 7974, 7975, 7984,
+       7985, 7986, 7987, 7988, 7989, 7990, 7991, 8000, 8001, 8002, 8003, 8004, 8005, 8017,
+       8019, 8021, 8023, 8032, 8033, 8034, 8035, 8036, 8037, 8038, 8039, 7936, 953, 7937,
+       953, 7938, 953, 7939, 953, 7940, 953, 7941, 953, 7942, 953, 7943, 953, 7968, 953,
+       7969, 953, 7970, 953, 7971, 953, 7972, 953, 7973, 953, 7974, 953, 7975, 953, 8032,
+       953, 8033, 953, 8034, 953, 8035, 953, 8036, 953, 8037, 953, 8038, 953, 8039, 953,
+       8048, 953, 945, 953, 940, 953, 8118, 953, 8112, 8113, 32, 787, 32, 834, 32, 776,
+       834, 8052, 953, 951, 953, 942, 953, 8134, 953, 8050, 32, 787, 768, 32, 787, 769,
+       32, 787, 834, 912, 8144, 8145, 8054, 32, 788, 768, 32, 788, 769, 32, 788, 834, 944,
+       8160, 8161, 8058, 8165, 32, 776, 768, 96, 8060, 953, 969, 953, 974, 953, 8182, 953,
+       8056, 8208, 32, 819, 8242, 8242, 8242, 8242, 8242, 8245, 8245, 8245, 8245, 8245,
+       33, 33, 32, 773, 63, 63, 63, 33, 33, 63, 48, 53, 54, 55, 56, 57, 43, 8722, 61, 40,
+       41, 97, 47, 99, 97, 47, 115, 176, 99, 99, 47, 111, 99, 47, 117, 176, 102, 115, 109,
+       116, 101, 108, 116, 109, 1488, 1489, 1490, 1491, 102, 97, 120, 8721, 49, 8260, 55,
+       49, 8260, 57, 49, 8260, 49, 48, 49, 8260, 51, 50, 8260, 51, 49, 8260, 53, 50, 8260,
+       53, 51, 8260, 53, 52, 8260, 53, 49, 8260, 54, 53, 8260, 54, 49, 8260, 56, 51, 8260,
+       56, 53, 8260, 56, 55, 8260, 56, 105, 105, 105, 105, 105, 105, 118, 118, 105, 118,
+       105, 105, 118, 105, 105, 105, 105, 120, 120, 105, 120, 105, 105, 48, 8260, 51, 8747,
+       8747, 8747, 8747, 8747, 8750, 8750, 8750, 8750, 8750, 12296, 12297, 49, 50, 49,
+       51, 49, 52, 49, 53, 49, 54, 49, 55, 49, 56, 49, 57, 50, 48, 40, 49, 41, 40, 50,
+       41, 40, 51, 41, 40, 52, 41, 40, 53, 41, 40, 54, 41, 40, 55, 41, 40, 56, 41, 40,
+       57, 41, 40, 49, 48, 41, 40, 49, 49, 41, 40, 49, 50, 41, 40, 49, 51, 41, 40, 49,
+       52, 41, 40, 49, 53, 41, 40, 49, 54, 41, 40, 49, 55, 41, 40, 49, 56, 41, 40, 49,
+       57, 41, 40, 50, 48, 41, 40, 97, 41, 40, 98, 41, 40, 99, 41, 40, 100, 41, 40, 101,
+       41, 40, 102, 41, 40, 103, 41, 40, 104, 41, 40, 105, 41, 40, 106, 41, 40, 107, 41,
+       40, 108, 41, 40, 109, 41, 40, 110, 41, 40, 111, 41, 40, 112, 41, 40, 113, 41, 40,
+       114, 41, 40, 115, 41, 40, 116, 41, 40, 117, 41, 40, 118, 41, 40, 119, 41, 40, 120,
+       41, 40, 121, 41, 40, 122, 41, 58, 58, 61, 61, 61, 10973, 824, 11312, 11313, 11314,
+       11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326,
+       11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338,
+       11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350,
+       11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358, 11359, 11361, 619, 7549,
+       637, 11368, 11370, 11372, 11379, 11382, 575, 576, 11393, 11395, 11397, 11399, 11401,
+       11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425,
+       11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449,
+       11451, 11453, 11455, 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473,
+       11475, 11477, 11479, 11481, 11483, 11485, 11487, 11489, 11491, 11500, 11502, 11507,
+       11617, 27597, 40863, 19968, 20008, 20022, 20031, 20057, 20101, 20108, 20128, 20154,
+       20799, 20837, 20843, 20866, 20886, 20907, 20960, 20981, 20992, 21147, 21241, 21269,
+       21274, 21304, 21313, 21340, 21353, 21378, 21430, 21448, 21475, 22231, 22303, 22763,
+       22786, 22794, 22805, 22823, 22899, 23376, 23424, 23544, 23567, 23586, 23608, 23662,
+       23665, 24027, 24037, 24049, 24062, 24178, 24186, 24191, 24308, 24318, 24331, 24339,
+       24400, 24417, 24435, 24515, 25096, 25142, 25163, 25903, 25908, 25991, 26007, 26020,
+       26041, 26080, 26085, 26352, 26376, 26408, 27424, 27490, 27513, 27571, 27595, 27604,
+       27611, 27663, 27668, 27700, 28779, 29226, 29238, 29243, 29247, 29255, 29273, 29275,
+       29356, 29572, 29577, 29916, 29926, 29976, 29983, 29992, 30000, 30091, 30098, 30326,
+       30333, 30382, 30399, 30446, 30683, 30690, 30707, 31034, 31160, 31166, 31348, 31435,
+       31481, 31859, 31992, 32566, 32593, 32650, 32701, 32769, 32780, 32786, 32819, 32895,
+       32905, 33251, 33258, 33267, 33276, 33292, 33307, 33311, 33390, 33394, 33400, 34381,
+       34411, 34880, 34892, 34915, 35198, 35211, 35282, 35328, 35895, 35910, 35925, 35960,
+       35997, 36196, 36208, 36275, 36523, 36554, 36763, 36784, 36789, 37009, 37193, 37318,
+       37324, 37329, 38263, 38272, 38428, 38582, 38585, 38632, 38737, 38750, 38754, 38761,
+       38859, 38893, 38899, 38913, 39080, 39131, 39135, 39318, 39321, 39340, 39592, 39640,
+       39647, 39717, 39727, 39730, 39740, 39770, 40165, 40565, 40575, 40613, 40635, 40643,
+       40653, 40657, 40697, 40701, 40718, 40723, 40736, 40763, 40778, 40786, 40845, 40860,
+       40864, 46, 12306, 21316, 21317, 32, 12441, 32, 12442, 12424, 12426, 12467, 12488,
+       4352, 4353, 4522, 4354, 4524, 4525, 4355, 4356, 4357, 4528, 4529, 4530, 4531, 4532,
+       4533, 4378, 4358, 4359, 4360, 4385, 4361, 4362, 4363, 4364, 4365, 4366, 4367, 4368,
+       4369, 4370, 4449, 4450, 4451, 4452, 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460,
+       4461, 4462, 4463, 4464, 4465, 4466, 4467, 4468, 4469, 4372, 4373, 4551, 4552, 4556,
+       4558, 4563, 4567, 4569, 4380, 4573, 4575, 4381, 4382, 4384, 4386, 4387, 4391, 4393,
+       4395, 4396, 4397, 4398, 4399, 4402, 4406, 4416, 4423, 4428, 4593, 4594, 4439, 4440,
+       4441, 4484, 4485, 4488, 4497, 4498, 4500, 4510, 4513, 19977, 22235, 19978, 20013,
+       19979, 30002, 19993, 19969, 22825, 22320, 40, 4352, 41, 40, 4354, 41, 40, 4355,
+       41, 40, 4357, 41, 40, 4358, 41, 40, 4359, 41, 40, 4361, 41, 40, 4363, 41, 40, 4364,
+       41, 40, 4366, 41, 40, 4367, 41, 40, 4368, 41, 40, 4369, 41, 40, 4370, 41, 40, 44032,
+       41, 40, 45208, 41, 40, 45796, 41, 40, 46972, 41, 40, 47560, 41, 40, 48148, 41, 40,
+       49324, 41, 40, 50500, 41, 40, 51088, 41, 40, 52264, 41, 40, 52852, 41, 40, 53440,
+       41, 40, 54028, 41, 40, 54616, 41, 40, 51452, 41, 40, 50724, 51204, 41, 40, 50724,
+       54980, 41, 40, 19968, 41, 40, 20108, 41, 40, 19977, 41, 40, 22235, 41, 40, 20116,
+       41, 40, 20845, 41, 40, 19971, 41, 40, 20843, 41, 40, 20061, 41, 40, 21313, 41, 40,
+       26376, 41, 40, 28779, 41, 40, 27700, 41, 40, 26408, 41, 40, 37329, 41, 40, 22303,
+       41, 40, 26085, 41, 40, 26666, 41, 40, 26377, 41, 40, 31038, 41, 40, 21517, 41, 40,
+       29305, 41, 40, 36001, 41, 40, 31069, 41, 40, 21172, 41, 40, 20195, 41, 40, 21628,
+       41, 40, 23398, 41, 40, 30435, 41, 40, 20225, 41, 40, 36039, 41, 40, 21332, 41, 40,
+       31085, 41, 40, 20241, 41, 40, 33258, 41, 40, 33267, 41, 21839, 24188, 31631, 112,
+       116, 101, 50, 50, 50, 52, 50, 53, 50, 54, 50, 55, 50, 56, 50, 57, 51, 48, 51, 51,
+       51, 52, 51, 53, 52280, 44256, 51452, 51032, 50864, 31192, 30007, 36969, 20778, 21360,
+       27880, 38917, 20889, 27491, 24038, 21491, 21307, 23447, 22812, 51, 54, 51, 55, 51,
+       56, 51, 57, 52, 48, 52, 52, 52, 53, 52, 54, 52, 55, 52, 56, 52, 57, 53, 48, 49,
+       26376, 50, 26376, 51, 26376, 52, 26376, 53, 26376, 54, 26376, 55, 26376, 56, 26376,
+       57, 26376, 49, 48, 26376, 49, 49, 26376, 49, 50, 26376, 104, 103, 101, 114, 103,
+       101, 118, 108, 116, 100, 12450, 12452, 12454, 12456, 12458, 12459, 12461, 12463,
+       12465, 12469, 12471, 12473, 12475, 12477, 12479, 12481, 12484, 12486, 12490, 12491,
+       12492, 12493, 12494, 12495, 12498, 12501, 12504, 12507, 12510, 12511, 12512, 12513,
+       12514, 12516, 12518, 12520, 12521, 12522, 12523, 12524, 12525, 12527, 12528, 12529,
+       12530, 20196, 21644, 12450, 12497, 12540, 12488, 12450, 12523, 12501, 12449, 12450,
+       12531, 12506, 12450, 12450, 12540, 12523, 12452, 12491, 12531, 12464, 12452, 12531,
+       12481, 12454, 12457, 12531, 12456, 12473, 12463, 12540, 12489, 12456, 12540, 12459,
+       12540, 12458, 12531, 12473, 12458, 12540, 12512, 12459, 12452, 12522, 12459, 12521,
+       12483, 12488, 12459, 12525, 12522, 12540, 12460, 12525, 12531, 12460, 12531, 12510,
+       12462, 12460, 12462, 12491, 12540, 12461, 12517, 12522, 12540, 12462, 12523, 12480,
+       12540, 12461, 12525, 12461, 12525, 12464, 12521, 12512, 12461, 12525, 12513, 12540,
+       12488, 12523, 12461, 12525, 12527, 12483, 12488, 12464, 12521, 12512, 12488, 12531,
+       12463, 12523, 12476, 12452, 12525, 12463, 12525, 12540, 12493, 12465, 12540, 12473,
+       12467, 12523, 12490, 12467, 12540, 12509, 12469, 12452, 12463, 12523, 12469, 12531,
+       12481, 12540, 12512, 12471, 12522, 12531, 12464, 12475, 12531, 12481, 12475, 12531,
+       12488, 12480, 12540, 12473, 12487, 12471, 12489, 12523, 12490, 12494, 12494, 12483,
+       12488, 12495, 12452, 12484, 12497, 12540, 12475, 12531, 12488, 12497, 12540, 12484,
+       12496, 12540, 12524, 12523, 12500, 12450, 12473, 12488, 12523, 12500, 12463, 12523,
+       12500, 12467, 12499, 12523, 12501, 12449, 12521, 12483, 12489, 12501, 12451, 12540,
+       12488, 12502, 12483, 12471, 12455, 12523, 12501, 12521, 12531, 12504, 12463, 12479,
+       12540, 12523, 12506, 12477, 12506, 12491, 12498, 12504, 12523, 12484, 12506, 12531,
+       12473, 12506, 12540, 12472, 12505, 12540, 12479, 12509, 12452, 12531, 12488, 12508,
+       12523, 12488, 12507, 12531, 12509, 12531, 12489, 12507, 12540, 12523, 12507, 12540,
+       12531, 12510, 12452, 12463, 12525, 12510, 12452, 12523, 12510, 12483, 12495, 12510,
+       12523, 12463, 12510, 12531, 12471, 12519, 12531, 12511, 12463, 12525, 12531, 12511,
+       12522, 12511, 12522, 12496, 12540, 12523, 12513, 12460, 12513, 12460, 12488, 12531,
+       12516, 12540, 12489, 12516, 12540, 12523, 12518, 12450, 12531, 12522, 12483, 12488,
+       12523, 12522, 12521, 12523, 12500, 12540, 12523, 12540, 12502, 12523, 12524, 12512,
+       12524, 12531, 12488, 12466, 12531, 48, 28857, 49, 28857, 50, 28857, 51, 28857, 52,
+       28857, 53, 28857, 54, 28857, 55, 28857, 56, 28857, 57, 28857, 49, 48, 28857, 49,
+       49, 28857, 49, 50, 28857, 49, 51, 28857, 49, 52, 28857, 49, 53, 28857, 49, 54, 28857,
+       49, 55, 28857, 49, 56, 28857, 49, 57, 28857, 50, 48, 28857, 50, 49, 28857, 50, 50,
+       28857, 50, 51, 28857, 50, 52, 28857, 104, 112, 97, 100, 97, 97, 117, 98, 97, 114,
+       111, 118, 112, 99, 100, 109, 100, 109, 50, 100, 109, 51, 105, 117, 24179, 25104,
+       26157, 21644, 22823, 27491, 26126, 27835, 26666, 24335, 20250, 31038, 110, 97, 956,
+       97, 109, 97, 107, 97, 107, 98, 109, 98, 103, 98, 99, 97, 108, 107, 99, 97, 108,
+       112, 102, 110, 102, 956, 102, 956, 103, 109, 103, 107, 103, 104, 122, 107, 104,
+       122, 109, 104, 122, 116, 104, 122, 956, 108, 109, 108, 100, 108, 102, 109, 110,
+       109, 956, 109, 109, 109, 99, 109, 107, 109, 109, 109, 50, 99, 109, 50, 107, 109,
+       50, 109, 109, 51, 99, 109, 51, 107, 109, 51, 109, 8725, 115, 109, 8725, 115, 50,
+       107, 112, 97, 109, 112, 97, 103, 112, 97, 114, 97, 100, 114, 97, 100, 8725, 115,
+       114, 97, 100, 8725, 115, 50, 112, 115, 110, 115, 956, 115, 109, 115, 112, 118, 110,
+       118, 956, 118, 109, 118, 107, 118, 112, 119, 110, 119, 956, 119, 109, 119, 107,
+       119, 107, 969, 109, 969, 98, 113, 99, 8725, 107, 103, 100, 98, 103, 121, 104, 97,
+       105, 110, 107, 107, 107, 116, 108, 110, 108, 111, 103, 108, 120, 109, 105, 108,
+       109, 111, 108, 112, 104, 112, 112, 109, 112, 114, 115, 118, 119, 98, 118, 8725,
+       109, 97, 8725, 109, 49, 26085, 50, 26085, 51, 26085, 52, 26085, 53, 26085, 54, 26085,
+       55, 26085, 56, 26085, 57, 26085, 49, 48, 26085, 49, 49, 26085, 49, 50, 26085, 49,
+       51, 26085, 49, 52, 26085, 49, 53, 26085, 49, 54, 26085, 49, 55, 26085, 49, 56, 26085,
+       49, 57, 26085, 50, 48, 26085, 50, 49, 26085, 50, 50, 26085, 50, 51, 26085, 50, 52,
+       26085, 50, 53, 26085, 50, 54, 26085, 50, 55, 26085, 50, 56, 26085, 50, 57, 26085,
+       51, 48, 26085, 51, 49, 26085, 103, 97, 108, 42561, 42563, 42565, 42567, 42569, 42573,
+       42575, 42577, 42579, 42581, 42583, 42585, 42587, 42589, 42591, 42593, 42595, 42597,
+       42599, 42601, 42603, 42605, 42625, 42627, 42629, 42631, 42633, 42635, 42637, 42639,
+       42641, 42643, 42645, 42647, 42649, 42651, 42787, 42789, 42791, 42793, 42795, 42797,
+       42799, 42803, 42805, 42807, 42809, 42811, 42813, 42815, 42817, 42819, 42821, 42823,
+       42825, 42827, 42829, 42831, 42833, 42835, 42837, 42839, 42841, 42843, 42845, 42847,
+       42849, 42851, 42853, 42855, 42857, 42859, 42861, 42863, 42874, 42876, 7545, 42879,
+       42881, 42883, 42885, 42887, 42892, 42897, 42899, 42903, 42905, 42907, 42909, 42911,
+       42913, 42915, 42917, 42919, 42921, 620, 670, 647, 43859, 42933, 42935, 42937, 42939,
+       42941, 42943, 42945, 42947, 42900, 7566, 42952, 42954, 42961, 42967, 42969, 42998,
+       43831, 43858, 653, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034,
+       5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, 5045, 5046, 5047, 5048,
+       5049, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5057, 5058, 5059, 5060, 5061, 5062,
+       5063, 5064, 5065, 5066, 5067, 5068, 5069, 5070, 5071, 5072, 5073, 5074, 5075, 5076,
+       5077, 5078, 5079, 5080, 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 5089, 5090,
+       5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, 5099, 5100, 5101, 5102, 5103, 35912,
+       26356, 36040, 28369, 20018, 21477, 22865, 21895, 22856, 25078, 30313, 32645, 34367,
+       34746, 35064, 37007, 27138, 27931, 28889, 29662, 33853, 37226, 39409, 20098, 21365,
+       27396, 29211, 34349, 40478, 23888, 28651, 34253, 35172, 25289, 33240, 34847, 24266,
+       26391, 28010, 29436, 37070, 20358, 20919, 21214, 25796, 27347, 29200, 30439, 34310,
+       34396, 36335, 38706, 39791, 40442, 30860, 31103, 32160, 33737, 37636, 35542, 22751,
+       24324, 31840, 32894, 29282, 30922, 36034, 38647, 22744, 23650, 27155, 28122, 28431,
+       32047, 32311, 38475, 21202, 32907, 20956, 20940, 31260, 32190, 33777, 38517, 35712,
+       25295, 35582, 20025, 23527, 24594, 29575, 30064, 21271, 30971, 20415, 24489, 19981,
+       27852, 25976, 32034, 21443, 22622, 30465, 33865, 35498, 27578, 27784, 25342, 33509,
+       25504, 30053, 20142, 20841, 20937, 26753, 31975, 33391, 35538, 37327, 21237, 21570,
+       24300, 26053, 28670, 31018, 38317, 39530, 40599, 40654, 26310, 27511, 36706, 24180,
+       24976, 25088, 25754, 28451, 29001, 29833, 31178, 32244, 32879, 36646, 34030, 36899,
+       37706, 21015, 21155, 21693, 28872, 35010, 24265, 24565, 25467, 27566, 31806, 29557,
+       22265, 23994, 24604, 29618, 29801, 32666, 32838, 37428, 38646, 38728, 38936, 20363,
+       31150, 37300, 38584, 24801, 20102, 20698, 23534, 23615, 26009, 29134, 30274, 34044,
+       36988, 26248, 38446, 21129, 26491, 26611, 27969, 28316, 29705, 30041, 30827, 32016,
+       39006, 25134, 38520, 20523, 23833, 28138, 36650, 24459, 24900, 26647, 38534, 21033,
+       21519, 23653, 26131, 26446, 26792, 27877, 29702, 30178, 32633, 35023, 35041, 38626,
+       21311, 28346, 21533, 29136, 29848, 34298, 38563, 40023, 40607, 26519, 28107, 33256,
+       31520, 31890, 29376, 28825, 35672, 20160, 33590, 21050, 20999, 24230, 25299, 31958,
+       23429, 27934, 26292, 36667, 38477, 24275, 20800, 21952, 22618, 26228, 20958, 29482,
+       30410, 31036, 31070, 31077, 31119, 38742, 31934, 34322, 35576, 36920, 37117, 39151,
+       39164, 39208, 40372, 37086, 38583, 20398, 20711, 20813, 21193, 21220, 21329, 21917,
+       22022, 22120, 22592, 22696, 23652, 24724, 24936, 24974, 25074, 25935, 26082, 26257,
+       26757, 28023, 28186, 28450, 29038, 29227, 29730, 30865, 31049, 31048, 31056, 31062,
+       31117, 31118, 31296, 31361, 31680, 32265, 32321, 32626, 32773, 33261, 33401, 33879,
+       35088, 35222, 35585, 35641, 36051, 36104, 36790, 38627, 38911, 38971, 24693, 148206,
+       33304, 20006, 20917, 20840, 20352, 20805, 20864, 21191, 21242, 21845, 21913, 21986,
+       22707, 22852, 22868, 23138, 23336, 24274, 24281, 24425, 24493, 24792, 24910, 24840,
+       24928, 25140, 25540, 25628, 25682, 25942, 26395, 26454, 28379, 28363, 28702, 30631,
+       29237, 29359, 29809, 29958, 30011, 30237, 30239, 30427, 30452, 30538, 30528, 30924,
+       31409, 31867, 32091, 32574, 33618, 33775, 34681, 35137, 35206, 35519, 35531, 35565,
+       35722, 36664, 36978, 37273, 37494, 38524, 38875, 38923, 39698, 141386, 141380, 144341,
+       15261, 16408, 16441, 152137, 154832, 163539, 40771, 40846, 102, 102, 102, 105, 102,
+       108, 102, 102, 108, 1396, 1398, 1396, 1381, 1396, 1387, 1406, 1398, 1396, 1389,
+       1497, 1460, 1522, 1463, 1506, 1492, 1499, 1500, 1501, 1512, 1514, 1513, 1473, 1513,
+       1474, 1513, 1468, 1473, 1513, 1468, 1474, 1488, 1463, 1488, 1464, 1488, 1468, 1489,
+       1468, 1490, 1468, 1491, 1468, 1492, 1468, 1493, 1468, 1494, 1468, 1496, 1468, 1497,
+       1468, 1498, 1468, 1499, 1468, 1500, 1468, 1502, 1468, 1504, 1468, 1505, 1468, 1507,
+       1468, 1508, 1468, 1510, 1468, 1511, 1468, 1512, 1468, 1514, 1468, 1493, 1465, 1489,
+       1471, 1499, 1471, 1508, 1471, 1488, 1500, 1649, 1659, 1662, 1664, 1658, 1663, 1657,
+       1700, 1702, 1668, 1667, 1670, 1671, 1677, 1676, 1678, 1672, 1688, 1681, 1705, 1711,
+       1715, 1713, 1722, 1723, 1728, 1729, 1726, 1746, 1747, 1709, 1734, 1736, 1739, 1733,
+       1737, 1744, 1609, 1574, 1575, 1574, 1749, 1574, 1608, 1574, 1735, 1574, 1734, 1574,
+       1736, 1574, 1744, 1574, 1609, 1740, 1574, 1580, 1574, 1581, 1574, 1605, 1574, 1610,
+       1576, 1580, 1576, 1581, 1576, 1582, 1576, 1605, 1576, 1609, 1576, 1610, 1578, 1580,
+       1578, 1581, 1578, 1582, 1578, 1605, 1578, 1609, 1578, 1610, 1579, 1580, 1579, 1605,
+       1579, 1609, 1579, 1610, 1580, 1581, 1580, 1605, 1581, 1605, 1582, 1580, 1582, 1581,
+       1582, 1605, 1587, 1580, 1587, 1581, 1587, 1582, 1587, 1605, 1589, 1581, 1589, 1605,
+       1590, 1580, 1590, 1581, 1590, 1582, 1590, 1605, 1591, 1581, 1591, 1605, 1592, 1605,
+       1593, 1580, 1593, 1605, 1594, 1580, 1594, 1605, 1601, 1580, 1601, 1581, 1601, 1582,
+       1601, 1605, 1601, 1609, 1601, 1610, 1602, 1581, 1602, 1605, 1602, 1609, 1602, 1610,
+       1603, 1575, 1603, 1580, 1603, 1581, 1603, 1582, 1603, 1604, 1603, 1605, 1603, 1609,
+       1603, 1610, 1604, 1580, 1604, 1581, 1604, 1582, 1604, 1605, 1604, 1609, 1604, 1610,
+       1605, 1580, 1605, 1605, 1605, 1609, 1605, 1610, 1606, 1580, 1606, 1581, 1606, 1582,
+       1606, 1605, 1606, 1609, 1606, 1610, 1607, 1580, 1607, 1605, 1607, 1609, 1607, 1610,
+       1610, 1581, 1610, 1582, 1610, 1609, 1584, 1648, 1585, 1648, 1609, 1648, 32, 1612,
+       1617, 32, 1613, 1617, 32, 1614, 1617, 32, 1615, 1617, 32, 1616, 1617, 32, 1617,
+       1648, 1574, 1585, 1574, 1586, 1574, 1606, 1576, 1585, 1576, 1586, 1576, 1606, 1578,
+       1585, 1578, 1586, 1578, 1606, 1579, 1585, 1579, 1586, 1579, 1606, 1605, 1575, 1606,
+       1585, 1606, 1586, 1606, 1606, 1610, 1585, 1610, 1586, 1574, 1582, 1574, 1607, 1576,
+       1607, 1578, 1607, 1589, 1582, 1604, 1607, 1606, 1607, 1607, 1648, 1579, 1607, 1587,
+       1607, 1588, 1605, 1588, 1607, 1600, 1614, 1617, 1600, 1615, 1617, 1600, 1616, 1617,
+       1591, 1609, 1591, 1610, 1593, 1609, 1593, 1610, 1594, 1609, 1594, 1610, 1587, 1609,
+       1587, 1610, 1588, 1609, 1588, 1610, 1581, 1609, 1580, 1609, 1580, 1610, 1582, 1609,
+       1589, 1609, 1589, 1610, 1590, 1609, 1590, 1610, 1588, 1580, 1588, 1581, 1588, 1582,
+       1588, 1585, 1587, 1585, 1589, 1585, 1590, 1585, 1575, 1611, 1578, 1580, 1605, 1578,
+       1581, 1580, 1578, 1581, 1605, 1578, 1582, 1605, 1578, 1605, 1580, 1578, 1605, 1581,
+       1578, 1605, 1582, 1581, 1605, 1610, 1581, 1605, 1609, 1587, 1581, 1580, 1587, 1580,
+       1581, 1587, 1580, 1609, 1587, 1605, 1581, 1587, 1605, 1580, 1587, 1605, 1605, 1589,
+       1581, 1581, 1589, 1605, 1605, 1588, 1581, 1605, 1588, 1580, 1610, 1588, 1605, 1582,
+       1588, 1605, 1605, 1590, 1581, 1609, 1590, 1582, 1605, 1591, 1605, 1581, 1591, 1605,
+       1605, 1591, 1605, 1610, 1593, 1580, 1605, 1593, 1605, 1605, 1593, 1605, 1609, 1594,
+       1605, 1605, 1594, 1605, 1610, 1594, 1605, 1609, 1601, 1582, 1605, 1602, 1605, 1581,
+       1602, 1605, 1605, 1604, 1581, 1605, 1604, 1581, 1610, 1604, 1581, 1609, 1604, 1580,
+       1580, 1604, 1582, 1605, 1604, 1605, 1581, 1605, 1581, 1580, 1605, 1581, 1610, 1605,
+       1580, 1581, 1605, 1582, 1605, 1605, 1580, 1582, 1607, 1605, 1580, 1607, 1605, 1605,
+       1606, 1581, 1605, 1606, 1581, 1609, 1606, 1580, 1605, 1606, 1580, 1609, 1606, 1605,
+       1610, 1606, 1605, 1609, 1610, 1605, 1605, 1576, 1582, 1610, 1578, 1580, 1610, 1578,
+       1580, 1609, 1578, 1582, 1610, 1578, 1582, 1609, 1578, 1605, 1610, 1578, 1605, 1609,
+       1580, 1605, 1610, 1580, 1581, 1609, 1580, 1605, 1609, 1587, 1582, 1609, 1589, 1581,
+       1610, 1588, 1581, 1610, 1590, 1581, 1610, 1604, 1580, 1610, 1604, 1605, 1610, 1610,
+       1580, 1610, 1610, 1605, 1610, 1605, 1605, 1610, 1602, 1605, 1610, 1606, 1581, 1610,
+       1593, 1605, 1610, 1603, 1605, 1610, 1606, 1580, 1581, 1605, 1582, 1610, 1604, 1580,
+       1605, 1603, 1605, 1605, 1580, 1581, 1610, 1581, 1580, 1610, 1605, 1580, 1610, 1601,
+       1605, 1610, 1576, 1581, 1610, 1587, 1582, 1610, 1606, 1580, 1610, 1589, 1604, 1746,
+       1602, 1604, 1746, 1575, 1604, 1604, 1607, 1575, 1603, 1576, 1585, 1605, 1581, 1605,
+       1583, 1589, 1604, 1593, 1605, 1585, 1587, 1608, 1604, 1593, 1604, 1610, 1607, 1608,
+       1587, 1604, 1605, 1589, 1604, 1609, 1589, 1604, 1609, 32, 1575, 1604, 1604, 1607,
+       32, 1593, 1604, 1610, 1607, 32, 1608, 1587, 1604, 1605, 1580, 1604, 32, 1580, 1604,
+       1575, 1604, 1607, 1585, 1740, 1575, 1604, 44, 12289, 12310, 12311, 8212, 8211, 95,
+       123, 125, 12308, 12309, 12304, 12305, 12298, 12299, 12300, 12301, 12302, 12303,
+       91, 93, 35, 38, 42, 45, 60, 62, 92, 36, 37, 64, 32, 1611, 1600, 1611, 1600, 1617,
+       32, 1618, 1600, 1618, 1569, 1570, 1571, 1572, 1573, 1577, 1604, 1570, 1604, 1571,
+       1604, 1573, 34, 39, 94, 124, 126, 10629, 10630, 12539, 12453, 12515, 162, 163, 172,
+       166, 165, 8361, 9474, 8592, 8593, 8594, 8595, 9632, 9675, 66600, 66601, 66602, 66603,
+       66604, 66605, 66606, 66607, 66608, 66609, 66610, 66611, 66612, 66613, 66614, 66615,
+       66616, 66617, 66618, 66619, 66620, 66621, 66622, 66623, 66624, 66625, 66626, 66627,
+       66628, 66629, 66630, 66631, 66632, 66633, 66634, 66635, 66636, 66637, 66638, 66639,
+       66776, 66777, 66778, 66779, 66780, 66781, 66782, 66783, 66784, 66785, 66786, 66787,
+       66788, 66789, 66790, 66791, 66792, 66793, 66794, 66795, 66796, 66797, 66798, 66799,
+       66800, 66801, 66802, 66803, 66804, 66805, 66806, 66807, 66808, 66809, 66810, 66811,
+       66967, 66968, 66969, 66970, 66971, 66972, 66973, 66974, 66975, 66976, 66977, 66979,
+       66980, 66981, 66982, 66983, 66984, 66985, 66986, 66987, 66988, 66989, 66990, 66991,
+       66992, 66993, 66995, 66996, 66997, 66998, 66999, 67000, 67001, 67003, 67004, 720,
+       721, 665, 675, 43878, 677, 676, 7569, 600, 606, 681, 612, 610, 667, 668, 615, 644,
+       682, 683, 122628, 42894, 622, 122629, 654, 122630, 630, 631, 634, 122632, 638, 680,
+       678, 43879, 679, 11377, 655, 673, 674, 664, 448, 449, 450, 122634, 122654, 68800,
+       68801, 68802, 68803, 68804, 68805, 68806, 68807, 68808, 68809, 68810, 68811, 68812,
+       68813, 68814, 68815, 68816, 68817, 68818, 68819, 68820, 68821, 68822, 68823, 68824,
+       68825, 68826, 68827, 68828, 68829, 68830, 68831, 68832, 68833, 68834, 68835, 68836,
+       68837, 68838, 68839, 68840, 68841, 68842, 68843, 68844, 68845, 68846, 68847, 68848,
+       68849, 68850, 71872, 71873, 71874, 71875, 71876, 71877, 71878, 71879, 71880, 71881,
+       71882, 71883, 71884, 71885, 71886, 71887, 71888, 71889, 71890, 71891, 71892, 71893,
+       71894, 71895, 71896, 71897, 71898, 71899, 71900, 71901, 71902, 71903, 93792, 93793,
+       93794, 93795, 93796, 93797, 93798, 93799, 93800, 93801, 93802, 93803, 93804, 93805,
+       93806, 93807, 93808, 93809, 93810, 93811, 93812, 93813, 93814, 93815, 93816, 93817,
+       93818, 93819, 93820, 93821, 93822, 93823, 119127, 119141, 119128, 119141, 119128,
+       119141, 119150, 119128, 119141, 119151, 119128, 119141, 119152, 119128, 119141,
+       119153, 119128, 119141, 119154, 119225, 119141, 119226, 119141, 119225, 119141,
+       119150, 119226, 119141, 119150, 119225, 119141, 119151, 119226, 119141, 119151,
+       305, 567, 8711, 8706, 1231, 125218, 125219, 125220, 125221, 125222, 125223, 125224,
+       125225, 125226, 125227, 125228, 125229, 125230, 125231, 125232, 125233, 125234,
+       125235, 125236, 125237, 125238, 125239, 125240, 125241, 125242, 125243, 125244,
+       125245, 125246, 125247, 125248, 125249, 125250, 125251, 1646, 1697, 1647, 48, 44,
+       49, 44, 50, 44, 51, 44, 52, 44, 53, 44, 54, 44, 55, 44, 56, 44, 57, 44, 12308, 115,
+       12309, 119, 122, 104, 118, 115, 100, 112, 112, 118, 119, 99, 109, 114, 100, 106,
+       12411, 12363, 12467, 12467, 23383, 21452, 22810, 35299, 20132, 26144, 28961, 21069,
+       24460, 20877, 26032, 21021, 32066, 36009, 22768, 21561, 28436, 25237, 25429, 36938,
+       25351, 25171, 31105, 31354, 21512, 28288, 30003, 21106, 21942, 37197, 12308, 26412,
+       12309, 12308, 19977, 12309, 12308, 20108, 12309, 12308, 23433, 12309, 12308, 28857,
+       12309, 12308, 25171, 12309, 12308, 30423, 12309, 12308, 21213, 12309, 12308, 25943,
+       12309, 24471, 21487, 20029, 20024, 20033, 131362, 20320, 20411, 20482, 20602, 20633,
+       20687, 13470, 132666, 20820, 20836, 20855, 132380, 13497, 20839, 132427, 20887,
+       20900, 20172, 20908, 168415, 20995, 13535, 21051, 21062, 21111, 13589, 21253, 21254,
+       21321, 21338, 21363, 21373, 21375, 133676, 28784, 21450, 21471, 133987, 21483, 21489,
+       21510, 21662, 21560, 21576, 21608, 21666, 21750, 21776, 21843, 21859, 21892, 21931,
+       21939, 21954, 22294, 22295, 22097, 22132, 22766, 22478, 22516, 22541, 22411, 22578,
+       22577, 22700, 136420, 22770, 22775, 22790, 22818, 22882, 136872, 136938, 23020,
+       23067, 23079, 23000, 23142, 14062, 23304, 23358, 137672, 23491, 23512, 23539, 138008,
+       23551, 23558, 14209, 23648, 23744, 23693, 138724, 23875, 138726, 23918, 23915, 23932,
+       24033, 24034, 14383, 24061, 24104, 24125, 24169, 14434, 139651, 14460, 24240, 24243,
+       24246, 172946, 140081, 33281, 24354, 14535, 144056, 156122, 24418, 24427, 14563,
+       24474, 24525, 24535, 24569, 24705, 14650, 14620, 141012, 24775, 24904, 24908, 24954,
+       25010, 24996, 25007, 25054, 25115, 25181, 25265, 25300, 25424, 142092, 25405, 25340,
+       25448, 25475, 25572, 142321, 25634, 25541, 25513, 14894, 25705, 25726, 25757, 25719,
+       14956, 25964, 143370, 26083, 26360, 26185, 15129, 15112, 15076, 20882, 20885, 26368,
+       26268, 32941, 17369, 26401, 26462, 26451, 144323, 15177, 26618, 26501, 26706, 144493,
+       26766, 26655, 26900, 26946, 27043, 27114, 27304, 145059, 27355, 15384, 27425, 145575,
+       27476, 15438, 27506, 27551, 27579, 146061, 138507, 146170, 27726, 146620, 27839,
+       27853, 27751, 27926, 27966, 28009, 28024, 28037, 146718, 27956, 28207, 28270, 15667,
+       28359, 147153, 28153, 28526, 147294, 147342, 28614, 28729, 28699, 15766, 28746,
+       28797, 28791, 28845, 132389, 28997, 148067, 29084, 29224, 29264, 149000, 29312,
+       29333, 149301, 149524, 29562, 29579, 16044, 29605, 16056, 29767, 29788, 29829, 29898,
+       16155, 29988, 150582, 30014, 150674, 139679, 30224, 151457, 151480, 151620, 16380,
+       16392, 151795, 151794, 151833, 151859, 30494, 30495, 30603, 16454, 16534, 152605,
+       30798, 16611, 153126, 153242, 153285, 31211, 16687, 31306, 31311, 153980, 154279,
+       16898, 154539, 31686, 31689, 16935, 154752, 31954, 17056, 31976, 31971, 32000, 155526,
+       32099, 17153, 32199, 32258, 32325, 17204, 156200, 156231, 17241, 156377, 32634,
+       156478, 32661, 32762, 156890, 156963, 32864, 157096, 32880, 144223, 17365, 32946,
+       33027, 17419, 33086, 23221, 157607, 157621, 144275, 144284, 33284, 36766, 17515,
+       33425, 33419, 33437, 21171, 33457, 33459, 33469, 33510, 158524, 33565, 33635, 33709,
+       33571, 33725, 33767, 33619, 33738, 33740, 33756, 158774, 159083, 158933, 17707,
+       34033, 34035, 34070, 160714, 34148, 159532, 17757, 17761, 159665, 159954, 17771,
+       34384, 34407, 34409, 34473, 34440, 34574, 34530, 34600, 34667, 34694, 34785, 34817,
+       17913, 34912, 161383, 35031, 35038, 17973, 35066, 13499, 161966, 162150, 18110,
+       18119, 35488, 162984, 36011, 36033, 36123, 36215, 163631, 133124, 36299, 36284,
+       36336, 133342, 36564, 165330, 165357, 37012, 37105, 37137, 165678, 37147, 37432,
+       37591, 37592, 37500, 37881, 37909, 166906, 38283, 18837, 38327, 167287, 18918, 38595,
+       23986, 38691, 168261, 168474, 19054, 19062, 38880, 168970, 19122, 169110, 38953,
+       169398, 39138, 19251, 39209, 39335, 39362, 39422, 19406, 170800, 40000, 40189, 19662,
+       19693, 40295, 172238, 19704, 172293, 172558, 172689, 19798, 40702, 40709, 40719,
+       40726, 173568,
+
+};
+const uint32_t table[8000][2] =
+{
+       {0, 1}, {65, 16777219}, {66, 16777475}, {67, 16777731},
+       {68, 16777987}, {69, 16778243}, {70, 16778499}, {71, 16778755},
+       {72, 16779011}, {73, 16779267}, {74, 16779523}, {75, 16779779},
+       {76, 16780035}, {77, 16780291}, {78, 16780547}, {79, 16780803},
+       {80, 16781059}, {81, 16781315}, {82, 16781571}, {83, 16781827},
+       {84, 16782083}, {85, 16782339}, {86, 16782595}, {87, 16782851},
+       {88, 16783107}, {89, 16783363}, {90, 16783619}, {91, 1},
+       {128, 2}, {160, 16783875}, {161, 1}, {168, 33561347},
+       {169, 1}, {170, 16777219}, {171, 1}, {173, 0},
+       {174, 1}, {175, 33561859}, {176, 1}, {178, 16785155},
+       {179, 16785411}, {180, 33562883}, {181, 16786179}, {182, 1},
+       {184, 33563651}, {185, 16786947}, {186, 16780803}, {187, 1},
+       {188, 50341635}, {189, 50342403}, {190, 50343171}, {191, 1},
+       {192, 16789507}, {193, 16789763}, {194, 16790019}, {195, 16790275},
+       {196, 16790531}, {197, 16790787}, {198, 16791043}, {199, 16791299},
+       {200, 16791555}, {201, 16791811}, {202, 16792067}, {203, 16792323},
+       {204, 16792579}, {205, 16792835}, {206, 16793091}, {207, 16793347},
+       {208, 16793603}, {209, 16793859}, {210, 16794115}, {211, 16794371},
+       {212, 16794627}, {213, 16794883}, {214, 16795139}, {215, 1},
+       {216, 16795395}, {217, 16795651}, {218, 16795907}, {219, 16796163},
+       {220, 16796419}, {221, 16796675}, {222, 16796931}, {223, 1},
+       {256, 16797187}, {257, 1}, {258, 16797443}, {259, 1},
+       {260, 16797699}, {261, 1}, {262, 16797955}, {263, 1},
+       {264, 16798211}, {265, 1}, {266, 16798467}, {267, 1},
+       {268, 16798723}, {269, 1}, {270, 16798979}, {271, 1},
+       {272, 16799235}, {273, 1}, {274, 16799491}, {275, 1},
+       {276, 16799747}, {277, 1}, {278, 16800003}, {279, 1},
+       {280, 16800259}, {281, 1}, {282, 16800515}, {283, 1},
+       {284, 16800771}, {285, 1}, {286, 16801027}, {287, 1},
+       {288, 16801283}, {289, 1}, {290, 16801539}, {291, 1},
+       {292, 16801795}, {293, 1}, {294, 16802051}, {295, 1},
+       {296, 16802307}, {297, 1}, {298, 16802563}, {299, 1},
+       {300, 16802819}, {301, 1}, {302, 16803075}, {303, 1},
+       {304, 33580547}, {305, 1}, {306, 33556483}, {308, 16803843},
+       {309, 1}, {310, 16804099}, {311, 1}, {313, 16804355},
+       {314, 1}, {315, 16804611}, {316, 1}, {317, 16804867},
+       {318, 1}, {319, 33582339}, {321, 16805635}, {322, 1},
+       {323, 16805891}, {324, 1}, {325, 16806147}, {326, 1},
+       {327, 16806403}, {328, 1}, {329, 33583875}, {330, 16807171},
+       {331, 1}, {332, 16807427}, {333, 1}, {334, 16807683},
+       {335, 1}, {336, 16807939}, {337, 1}, {338, 16808195},
+       {339, 1}, {340, 16808451}, {341, 1}, {342, 16808707},
+       {343, 1}, {344, 16808963}, {345, 1}, {346, 16809219},
+       {347, 1}, {348, 16809475}, {349, 1}, {350, 16809731},
+       {351, 1}, {352, 16809987}, {353, 1}, {354, 16810243},
+       {355, 1}, {356, 16810499}, {357, 1}, {358, 16810755},
+       {359, 1}, {360, 16811011}, {361, 1}, {362, 16811267},
+       {363, 1}, {364, 16811523}, {365, 1}, {366, 16811779},
+       {367, 1}, {368, 16812035}, {369, 1}, {370, 16812291},
+       {371, 1}, {372, 16812547}, {373, 1}, {374, 16812803},
+       {375, 1}, {376, 16813059}, {377, 16813315}, {378, 1},
+       {379, 16813571}, {380, 1}, {381, 16813827}, {382, 1},
+       {383, 16781827}, {384, 1}, {385, 16814083}, {386, 16814339},
+       {387, 1}, {388, 16814595}, {389, 1}, {390, 16814851},
+       {391, 16815107}, {392, 1}, {393, 16815363}, {394, 16815619},
+       {395, 16815875}, {396, 1}, {398, 16816131}, {399, 16816387},
+       {400, 16816643}, {401, 16816899}, {402, 1}, {403, 16817155},
+       {404, 16817411}, {405, 1}, {406, 16817667}, {407, 16817923},
+       {408, 16818179}, {409, 1}, {412, 16818435}, {413, 16818691},
+       {414, 1}, {415, 16818947}, {416, 16819203}, {417, 1},
+       {418, 16819459}, {419, 1}, {420, 16819715}, {421, 1},
+       {422, 16819971}, {423, 16820227}, {424, 1}, {425, 16820483},
+       {426, 1}, {428, 16820739}, {429, 1}, {430, 16820995},
+       {431, 16821251}, {432, 1}, {433, 16821507}, {434, 16821763},
+       {435, 16822019}, {436, 1}, {437, 16822275}, {438, 1},
+       {439, 16822531}, {440, 16822787}, {441, 1}, {444, 16823043},
+       {445, 1}, {452, 33600515}, {455, 33601027}, {458, 33601539},
+       {461, 16824835}, {462, 1}, {463, 16825091}, {464, 1},
+       {465, 16825347}, {466, 1}, {467, 16825603}, {468, 1},
+       {469, 16825859}, {470, 1}, {471, 16826115}, {472, 1},
+       {473, 16826371}, {474, 1}, {475, 16826627}, {476, 1},
+       {478, 16826883}, {479, 1}, {480, 16827139}, {481, 1},
+       {482, 16827395}, {483, 1}, {484, 16827651}, {485, 1},
+       {486, 16827907}, {487, 1}, {488, 16828163}, {489, 1},
+       {490, 16828419}, {491, 1}, {492, 16828675}, {493, 1},
+       {494, 16828931}, {495, 1}, {497, 33606403}, {500, 16829699},
+       {501, 1}, {502, 16829955}, {503, 16830211}, {504, 16830467},
+       {505, 1}, {506, 16830723}, {507, 1}, {508, 16830979},
+       {509, 1}, {510, 16831235}, {511, 1}, {512, 16831491},
+       {513, 1}, {514, 16831747}, {515, 1}, {516, 16832003},
+       {517, 1}, {518, 16832259}, {519, 1}, {520, 16832515},
+       {521, 1}, {522, 16832771}, {523, 1}, {524, 16833027},
+       {525, 1}, {526, 16833283}, {527, 1}, {528, 16833539},
+       {529, 1}, {530, 16833795}, {531, 1}, {532, 16834051},
+       {533, 1}, {534, 16834307}, {535, 1}, {536, 16834563},
+       {537, 1}, {538, 16834819}, {539, 1}, {540, 16835075},
+       {541, 1}, {542, 16835331}, {543, 1}, {544, 16835587},
+       {545, 1}, {546, 16835843}, {547, 1}, {548, 16836099},
+       {549, 1}, {550, 16836355}, {551, 1}, {552, 16836611},
+       {553, 1}, {554, 16836867}, {555, 1}, {556, 16837123},
+       {557, 1}, {558, 16837379}, {559, 1}, {560, 16837635},
+       {561, 1}, {562, 16837891}, {563, 1}, {570, 16838147},
+       {571, 16838403}, {572, 1}, {573, 16838659}, {574, 16838915},
+       {575, 1}, {577, 16839171}, {578, 1}, {579, 16839427},
+       {580, 16839683}, {581, 16839939}, {582, 16840195}, {583, 1},
+       {584, 16840451}, {585, 1}, {586, 16840707}, {587, 1},
+       {588, 16840963}, {589, 1}, {590, 16841219}, {591, 1},
+       {688, 16779011}, {689, 16841475}, {690, 16779523}, {691, 16781571},
+       {692, 16841731}, {693, 16841987}, {694, 16842243}, {695, 16782851},
+       {696, 16783363}, {697, 1}, {728, 33619715}, {729, 33620227},
+       {730, 33620739}, {731, 33621251}, {732, 33621763}, {733, 33622275},
+       {734, 1}, {736, 16817411}, {737, 16780035}, {738, 16781827},
+       {739, 16783107}, {740, 16845571}, {741, 1}, {832, 16845827},
+       {833, 16785923}, {834, 1}, {835, 16846083}, {836, 33623555},
+       {837, 16846851}, {838, 1}, {847, 0}, {848, 1},
+       {880, 16847107}, {881, 1}, {882, 16847363}, {883, 1},
+       {884, 16847619}, {885, 1}, {886, 16847875}, {887, 1},
+       {888, 2}, {890, 33625347}, {891, 1}, {894, 16848643},
+       {895, 16848899}, {896, 2}, {900, 33562883}, {901, 50403587},
+       {902, 16849923}, {903, 16805379}, {904, 16850179}, {905, 16850435},
+       {906, 16850691}, {907, 2}, {908, 16850947}, {909, 2},
+       {910, 16851203}, {911, 16851459}, {912, 1}, {913, 16851715},
+       {914, 16851971}, {915, 16852227}, {916, 16852483}, {917, 16852739},
+       {918, 16852995}, {919, 16853251}, {920, 16853507}, {921, 16846851},
+       {922, 16853763}, {923, 16854019}, {924, 16786179}, {925, 16854275},
+       {926, 16854531}, {927, 16854787}, {928, 16855043}, {929, 16855299},
+       {930, 2}, {931, 16855555}, {932, 16855811}, {933, 16856067},
+       {934, 16856323}, {935, 16856579}, {936, 16856835}, {937, 16857091},
+       {938, 16857347}, {939, 16857603}, {940, 1}, {975, 16857859},
+       {976, 16851971}, {977, 16853507}, {978, 16856067}, {979, 16851203},
+       {980, 16857603}, {981, 16856323}, {982, 16855043}, {983, 1},
+       {984, 16858115}, {985, 1}, {986, 16858371}, {987, 1},
+       {988, 16858627}, {989, 1}, {990, 16858883}, {991, 1},
+       {992, 16859139}, {993, 1}, {994, 16859395}, {995, 1},
+       {996, 16859651}, {997, 1}, {998, 16859907}, {999, 1},
+       {1000, 16860163}, {1001, 1}, {1002, 16860419}, {1003, 1},
+       {1004, 16860675}, {1005, 1}, {1006, 16860931}, {1007, 1},
+       {1008, 16853763}, {1009, 16855299}, {1010, 16855555}, {1011, 1},
+       {1012, 16853507}, {1013, 16852739}, {1014, 1}, {1015, 16861187},
+       {1016, 1}, {1017, 16855555}, {1018, 16861443}, {1019, 1},
+       {1021, 16861699}, {1022, 16861955}, {1023, 16862211}, {1024, 16862467},
+       {1025, 16862723}, {1026, 16862979}, {1027, 16863235}, {1028, 16863491},
+       {1029, 16863747}, {1030, 16864003}, {1031, 16864259}, {1032, 16864515},
+       {1033, 16864771}, {1034, 16865027}, {1035, 16865283}, {1036, 16865539},
+       {1037, 16865795}, {1038, 16866051}, {1039, 16866307}, {1040, 16866563},
+       {1041, 16866819}, {1042, 16867075}, {1043, 16867331}, {1044, 16867587},
+       {1045, 16867843}, {1046, 16868099}, {1047, 16868355}, {1048, 16868611},
+       {1049, 16868867}, {1050, 16869123}, {1051, 16869379}, {1052, 16869635},
+       {1053, 16869891}, {1054, 16870147}, {1055, 16870403}, {1056, 16870659},
+       {1057, 16870915}, {1058, 16871171}, {1059, 16871427}, {1060, 16871683},
+       {1061, 16871939}, {1062, 16872195}, {1063, 16872451}, {1064, 16872707},
+       {1065, 16872963}, {1066, 16873219}, {1067, 16873475}, {1068, 16873731},
+       {1069, 16873987}, {1070, 16874243}, {1071, 16874499}, {1072, 1},
+       {1120, 16874755}, {1121, 1}, {1122, 16875011}, {1123, 1},
+       {1124, 16875267}, {1125, 1}, {1126, 16875523}, {1127, 1},
+       {1128, 16875779}, {1129, 1}, {1130, 16876035}, {1131, 1},
+       {1132, 16876291}, {1133, 1}, {1134, 16876547}, {1135, 1},
+       {1136, 16876803}, {1137, 1}, {1138, 16877059}, {1139, 1},
+       {1140, 16877315}, {1141, 1}, {1142, 16877571}, {1143, 1},
+       {1144, 16877827}, {1145, 1}, {1146, 16878083}, {1147, 1},
+       {1148, 16878339}, {1149, 1}, {1150, 16878595}, {1151, 1},
+       {1152, 16878851}, {1153, 1}, {1162, 16879107}, {1163, 1},
+       {1164, 16879363}, {1165, 1}, {1166, 16879619}, {1167, 1},
+       {1168, 16879875}, {1169, 1}, {1170, 16880131}, {1171, 1},
+       {1172, 16880387}, {1173, 1}, {1174, 16880643}, {1175, 1},
+       {1176, 16880899}, {1177, 1}, {1178, 16881155}, {1179, 1},
+       {1180, 16881411}, {1181, 1}, {1182, 16881667}, {1183, 1},
+       {1184, 16881923}, {1185, 1}, {1186, 16882179}, {1187, 1},
+       {1188, 16882435}, {1189, 1}, {1190, 16882691}, {1191, 1},
+       {1192, 16882947}, {1193, 1}, {1194, 16883203}, {1195, 1},
+       {1196, 16883459}, {1197, 1}, {1198, 16883715}, {1199, 1},
+       {1200, 16883971}, {1201, 1}, {1202, 16884227}, {1203, 1},
+       {1204, 16884483}, {1205, 1}, {1206, 16884739}, {1207, 1},
+       {1208, 16884995}, {1209, 1}, {1210, 16885251}, {1211, 1},
+       {1212, 16885507}, {1213, 1}, {1214, 16885763}, {1215, 1},
+       {1216, 2}, {1217, 16886019}, {1218, 1}, {1219, 16886275},
+       {1220, 1}, {1221, 16886531}, {1222, 1}, {1223, 16886787},
+       {1224, 1}, {1225, 16887043}, {1226, 1}, {1227, 16887299},
+       {1228, 1}, {1229, 16887555}, {1230, 1}, {1232, 16887811},
+       {1233, 1}, {1234, 16888067}, {1235, 1}, {1236, 16888323},
+       {1237, 1}, {1238, 16888579}, {1239, 1}, {1240, 16888835},
+       {1241, 1}, {1242, 16889091}, {1243, 1}, {1244, 16889347},
+       {1245, 1}, {1246, 16889603}, {1247, 1}, {1248, 16889859},
+       {1249, 1}, {1250, 16890115}, {1251, 1}, {1252, 16890371},
+       {1253, 1}, {1254, 16890627}, {1255, 1}, {1256, 16890883},
+       {1257, 1}, {1258, 16891139}, {1259, 1}, {1260, 16891395},
+       {1261, 1}, {1262, 16891651}, {1263, 1}, {1264, 16891907},
+       {1265, 1}, {1266, 16892163}, {1267, 1}, {1268, 16892419},
+       {1269, 1}, {1270, 16892675}, {1271, 1}, {1272, 16892931},
+       {1273, 1}, {1274, 16893187}, {1275, 1}, {1276, 16893443},
+       {1277, 1}, {1278, 16893699}, {1279, 1}, {1280, 16893955},
+       {1281, 1}, {1282, 16894211}, {1283, 1}, {1284, 16894467},
+       {1285, 1}, {1286, 16894723}, {1287, 1}, {1288, 16894979},
+       {1289, 1}, {1290, 16895235}, {1291, 1}, {1292, 16895491},
+       {1293, 1}, {1294, 16895747}, {1295, 1}, {1296, 16896003},
+       {1297, 1}, {1298, 16896259}, {1299, 1}, {1300, 16896515},
+       {1301, 1}, {1302, 16896771}, {1303, 1}, {1304, 16897027},
+       {1305, 1}, {1306, 16897283}, {1307, 1}, {1308, 16897539},
+       {1309, 1}, {1310, 16897795}, {1311, 1}, {1312, 16898051},
+       {1313, 1}, {1314, 16898307}, {1315, 1}, {1316, 16898563},
+       {1317, 1}, {1318, 16898819}, {1319, 1}, {1320, 16899075},
+       {1321, 1}, {1322, 16899331}, {1323, 1}, {1324, 16899587},
+       {1325, 1}, {1326, 16899843}, {1327, 1}, {1328, 2},
+       {1329, 16900099}, {1330, 16900355}, {1331, 16900611}, {1332, 16900867},
+       {1333, 16901123}, {1334, 16901379}, {1335, 16901635}, {1336, 16901891},
+       {1337, 16902147}, {1338, 16902403}, {1339, 16902659}, {1340, 16902915},
+       {1341, 16903171}, {1342, 16903427}, {1343, 16903683}, {1344, 16903939},
+       {1345, 16904195}, {1346, 16904451}, {1347, 16904707}, {1348, 16904963},
+       {1349, 16905219}, {1350, 16905475}, {1351, 16905731}, {1352, 16905987},
+       {1353, 16906243}, {1354, 16906499}, {1355, 16906755}, {1356, 16907011},
+       {1357, 16907267}, {1358, 16907523}, {1359, 16907779}, {1360, 16908035},
+       {1361, 16908291}, {1362, 16908547}, {1363, 16908803}, {1364, 16909059},
+       {1365, 16909315}, {1366, 16909571}, {1367, 2}, {1369, 1},
+       {1415, 33687043}, {1416, 1}, {1419, 2}, {1421, 1},
+       {1424, 2}, {1425, 1}, {1480, 2}, {1488, 1},
+       {1515, 2}, {1519, 1}, {1525, 2}, {1542, 1},
+       {1564, 2}, {1565, 1}, {1653, 33687555}, {1654, 33688067},
+       {1655, 33688579}, {1656, 33689091}, {1657, 1}, {1757, 2},
+       {1758, 1}, {1806, 2}, {1808, 1}, {1867, 2},
+       {1869, 1}, {1970, 2}, {1984, 1}, {2043, 2},
+       {2045, 1}, {2094, 2}, {2096, 1}, {2111, 2},
+       {2112, 1}, {2140, 2}, {2142, 1}, {2143, 2},
+       {2144, 1}, {2155, 2}, {2160, 1}, {2191, 2},
+       {2200, 1}, {2274, 2}, {2275, 1}, {2392, 33689603},
+       {2393, 33690115}, {2394, 33690627}, {2395, 33691139}, {2396, 33691651},
+       {2397, 33692163}, {2398, 33692675}, {2399, 33693187}, {2400, 1},
+       {2436, 2}, {2437, 1}, {2445, 2}, {2447, 1},
+       {2449, 2}, {2451, 1}, {2473, 2}, {2474, 1},
+       {2481, 2}, {2482, 1}, {2483, 2}, {2486, 1},
+       {2490, 2}, {2492, 1}, {2501, 2}, {2503, 1},
+       {2505, 2}, {2507, 1}, {2511, 2}, {2519, 1},
+       {2520, 2}, {2524, 33693699}, {2525, 33694211}, {2526, 2},
+       {2527, 33694723}, {2528, 1}, {2532, 2}, {2534, 1},
+       {2559, 2}, {2561, 1}, {2564, 2}, {2565, 1},
+       {2571, 2}, {2575, 1}, {2577, 2}, {2579, 1},
+       {2601, 2}, {2602, 1}, {2609, 2}, {2610, 1},
+       {2611, 33695235}, {2612, 2}, {2613, 1}, {2614, 33695747},
+       {2615, 2}, {2616, 1}, {2618, 2}, {2620, 1},
+       {2621, 2}, {2622, 1}, {2627, 2}, {2631, 1},
+       {2633, 2}, {2635, 1}, {2638, 2}, {2641, 1},
+       {2642, 2}, {2649, 33696259}, {2650, 33696771}, {2651, 33697283},
+       {2652, 1}, {2653, 2}, {2654, 33697795}, {2655, 2},
+       {2662, 1}, {2679, 2}, {2689, 1}, {2692, 2},
+       {2693, 1}, {2702, 2}, {2703, 1}, {2706, 2},
+       {2707, 1}, {2729, 2}, {2730, 1}, {2737, 2},
+       {2738, 1}, {2740, 2}, {2741, 1}, {2746, 2},
+       {2748, 1}, {2758, 2}, {2759, 1}, {2762, 2},
+       {2763, 1}, {2766, 2}, {2768, 1}, {2769, 2},
+       {2784, 1}, {2788, 2}, {2790, 1}, {2802, 2},
+       {2809, 1}, {2816, 2}, {2817, 1}, {2820, 2},
+       {2821, 1}, {2829, 2}, {2831, 1}, {2833, 2},
+       {2835, 1}, {2857, 2}, {2858, 1}, {2865, 2},
+       {2866, 1}, {2868, 2}, {2869, 1}, {2874, 2},
+       {2876, 1}, {2885, 2}, {2887, 1}, {2889, 2},
+       {2891, 1}, {2894, 2}, {2901, 1}, {2904, 2},
+       {2908, 33698307}, {2909, 33698819}, {2910, 2}, {2911, 1},
+       {2916, 2}, {2918, 1}, {2936, 2}, {2946, 1},
+       {2948, 2}, {2949, 1}, {2955, 2}, {2958, 1},
+       {2961, 2}, {2962, 1}, {2966, 2}, {2969, 1},
+       {2971, 2}, {2972, 1}, {2973, 2}, {2974, 1},
+       {2976, 2}, {2979, 1}, {2981, 2}, {2984, 1},
+       {2987, 2}, {2990, 1}, {3002, 2}, {3006, 1},
+       {3011, 2}, {3014, 1}, {3017, 2}, {3018, 1},
+       {3022, 2}, {3024, 1}, {3025, 2}, {3031, 1},
+       {3032, 2}, {3046, 1}, {3067, 2}, {3072, 1},
+       {3085, 2}, {3086, 1}, {3089, 2}, {3090, 1},
+       {3113, 2}, {3114, 1}, {3130, 2}, {3132, 1},
+       {3141, 2}, {3142, 1}, {3145, 2}, {3146, 1},
+       {3150, 2}, {3157, 1}, {3159, 2}, {3160, 1},
+       {3163, 2}, {3165, 1}, {3166, 2}, {3168, 1},
+       {3172, 2}, {3174, 1}, {3184, 2}, {3191, 1},
+       {3213, 2}, {3214, 1}, {3217, 2}, {3218, 1},
+       {3241, 2}, {3242, 1}, {3252, 2}, {3253, 1},
+       {3258, 2}, {3260, 1}, {3269, 2}, {3270, 1},
+       {3273, 2}, {3274, 1}, {3278, 2}, {3285, 1},
+       {3287, 2}, {3293, 1}, {3295, 2}, {3296, 1},
+       {3300, 2}, {3302, 1}, {3312, 2}, {3313, 1},
+       {3316, 2}, {3328, 1}, {3341, 2}, {3342, 1},
+       {3345, 2}, {3346, 1}, {3397, 2}, {3398, 1},
+       {3401, 2}, {3402, 1}, {3408, 2}, {3412, 1},
+       {3428, 2}, {3430, 1}, {3456, 2}, {3457, 1},
+       {3460, 2}, {3461, 1}, {3479, 2}, {3482, 1},
+       {3506, 2}, {3507, 1}, {3516, 2}, {3517, 1},
+       {3518, 2}, {3520, 1}, {3527, 2}, {3530, 1},
+       {3531, 2}, {3535, 1}, {3541, 2}, {3542, 1},
+       {3543, 2}, {3544, 1}, {3552, 2}, {3558, 1},
+       {3568, 2}, {3570, 1}, {3573, 2}, {3585, 1},
+       {3635, 33699331}, {3636, 1}, {3643, 2}, {3647, 1},
+       {3676, 2}, {3713, 1}, {3715, 2}, {3716, 1},
+       {3717, 2}, {3718, 1}, {3723, 2}, {3724, 1},
+       {3748, 2}, {3749, 1}, {3750, 2}, {3751, 1},
+       {3763, 33699843}, {3764, 1}, {3774, 2}, {3776, 1},
+       {3781, 2}, {3782, 1}, {3783, 2}, {3784, 1},
+       {3791, 2}, {3792, 1}, {3802, 2}, {3804, 33700355},
+       {3805, 33700867}, {3806, 1}, {3808, 2}, {3840, 1},
+       {3852, 16924163}, {3853, 1}, {3907, 33701635}, {3908, 1},
+       {3912, 2}, {3913, 1}, {3917, 33702147}, {3918, 1},
+       {3922, 33702659}, {3923, 1}, {3927, 33703171}, {3928, 1},
+       {3932, 33703683}, {3933, 1}, {3945, 33704195}, {3946, 1},
+       {3949, 2}, {3953, 1}, {3955, 33704707}, {3956, 1},
+       {3957, 33705219}, {3958, 33705731}, {3959, 50483459}, {3960, 33707011},
+       {3961, 50484739}, {3962, 1}, {3969, 33706499}, {3970, 1},
+       {3987, 33708291}, {3988, 1}, {3992, 2}, {3993, 1},
+       {3997, 33708803}, {3998, 1}, {4002, 33709315}, {4003, 1},
+       {4007, 33709827}, {4008, 1}, {4012, 33710339}, {4013, 1},
+       {4025, 33710851}, {4026, 1}, {4029, 2}, {4030, 1},
+       {4045, 2}, {4046, 1}, {4059, 2}, {4096, 1},
+       {4256, 2}, {4295, 16934147}, {4296, 2}, {4301, 16934403},
+       {4302, 2}, {4304, 1}, {4348, 16934659}, {4349, 1},
+       {4447, 2}, {4449, 1}, {4681, 2}, {4682, 1},
+       {4686, 2}, {4688, 1}, {4695, 2}, {4696, 1},
+       {4697, 2}, {4698, 1}, {4702, 2}, {4704, 1},
+       {4745, 2}, {4746, 1}, {4750, 2}, {4752, 1},
+       {4785, 2}, {4786, 1}, {4790, 2}, {4792, 1},
+       {4799, 2}, {4800, 1}, {4801, 2}, {4802, 1},
+       {4806, 2}, {4808, 1}, {4823, 2}, {4824, 1},
+       {4881, 2}, {4882, 1}, {4886, 2}, {4888, 1},
+       {4955, 2}, {4957, 1}, {4989, 2}, {4992, 1},
+       {5018, 2}, {5024, 1}, {5110, 2}, {5112, 16934915},
+       {5113, 16935171}, {5114, 16935427}, {5115, 16935683}, {5116, 16935939},
+       {5117, 16936195}, {5118, 2}, {5120, 1}, {5760, 2},
+       {5761, 1}, {5789, 2}, {5792, 1}, {5881, 2},
+       {5888, 1}, {5910, 2}, {5919, 1}, {5943, 2},
+       {5952, 1}, {5972, 2}, {5984, 1}, {5997, 2},
+       {5998, 1}, {6001, 2}, {6002, 1}, {6004, 2},
+       {6016, 1}, {6068, 2}, {6070, 1}, {6110, 2},
+       {6112, 1}, {6122, 2}, {6128, 1}, {6138, 2},
+       {6144, 1}, {6150, 2}, {6151, 1}, {6155, 0},
+       {6158, 2}, {6159, 0}, {6160, 1}, {6170, 2},
+       {6176, 1}, {6265, 2}, {6272, 1}, {6315, 2},
+       {6320, 1}, {6390, 2}, {6400, 1}, {6431, 2},
+       {6432, 1}, {6444, 2}, {6448, 1}, {6460, 2},
+       {6464, 1}, {6465, 2}, {6468, 1}, {6510, 2},
+       {6512, 1}, {6517, 2}, {6528, 1}, {6572, 2},
+       {6576, 1}, {6602, 2}, {6608, 1}, {6619, 2},
+       {6622, 1}, {6684, 2}, {6686, 1}, {6751, 2},
+       {6752, 1}, {6781, 2}, {6783, 1}, {6794, 2},
+       {6800, 1}, {6810, 2}, {6816, 1}, {6830, 2},
+       {6832, 1}, {6863, 2}, {6912, 1}, {6989, 2},
+       {6992, 1}, {7039, 2}, {7040, 1}, {7156, 2},
+       {7164, 1}, {7224, 2}, {7227, 1}, {7242, 2},
+       {7245, 1}, {7296, 16867075}, {7297, 16867587}, {7298, 16870147},
+       {7299, 16870915}, {7300, 16871171}, {7302, 16873219}, {7303, 16875011},
+       {7304, 16936451}, {7305, 2}, {7312, 16936707}, {7313, 16936963},
+       {7314, 16937219}, {7315, 16937475}, {7316, 16937731}, {7317, 16937987},
+       {7318, 16938243}, {7319, 16938499}, {7320, 16938755}, {7321, 16939011},
+       {7322, 16939267}, {7323, 16939523}, {7324, 16934659}, {7325, 16939779},
+       {7326, 16940035}, {7327, 16940291}, {7328, 16940547}, {7329, 16940803},
+       {7330, 16941059}, {7331, 16941315}, {7332, 16941571}, {7333, 16941827},
+       {7334, 16942083}, {7335, 16942339}, {7336, 16942595}, {7337, 16942851},
+       {7338, 16943107}, {7339, 16943363}, {7340, 16943619}, {7341, 16943875},
+       {7342, 16944131}, {7343, 16944387}, {7344, 16944643}, {7345, 16944899},
+       {7346, 16945155}, {7347, 16945411}, {7348, 16945667}, {7349, 16945923},
+       {7350, 16946179}, {7351, 16946435}, {7352, 16946691}, {7353, 16946947},
+       {7354, 16947203}, {7355, 2}, {7357, 16947459}, {7358, 16947715},
+       {7359, 16947971}, {7360, 1}, {7368, 2}, {7376, 1},
+       {7419, 2}, {7424, 1}, {7468, 16777219}, {7469, 16791043},
+       {7470, 16777475}, {7471, 1}, {7472, 16777987}, {7473, 16778243},
+       {7474, 16816131}, {7475, 16778755}, {7476, 16779011}, {7477, 16779267},
+       {7478, 16779523}, {7479, 16779779}, {7480, 16780035}, {7481, 16780291},
+       {7482, 16780547}, {7483, 1}, {7484, 16780803}, {7485, 16835843},
+       {7486, 16781059}, {7487, 16781571}, {7488, 16782083}, {7489, 16782339},
+       {7490, 16782851}, {7491, 16777219}, {7492, 16948227}, {7493, 16948483},
+       {7494, 16948739}, {7495, 16777475}, {7496, 16777987}, {7497, 16778243},
+       {7498, 16816387}, {7499, 16816643}, {7500, 16948995}, {7501, 16778755},
+       {7502, 1}, {7503, 16779779}, {7504, 16780291}, {7505, 16807171},
+       {7506, 16780803}, {7507, 16814851}, {7508, 16949251}, {7509, 16949507},
+       {7510, 16781059}, {7511, 16782083}, {7512, 16782339}, {7513, 16949763},
+       {7514, 16818435}, {7515, 16782595}, {7516, 16950019}, {7517, 16851971},
+       {7518, 16852227}, {7519, 16852483}, {7520, 16856323}, {7521, 16856579},
+       {7522, 16779267}, {7523, 16781571}, {7524, 16782339}, {7525, 16782595},
+       {7526, 16851971}, {7527, 16852227}, {7528, 16855299}, {7529, 16856323},
+       {7530, 16856579}, {7531, 1}, {7544, 16869891}, {7545, 1},
+       {7579, 16950275}, {7580, 16777731}, {7581, 16950531}, {7582, 16793603},
+       {7583, 16948995}, {7584, 16778499}, {7585, 16950787}, {7586, 16951043},
+       {7587, 16951299}, {7588, 16817923}, {7589, 16817667}, {7590, 16951555},
+       {7591, 16951811}, {7592, 16952067}, {7593, 16952323}, {7594, 16952579},
+       {7595, 16952835}, {7596, 16953091}, {7597, 16953347}, {7598, 16818691},
+       {7599, 16953603}, {7600, 16953859}, {7601, 16818947}, {7602, 16954115},
+       {7603, 16954371}, {7604, 16820483}, {7605, 16954627}, {7606, 16839683},
+       {7607, 16821507}, {7608, 16954883}, {7609, 16821763}, {7610, 16839939},
+       {7611, 16783619}, {7612, 16955139}, {7613, 16955395}, {7614, 16822531},
+       {7615, 16853507}, {7616, 1}, {7680, 16955651}, {7681, 1},
+       {7682, 16955907}, {7683, 1}, {7684, 16956163}, {7685, 1},
+       {7686, 16956419}, {7687, 1}, {7688, 16956675}, {7689, 1},
+       {7690, 16956931}, {7691, 1}, {7692, 16957187}, {7693, 1},
+       {7694, 16957443}, {7695, 1}, {7696, 16957699}, {7697, 1},
+       {7698, 16957955}, {7699, 1}, {7700, 16958211}, {7701, 1},
+       {7702, 16958467}, {7703, 1}, {7704, 16958723}, {7705, 1},
+       {7706, 16958979}, {7707, 1}, {7708, 16959235}, {7709, 1},
+       {7710, 16959491}, {7711, 1}, {7712, 16959747}, {7713, 1},
+       {7714, 16960003}, {7715, 1}, {7716, 16960259}, {7717, 1},
+       {7718, 16960515}, {7719, 1}, {7720, 16960771}, {7721, 1},
+       {7722, 16961027}, {7723, 1}, {7724, 16961283}, {7725, 1},
+       {7726, 16961539}, {7727, 1}, {7728, 16961795}, {7729, 1},
+       {7730, 16962051}, {7731, 1}, {7732, 16962307}, {7733, 1},
+       {7734, 16962563}, {7735, 1}, {7736, 16962819}, {7737, 1},
+       {7738, 16963075}, {7739, 1}, {7740, 16963331}, {7741, 1},
+       {7742, 16963587}, {7743, 1}, {7744, 16963843}, {7745, 1},
+       {7746, 16964099}, {7747, 1}, {7748, 16964355}, {7749, 1},
+       {7750, 16964611}, {7751, 1}, {7752, 16964867}, {7753, 1},
+       {7754, 16965123}, {7755, 1}, {7756, 16965379}, {7757, 1},
+       {7758, 16965635}, {7759, 1}, {7760, 16965891}, {7761, 1},
+       {7762, 16966147}, {7763, 1}, {7764, 16966403}, {7765, 1},
+       {7766, 16966659}, {7767, 1}, {7768, 16966915}, {7769, 1},
+       {7770, 16967171}, {7771, 1}, {7772, 16967427}, {7773, 1},
+       {7774, 16967683}, {7775, 1}, {7776, 16967939}, {7777, 1},
+       {7778, 16968195}, {7779, 1}, {7780, 16968451}, {7781, 1},
+       {7782, 16968707}, {7783, 1}, {7784, 16968963}, {7785, 1},
+       {7786, 16969219}, {7787, 1}, {7788, 16969475}, {7789, 1},
+       {7790, 16969731}, {7791, 1}, {7792, 16969987}, {7793, 1},
+       {7794, 16970243}, {7795, 1}, {7796, 16970499}, {7797, 1},
+       {7798, 16970755}, {7799, 1}, {7800, 16971011}, {7801, 1},
+       {7802, 16971267}, {7803, 1}, {7804, 16971523}, {7805, 1},
+       {7806, 16971779}, {7807, 1}, {7808, 16972035}, {7809, 1},
+       {7810, 16972291}, {7811, 1}, {7812, 16972547}, {7813, 1},
+       {7814, 16972803}, {7815, 1}, {7816, 16973059}, {7817, 1},
+       {7818, 16973315}, {7819, 1}, {7820, 16973571}, {7821, 1},
+       {7822, 16973827}, {7823, 1}, {7824, 16974083}, {7825, 1},
+       {7826, 16974339}, {7827, 1}, {7828, 16974595}, {7829, 1},
+       {7834, 33752067}, {7835, 16967939}, {7836, 1}, {7838, 33752579},
+       {7839, 1}, {7840, 16975875}, {7841, 1}, {7842, 16976131},
+       {7843, 1}, {7844, 16976387}, {7845, 1}, {7846, 16976643},
+       {7847, 1}, {7848, 16976899}, {7849, 1}, {7850, 16977155},
+       {7851, 1}, {7852, 16977411}, {7853, 1}, {7854, 16977667},
+       {7855, 1}, {7856, 16977923}, {7857, 1}, {7858, 16978179},
+       {7859, 1}, {7860, 16978435}, {7861, 1}, {7862, 16978691},
+       {7863, 1}, {7864, 16978947}, {7865, 1}, {7866, 16979203},
+       {7867, 1}, {7868, 16979459}, {7869, 1}, {7870, 16979715},
+       {7871, 1}, {7872, 16979971}, {7873, 1}, {7874, 16980227},
+       {7875, 1}, {7876, 16980483}, {7877, 1}, {7878, 16980739},
+       {7879, 1}, {7880, 16980995}, {7881, 1}, {7882, 16981251},
+       {7883, 1}, {7884, 16981507}, {7885, 1}, {7886, 16981763},
+       {7887, 1}, {7888, 16982019}, {7889, 1}, {7890, 16982275},
+       {7891, 1}, {7892, 16982531}, {7893, 1}, {7894, 16982787},
+       {7895, 1}, {7896, 16983043}, {7897, 1}, {7898, 16983299},
+       {7899, 1}, {7900, 16983555}, {7901, 1}, {7902, 16983811},
+       {7903, 1}, {7904, 16984067}, {7905, 1}, {7906, 16984323},
+       {7907, 1}, {7908, 16984579}, {7909, 1}, {7910, 16984835},
+       {7911, 1}, {7912, 16985091}, {7913, 1}, {7914, 16985347},
+       {7915, 1}, {7916, 16985603}, {7917, 1}, {7918, 16985859},
+       {7919, 1}, {7920, 16986115}, {7921, 1}, {7922, 16986371},
+       {7923, 1}, {7924, 16986627}, {7925, 1}, {7926, 16986883},
+       {7927, 1}, {7928, 16987139}, {7929, 1}, {7930, 16987395},
+       {7931, 1}, {7932, 16987651}, {7933, 1}, {7934, 16987907},
+       {7935, 1}, {7944, 16988163}, {7945, 16988419}, {7946, 16988675},
+       {7947, 16988931}, {7948, 16989187}, {7949, 16989443}, {7950, 16989699},
+       {7951, 16989955}, {7952, 1}, {7958, 2}, {7960, 16990211},
+       {7961, 16990467}, {7962, 16990723}, {7963, 16990979}, {7964, 16991235},
+       {7965, 16991491}, {7966, 2}, {7968, 1}, {7976, 16991747},
+       {7977, 16992003}, {7978, 16992259}, {7979, 16992515}, {7980, 16992771},
+       {7981, 16993027}, {7982, 16993283}, {7983, 16993539}, {7984, 1},
+       {7992, 16993795}, {7993, 16994051}, {7994, 16994307}, {7995, 16994563},
+       {7996, 16994819}, {7997, 16995075}, {7998, 16995331}, {7999, 16995587},
+       {8000, 1}, {8006, 2}, {8008, 16995843}, {8009, 16996099},
+       {8010, 16996355}, {8011, 16996611}, {8012, 16996867}, {8013, 16997123},
+       {8014, 2}, {8016, 1}, {8024, 2}, {8025, 16997379},
+       {8026, 2}, {8027, 16997635}, {8028, 2}, {8029, 16997891},
+       {8030, 2}, {8031, 16998147}, {8032, 1}, {8040, 16998403},
+       {8041, 16998659}, {8042, 16998915}, {8043, 16999171}, {8044, 16999427},
+       {8045, 16999683}, {8046, 16999939}, {8047, 17000195}, {8048, 1},
+       {8049, 16849923}, {8050, 1}, {8051, 16850179}, {8052, 1},
+       {8053, 16850435}, {8054, 1}, {8055, 16850691}, {8056, 1},
+       {8057, 16850947}, {8058, 1}, {8059, 16851203}, {8060, 1},
+       {8061, 16851459}, {8062, 2}, {8064, 33777667}, {8065, 33778179},
+       {8066, 33778691}, {8067, 33779203}, {8068, 33779715}, {8069, 33780227},
+       {8070, 33780739}, {8071, 33781251}, {8072, 33777667}, {8073, 33778179},
+       {8074, 33778691}, {8075, 33779203}, {8076, 33779715}, {8077, 33780227},
+       {8078, 33780739}, {8079, 33781251}, {8080, 33781763}, {8081, 33782275},
+       {8082, 33782787}, {8083, 33783299}, {8084, 33783811}, {8085, 33784323},
+       {8086, 33784835}, {8087, 33785347}, {8088, 33781763}, {8089, 33782275},
+       {8090, 33782787}, {8091, 33783299}, {8092, 33783811}, {8093, 33784323},
+       {8094, 33784835}, {8095, 33785347}, {8096, 33785859}, {8097, 33786371},
+       {8098, 33786883}, {8099, 33787395}, {8100, 33787907}, {8101, 33788419},
+       {8102, 33788931}, {8103, 33789443}, {8104, 33785859}, {8105, 33786371},
+       {8106, 33786883}, {8107, 33787395}, {8108, 33787907}, {8109, 33788419},
+       {8110, 33788931}, {8111, 33789443}, {8112, 1}, {8114, 33789955},
+       {8115, 33790467}, {8116, 33790979}, {8117, 2}, {8118, 1},
+       {8119, 33791491}, {8120, 17014787}, {8121, 17015043}, {8122, 17012739},
+       {8123, 16849923}, {8124, 33790467}, {8125, 33792515}, {8126, 16846851},
+       {8127, 33792515}, {8128, 33793027}, {8129, 50570755}, {8130, 33794307},
+       {8131, 33794819}, {8132, 33795331}, {8133, 2}, {8134, 1},
+       {8135, 33795843}, {8136, 17019139}, {8137, 16850179}, {8138, 17017091},
+       {8139, 16850435}, {8140, 33794819}, {8141, 50573827}, {8142, 50574595},
+       {8143, 50575363}, {8144, 1}, {8147, 17021699}, {8148, 2},
+       {8150, 1}, {8152, 17021955}, {8153, 17022211}, {8154, 17022467},
+       {8155, 16850691}, {8156, 2}, {8157, 50577155}, {8158, 50577923},
+       {8159, 50578691}, {8160, 1}, {8163, 17025027}, {8164, 1},
+       {8168, 17025283}, {8169, 17025539}, {8170, 17025795}, {8171, 16851203},
+       {8172, 17026051}, {8173, 50580739}, {8174, 50403587}, {8175, 17027075},
+       {8176, 2}, {8178, 33804547}, {8179, 33805059}, {8180, 33805571},
+       {8181, 2}, {8182, 1}, {8183, 33806083}, {8184, 17029379},
+       {8185, 16850947}, {8186, 17027331}, {8187, 16851459}, {8188, 33805059},
+       {8189, 33562883}, {8190, 33799939}, {8191, 2}, {8192, 16783875},
+       {8203, 0}, {8204, 1}, {8206, 2}, {8208, 1},
+       {8209, 17029635}, {8210, 1}, {8215, 33807107}, {8216, 1},
+       {8228, 2}, {8231, 1}, {8232, 2}, {8239, 16783875},
+       {8240, 1}, {8243, 33807619}, {8244, 50585347}, {8245, 1},
+       {8246, 33808899}, {8247, 50586627}, {8248, 1}, {8252, 33810179},
+       {8253, 1}, {8254, 33810691}, {8255, 1}, {8263, 33811203},
+       {8264, 33811715}, {8265, 33812227}, {8266, 1}, {8279, 67362051},
+       {8280, 1}, {8287, 16783875}, {8288, 0}, {8289, 2},
+       {8292, 0}, {8293, 2}, {8304, 17035523}, {8305, 16779267},
+       {8306, 2}, {8308, 16787715}, {8309, 17035779}, {8310, 17036035},
+       {8311, 17036291}, {8312, 17036547}, {8313, 17036803}, {8314, 17037059},
+       {8315, 17037315}, {8316, 17037571}, {8317, 17037827}, {8318, 17038083},
+       {8319, 16780547}, {8320, 17035523}, {8321, 16786947}, {8322, 16785155},
+       {8323, 16785411}, {8324, 16787715}, {8325, 17035779}, {8326, 17036035},
+       {8327, 17036291}, {8328, 17036547}, {8329, 17036803}, {8330, 17037059},
+       {8331, 17037315}, {8332, 17037571}, {8333, 17037827}, {8334, 17038083},
+       {8335, 2}, {8336, 16777219}, {8337, 16778243}, {8338, 16780803},
+       {8339, 16783107}, {8340, 16816387}, {8341, 16779011}, {8342, 16779779},
+       {8343, 16780035}, {8344, 16780291}, {8345, 16780547}, {8346, 16781059},
+       {8347, 16781827}, {8348, 16782083}, {8349, 2}, {8352, 1},
+       {8360, 33558787}, {8361, 1}, {8385, 2}, {8400, 1},
+       {8433, 2}, {8448, 50592771}, {8449, 50593539}, {8450, 16777731},
+       {8451, 33817091}, {8452, 1}, {8453, 50594819}, {8454, 50595587},
+       {8455, 16816643}, {8456, 1}, {8457, 33819139}, {8458, 16778755},
+       {8459, 16779011}, {8463, 16802051}, {8464, 16779267}, {8466, 16780035},
+       {8468, 1}, {8469, 16780547}, {8470, 33557763}, {8471, 1},
+       {8473, 16781059}, {8474, 16781315}, {8475, 16781571}, {8478, 1},
+       {8480, 33819651}, {8481, 50597379}, {8482, 33820931}, {8483, 1},
+       {8484, 16783619}, {8485, 1}, {8486, 16857091}, {8487, 1},
+       {8488, 16783619}, {8489, 1}, {8490, 16779779}, {8491, 16790787},
+       {8492, 16777475}, {8493, 16777731}, {8494, 1}, {8495, 16778243},
+       {8497, 16778499}, {8498, 2}, {8499, 16780291}, {8500, 16780803},
+       {8501, 17044227}, {8502, 17044483}, {8503, 17044739}, {8504, 17044995},
+       {8505, 16779267}, {8506, 1}, {8507, 50599683}, {8508, 16855043},
+       {8509, 16852227}, {8511, 16855043}, {8512, 17046019}, {8513, 1},
+       {8517, 16777987}, {8519, 16778243}, {8520, 16779267}, {8521, 16779523},
+       {8522, 1}, {8528, 50600707}, {8529, 50601475}, {8530, 67379459},
+       {8531, 50603267}, {8532, 50604035}, {8533, 50604803}, {8534, 50605571},
+       {8535, 50606339}, {8536, 50607107}, {8537, 50607875}, {8538, 50608643},
+       {8539, 50609411}, {8540, 50610179}, {8541, 50610947}, {8542, 50611715},
+       {8543, 33564419}, {8544, 16779267}, {8545, 33835267}, {8546, 50612995},
+       {8547, 33836547}, {8548, 16782595}, {8549, 33837059}, {8550, 50614787},
+       {8551, 67392771}, {8552, 33839363}, {8553, 16783107}, {8554, 33839875},
+       {8555, 50617603}, {8556, 16780035}, {8557, 16777731}, {8558, 16777987},
+       {8559, 16780291}, {8560, 16779267}, {8561, 33835267}, {8562, 50612483},
+       {8563, 33836547}, {8564, 16782595}, {8565, 33837059}, {8566, 50614787},
+       {8567, 67392771}, {8568, 33839363}, {8569, 16783107}, {8570, 33839875},
+       {8571, 50617603}, {8572, 16780035}, {8573, 16777731}, {8574, 16777987},
+       {8575, 16780291}, {8576, 1}, {8579, 2}, {8580, 1},
+       {8585, 50618371}, {8586, 1}, {8588, 2}, {8592, 1},
+       {8748, 33841923}, {8749, 50619651}, {8750, 1}, {8751, 33843203},
+       {8752, 50620931}, {8753, 1}, {9001, 17067267}, {9002, 17067523},
+       {9003, 1}, {9255, 2}, {9280, 1}, {9291, 2},
+       {9312, 16786947}, {9313, 16785155}, {9314, 16785411}, {9315, 16787715},
+       {9316, 17035779}, {9317, 17036035}, {9318, 17036291}, {9319, 17036547},
+       {9320, 17036803}, {9321, 33825539}, {9322, 33564163}, {9323, 33844995},
+       {9324, 33845507}, {9325, 33846019}, {9326, 33846531}, {9327, 33847043},
+       {9328, 33847555}, {9329, 33848067}, {9330, 33848579}, {9331, 33849091},
+       {9332, 50626819}, {9333, 50627587}, {9334, 50628355}, {9335, 50629123},
+       {9336, 50629891}, {9337, 50630659}, {9338, 50631427}, {9339, 50632195},
+       {9340, 50632963}, {9341, 67410947}, {9342, 67411971}, {9343, 67412995},
+       {9344, 67414019}, {9345, 67415043}, {9346, 67416067}, {9347, 67417091},
+       {9348, 67418115}, {9349, 67419139}, {9350, 67420163}, {9351, 67421187},
+       {9352, 2}, {9372, 50644995}, {9373, 50645763}, {9374, 50646531},
+       {9375, 50647299}, {9376, 50648067}, {9377, 50648835}, {9378, 50649603},
+       {9379, 50650371}, {9380, 50651139}, {9381, 50651907}, {9382, 50652675},
+       {9383, 50653443}, {9384, 50654211}, {9385, 50654979}, {9386, 50655747},
+       {9387, 50656515}, {9388, 50657283}, {9389, 50658051}, {9390, 50658819},
+       {9391, 50659587}, {9392, 50660355}, {9393, 50661123}, {9394, 50661891},
+       {9395, 50662659}, {9396, 50663427}, {9397, 50664195}, {9398, 16777219},
+       {9399, 16777475}, {9400, 16777731}, {9401, 16777987}, {9402, 16778243},
+       {9403, 16778499}, {9404, 16778755}, {9405, 16779011}, {9406, 16779267},
+       {9407, 16779523}, {9408, 16779779}, {9409, 16780035}, {9410, 16780291},
+       {9411, 16780547}, {9412, 16780803}, {9413, 16781059}, {9414, 16781315},
+       {9415, 16781571}, {9416, 16781827}, {9417, 16782083}, {9418, 16782339},
+       {9419, 16782595}, {9420, 16782851}, {9421, 16783107}, {9422, 16783363},
+       {9423, 16783619}, {9424, 16777219}, {9425, 16777475}, {9426, 16777731},
+       {9427, 16777987}, {9428, 16778243}, {9429, 16778499}, {9430, 16778755},
+       {9431, 16779011}, {9432, 16779267}, {9433, 16779523}, {9434, 16779779},
+       {9435, 16780035}, {9436, 16780291}, {9437, 16780547}, {9438, 16780803},
+       {9439, 16781059}, {9440, 16781315}, {9441, 16781571}, {9442, 16781827},
+       {9443, 16782083}, {9444, 16782339}, {9445, 16782595}, {9446, 16782851},
+       {9447, 16783107}, {9448, 16783363}, {9449, 16783619}, {9450, 17035523},
+       {9451, 1}, {10764, 67396355}, {10765, 1}, {10868, 50664963},
+       {10869, 33888515}, {10870, 50665475}, {10871, 1}, {10972, 33889027},
+       {10973, 1}, {11124, 2}, {11126, 1}, {11158, 2},
+       {11159, 1}, {11264, 17112323}, {11265, 17112579}, {11266, 17112835},
+       {11267, 17113091}, {11268, 17113347}, {11269, 17113603}, {11270, 17113859},
+       {11271, 17114115}, {11272, 17114371}, {11273, 17114627}, {11274, 17114883},
+       {11275, 17115139}, {11276, 17115395}, {11277, 17115651}, {11278, 17115907},
+       {11279, 17116163}, {11280, 17116419}, {11281, 17116675}, {11282, 17116931},
+       {11283, 17117187}, {11284, 17117443}, {11285, 17117699}, {11286, 17117955},
+       {11287, 17118211}, {11288, 17118467}, {11289, 17118723}, {11290, 17118979},
+       {11291, 17119235}, {11292, 17119491}, {11293, 17119747}, {11294, 17120003},
+       {11295, 17120259}, {11296, 17120515}, {11297, 17120771}, {11298, 17121027},
+       {11299, 17121283}, {11300, 17121539}, {11301, 17121795}, {11302, 17122051},
+       {11303, 17122307}, {11304, 17122563}, {11305, 17122819}, {11306, 17123075},
+       {11307, 17123331}, {11308, 17123587}, {11309, 17123843}, {11310, 17124099},
+       {11311, 17124355}, {11312, 1}, {11360, 17124611}, {11361, 1},
+       {11362, 17124867}, {11363, 17125123}, {11364, 17125379}, {11365, 1},
+       {11367, 17125635}, {11368, 1}, {11369, 17125891}, {11370, 1},
+       {11371, 17126147}, {11372, 1}, {11373, 16948483}, {11374, 16953091},
+       {11375, 16948227}, {11376, 16950275}, {11377, 1}, {11378, 17126403},
+       {11379, 1}, {11381, 17126659}, {11382, 1}, {11388, 16779523},
+       {11389, 16782595}, {11390, 17126915}, {11391, 17127171}, {11392, 17127427},
+       {11393, 1}, {11394, 17127683}, {11395, 1}, {11396, 17127939},
+       {11397, 1}, {11398, 17128195}, {11399, 1}, {11400, 17128451},
+       {11401, 1}, {11402, 17128707}, {11403, 1}, {11404, 17128963},
+       {11405, 1}, {11406, 17129219}, {11407, 1}, {11408, 17129475},
+       {11409, 1}, {11410, 17129731}, {11411, 1}, {11412, 17129987},
+       {11413, 1}, {11414, 17130243}, {11415, 1}, {11416, 17130499},
+       {11417, 1}, {11418, 17130755}, {11419, 1}, {11420, 17131011},
+       {11421, 1}, {11422, 17131267}, {11423, 1}, {11424, 17131523},
+       {11425, 1}, {11426, 17131779}, {11427, 1}, {11428, 17132035},
+       {11429, 1}, {11430, 17132291}, {11431, 1}, {11432, 17132547},
+       {11433, 1}, {11434, 17132803}, {11435, 1}, {11436, 17133059},
+       {11437, 1}, {11438, 17133315}, {11439, 1}, {11440, 17133571},
+       {11441, 1}, {11442, 17133827}, {11443, 1}, {11444, 17134083},
+       {11445, 1}, {11446, 17134339}, {11447, 1}, {11448, 17134595},
+       {11449, 1}, {11450, 17134851}, {11451, 1}, {11452, 17135107},
+       {11453, 1}, {11454, 17135363}, {11455, 1}, {11456, 17135619},
+       {11457, 1}, {11458, 17135875}, {11459, 1}, {11460, 17136131},
+       {11461, 1}, {11462, 17136387}, {11463, 1}, {11464, 17136643},
+       {11465, 1}, {11466, 17136899}, {11467, 1}, {11468, 17137155},
+       {11469, 1}, {11470, 17137411}, {11471, 1}, {11472, 17137667},
+       {11473, 1}, {11474, 17137923}, {11475, 1}, {11476, 17138179},
+       {11477, 1}, {11478, 17138435}, {11479, 1}, {11480, 17138691},
+       {11481, 1}, {11482, 17138947}, {11483, 1}, {11484, 17139203},
+       {11485, 1}, {11486, 17139459}, {11487, 1}, {11488, 17139715},
+       {11489, 1}, {11490, 17139971}, {11491, 1}, {11499, 17140227},
+       {11500, 1}, {11501, 17140483}, {11502, 1}, {11506, 17140739},
+       {11507, 1}, {11508, 2}, {11513, 1}, {11558, 2},
+       {11559, 1}, {11560, 2}, {11565, 1}, {11566, 2},
+       {11568, 1}, {11624, 2}, {11631, 17140995}, {11632, 1},
+       {11633, 2}, {11647, 1}, {11671, 2}, {11680, 1},
+       {11687, 2}, {11688, 1}, {11695, 2}, {11696, 1},
+       {11703, 2}, {11704, 1}, {11711, 2}, {11712, 1},
+       {11719, 2}, {11720, 1}, {11727, 2}, {11728, 1},
+       {11735, 2}, {11736, 1}, {11743, 2}, {11744, 1},
+       {11870, 2}, {11904, 1}, {11930, 2}, {11931, 1},
+       {11935, 17141251}, {11936, 1}, {12019, 17141507}, {12020, 2},
+       {12032, 17141763}, {12033, 17142019}, {12034, 17142275}, {12035, 17142531},
+       {12036, 17142787}, {12037, 17143043}, {12038, 17143299}, {12039, 17143555},
+       {12040, 17143811}, {12041, 17144067}, {12042, 17144323}, {12043, 17144579},
+       {12044, 17144835}, {12045, 17145091}, {12046, 17145347}, {12047, 17145603},
+       {12048, 17145859}, {12049, 17146115}, {12050, 17146371}, {12051, 17146627},
+       {12052, 17146883}, {12053, 17147139}, {12054, 17147395}, {12055, 17147651},
+       {12056, 17147907}, {12057, 17148163}, {12058, 17148419}, {12059, 17148675},
+       {12060, 17148931}, {12061, 17149187}, {12062, 17149443}, {12063, 17149699},
+       {12064, 17149955}, {12065, 17150211}, {12066, 17150467}, {12067, 17150723},
+       {12068, 17150979}, {12069, 17151235}, {12070, 17151491}, {12071, 17151747},
+       {12072, 17152003}, {12073, 17152259}, {12074, 17152515}, {12075, 17152771},
+       {12076, 17153027}, {12077, 17153283}, {12078, 17153539}, {12079, 17153795},
+       {12080, 17154051}, {12081, 17154307}, {12082, 17154563}, {12083, 17154819},
+       {12084, 17155075}, {12085, 17155331}, {12086, 17155587}, {12087, 17155843},
+       {12088, 17156099}, {12089, 17156355}, {12090, 17156611}, {12091, 17156867},
+       {12092, 17157123}, {12093, 17157379}, {12094, 17157635}, {12095, 17157891},
+       {12096, 17158147}, {12097, 17158403}, {12098, 17158659}, {12099, 17158915},
+       {12100, 17159171}, {12101, 17159427}, {12102, 17159683}, {12103, 17159939},
+       {12104, 17160195}, {12105, 17160451}, {12106, 17160707}, {12107, 17160963},
+       {12108, 17161219}, {12109, 17161475}, {12110, 17161731}, {12111, 17161987},
+       {12112, 17162243}, {12113, 17162499}, {12114, 17162755}, {12115, 17163011},
+       {12116, 17163267}, {12117, 17163523}, {12118, 17163779}, {12119, 17164035},
+       {12120, 17164291}, {12121, 17164547}, {12122, 17164803}, {12123, 17165059},
+       {12124, 17165315}, {12125, 17165571}, {12126, 17165827}, {12127, 17166083},
+       {12128, 17166339}, {12129, 17166595}, {12130, 17166851}, {12131, 17167107},
+       {12132, 17167363}, {12133, 17167619}, {12134, 17167875}, {12135, 17168131},
+       {12136, 17168387}, {12137, 17168643}, {12138, 17168899}, {12139, 17169155},
+       {12140, 17169411}, {12141, 17169667}, {12142, 17169923}, {12143, 17170179},
+       {12144, 17170435}, {12145, 17170691}, {12146, 17170947}, {12147, 17171203},
+       {12148, 17171459}, {12149, 17171715}, {12150, 17171971}, {12151, 17172227},
+       {12152, 17172483}, {12153, 17172739}, {12154, 17172995}, {12155, 17173251},
+       {12156, 17173507}, {12157, 17173763}, {12158, 17174019}, {12159, 17174275},
+       {12160, 17174531}, {12161, 17174787}, {12162, 17175043}, {12163, 17175299},
+       {12164, 17175555}, {12165, 17175811}, {12166, 17176067}, {12167, 17176323},
+       {12168, 17176579}, {12169, 17176835}, {12170, 17177091}, {12171, 17177347},
+       {12172, 17177603}, {12173, 17177859}, {12174, 17178115}, {12175, 17178371},
+       {12176, 17178627}, {12177, 17178883}, {12178, 17179139}, {12179, 17179395},
+       {12180, 17179651}, {12181, 17179907}, {12182, 17180163}, {12183, 17180419},
+       {12184, 17180675}, {12185, 17180931}, {12186, 17181187}, {12187, 17181443},
+       {12188, 17181699}, {12189, 17181955}, {12190, 17182211}, {12191, 17182467},
+       {12192, 17182723}, {12193, 17182979}, {12194, 17183235}, {12195, 17183491},
+       {12196, 17183747}, {12197, 17184003}, {12198, 17184259}, {12199, 17184515},
+       {12200, 17184771}, {12201, 17185027}, {12202, 17185283}, {12203, 17185539},
+       {12204, 17185795}, {12205, 17186051}, {12206, 17186307}, {12207, 17186563},
+       {12208, 17186819}, {12209, 17187075}, {12210, 17187331}, {12211, 17187587},
+       {12212, 17187843}, {12213, 17188099}, {12214, 17188355}, {12215, 17188611},
+       {12216, 17188867}, {12217, 17189123}, {12218, 17189379}, {12219, 17189635},
+       {12220, 17189891}, {12221, 17190147}, {12222, 17190403}, {12223, 17190659},
+       {12224, 17190915}, {12225, 17191171}, {12226, 17191427}, {12227, 17191683},
+       {12228, 17191939}, {12229, 17192195}, {12230, 17192451}, {12231, 17192707},
+       {12232, 17192963}, {12233, 17193219}, {12234, 17193475}, {12235, 17193731},
+       {12236, 17193987}, {12237, 17194243}, {12238, 17194499}, {12239, 17194755},
+       {12240, 17195011}, {12241, 17195267}, {12242, 17195523}, {12243, 17195779},
+       {12244, 17196035}, {12245, 17196291}, {12246, 2}, {12288, 16783875},
+       {12289, 1}, {12290, 17196547}, {12291, 1}, {12342, 17196803},
+       {12343, 1}, {12344, 17147651}, {12345, 17197059}, {12346, 17197315},
+       {12347, 1}, {12352, 2}, {12353, 1}, {12439, 2},
+       {12441, 1}, {12443, 33974787}, {12444, 33975299}, {12445, 1},
+       {12447, 33975811}, {12448, 1}, {12543, 33976323}, {12544, 2},
+       {12549, 1}, {12592, 2}, {12593, 17199619}, {12594, 17199875},
+       {12595, 17200131}, {12596, 17200387}, {12597, 17200643}, {12598, 17200899},
+       {12599, 17201155}, {12600, 17201411}, {12601, 17201667}, {12602, 17201923},
+       {12603, 17202179}, {12604, 17202435}, {12605, 17202691}, {12606, 17202947},
+       {12607, 17203203}, {12608, 17203459}, {12609, 17203715}, {12610, 17203971},
+       {12611, 17204227}, {12612, 17204483}, {12613, 17204739}, {12614, 17204995},
+       {12615, 17205251}, {12616, 17205507}, {12617, 17205763}, {12618, 17206019},
+       {12619, 17206275}, {12620, 17206531}, {12621, 17206787}, {12622, 17207043},
+       {12623, 17207299}, {12624, 17207555}, {12625, 17207811}, {12626, 17208067},
+       {12627, 17208323}, {12628, 17208579}, {12629, 17208835}, {12630, 17209091},
+       {12631, 17209347}, {12632, 17209603}, {12633, 17209859}, {12634, 17210115},
+       {12635, 17210371}, {12636, 17210627}, {12637, 17210883}, {12638, 17211139},
+       {12639, 17211395}, {12640, 17211651}, {12641, 17211907}, {12642, 17212163},
+       {12643, 17212419}, {12644, 2}, {12645, 17212675}, {12646, 17212931},
+       {12647, 17213187}, {12648, 17213443}, {12649, 17213699}, {12650, 17213955},
+       {12651, 17214211}, {12652, 17214467}, {12653, 17214723}, {12654, 17214979},
+       {12655, 17215235}, {12656, 17215491}, {12657, 17215747}, {12658, 17216003},
+       {12659, 17216259}, {12660, 17216515}, {12661, 17216771}, {12662, 17217027},
+       {12663, 17217283}, {12664, 17217539}, {12665, 17217795}, {12666, 17218051},
+       {12667, 17218307}, {12668, 17218563}, {12669, 17218819}, {12670, 17219075},
+       {12671, 17219331}, {12672, 17219587}, {12673, 17219843}, {12674, 17220099},
+       {12675, 17220355}, {12676, 17220611}, {12677, 17220867}, {12678, 17221123},
+       {12679, 17221379}, {12680, 17221635}, {12681, 17221891}, {12682, 17222147},
+       {12683, 17222403}, {12684, 17222659}, {12685, 17222915}, {12686, 17223171},
+       {12687, 2}, {12688, 1}, {12690, 17141763}, {12691, 17143299},
+       {12692, 17223427}, {12693, 17223683}, {12694, 17223939}, {12695, 17224195},
+       {12696, 17224451}, {12697, 17224707}, {12698, 17142787}, {12699, 17224963},
+       {12700, 17225219}, {12701, 17225475}, {12702, 17225731}, {12703, 17143811},
+       {12704, 1}, {12772, 2}, {12784, 1}, {12800, 50780419},
+       {12801, 50781187}, {12802, 50781955}, {12803, 50782723}, {12804, 50783491},
+       {12805, 50784259}, {12806, 50785027}, {12807, 50785795}, {12808, 50786563},
+       {12809, 50787331}, {12810, 50788099}, {12811, 50788867}, {12812, 50789635},
+       {12813, 50790403}, {12814, 50791171}, {12815, 50791939}, {12816, 50792707},
+       {12817, 50793475}, {12818, 50794243}, {12819, 50795011}, {12820, 50795779},
+       {12821, 50796547}, {12822, 50797315}, {12823, 50798083}, {12824, 50798851},
+       {12825, 50799619}, {12826, 50800387}, {12827, 50801155}, {12828, 50801923},
+       {12829, 67579907}, {12830, 67580931}, {12831, 2}, {12832, 50804739},
+       {12833, 50805507}, {12834, 50806275}, {12835, 50807043}, {12836, 50807811},
+       {12837, 50808579}, {12838, 50809347}, {12839, 50810115}, {12840, 50810883},
+       {12841, 50811651}, {12842, 50812419}, {12843, 50813187}, {12844, 50813955},
+       {12845, 50814723}, {12846, 50815491}, {12847, 50816259}, {12848, 50817027},
+       {12849, 50817795}, {12850, 50818563}, {12851, 50819331}, {12852, 50820099},
+       {12853, 50820867}, {12854, 50821635}, {12855, 50822403}, {12856, 50823171},
+       {12857, 50823939}, {12858, 50824707}, {12859, 50825475}, {12860, 50826243},
+       {12861, 50827011}, {12862, 50827779}, {12863, 50828547}, {12864, 50829315},
+       {12865, 50830083}, {12866, 50830851}, {12867, 50831619}, {12868, 17277955},
+       {12869, 17278211}, {12870, 17158659}, {12871, 17278467}, {12872, 1},
+       {12880, 50833155}, {12881, 33845251}, {12882, 34056707}, {12883, 33562371},
+       {12884, 34057219}, {12885, 34057731}, {12886, 34058243}, {12887, 34058755},
+       {12888, 34059267}, {12889, 34059779}, {12890, 34060291}, {12891, 33827331},
+       {12892, 33826563}, {12893, 34060803}, {12894, 34061315}, {12895, 34061827},
+       {12896, 17199619}, {12897, 17200387}, {12898, 17201155}, {12899, 17201667},
+       {12900, 17203715}, {12901, 17203971}, {12902, 17204739}, {12903, 17205251},
+       {12904, 17205507}, {12905, 17206019}, {12906, 17206275}, {12907, 17206531},
+       {12908, 17206787}, {12909, 17207043}, {12910, 17236995}, {12911, 17237763},
+       {12912, 17238531}, {12913, 17239299}, {12914, 17240067}, {12915, 17240835},
+       {12916, 17241603}, {12917, 17242371}, {12918, 17243139}, {12919, 17243907},
+       {12920, 17244675}, {12921, 17245443}, {12922, 17246211}, {12923, 17246979},
+       {12924, 34062339}, {12925, 34062851}, {12926, 17286147}, {12927, 1},
+       {12928, 17141763}, {12929, 17143299}, {12930, 17223427}, {12931, 17223683},
+       {12932, 17253635}, {12933, 17254403}, {12934, 17255171}, {12935, 17144579},
+       {12936, 17256707}, {12937, 17147651}, {12938, 17160451}, {12939, 17163523},
+       {12940, 17163267}, {12941, 17160707}, {12942, 17184259}, {12943, 17149699},
+       {12944, 17159939}, {12945, 17263619}, {12946, 17264387}, {12947, 17265155},
+       {12948, 17265923}, {12949, 17266691}, {12950, 17267459}, {12951, 17268227},
+       {12952, 17268995}, {12953, 17286403}, {12954, 17286659}, {12955, 17151235},
+       {12956, 17286915}, {12957, 17287171}, {12958, 17287427}, {12959, 17287683},
+       {12960, 17287939}, {12961, 17275907}, {12962, 17288195}, {12963, 17288451},
+       {12964, 17223939}, {12965, 17224195}, {12966, 17224451}, {12967, 17288707},
+       {12968, 17288963}, {12969, 17289219}, {12970, 17289475}, {12971, 17271299},
+       {12972, 17272067}, {12973, 17272835}, {12974, 17273603}, {12975, 17274371},
+       {12976, 17289731}, {12977, 34067203}, {12978, 34067715}, {12979, 34068227},
+       {12980, 34068739}, {12981, 34069251}, {12982, 33564931}, {12983, 34057475},
+       {12984, 34061571}, {12985, 34069763}, {12986, 34070275}, {12987, 34070787},
+       {12988, 34071299}, {12989, 34071811}, {12990, 34072323}, {12991, 34072835},
+       {12992, 34073347}, {12993, 34073859}, {12994, 34074371}, {12995, 34074883},
+       {12996, 34075395}, {12997, 34075907}, {12998, 34076419}, {12999, 34076931},
+       {13000, 34077443}, {13001, 50855171}, {13002, 50855939}, {13003, 50856707},
+       {13004, 34080259}, {13005, 50857987}, {13006, 34081539}, {13007, 50859267},
+       {13008, 17305603}, {13009, 17305859}, {13010, 17306115}, {13011, 17306371},
+       {13012, 17306627}, {13013, 17306883}, {13014, 17307139}, {13015, 17307395},
+       {13016, 17307651}, {13017, 17199107}, {13018, 17307907}, {13019, 17308163},
+       {13020, 17308419}, {13021, 17308675}, {13022, 17308931}, {13023, 17309187},
+       {13024, 17309443}, {13025, 17309699}, {13026, 17309955}, {13027, 17199363},
+       {13028, 17310211}, {13029, 17310467}, {13030, 17310723}, {13031, 17310979},
+       {13032, 17311235}, {13033, 17311491}, {13034, 17311747}, {13035, 17312003},
+       {13036, 17312259}, {13037, 17312515}, {13038, 17312771}, {13039, 17313027},
+       {13040, 17313283}, {13041, 17313539}, {13042, 17313795}, {13043, 17314051},
+       {13044, 17314307}, {13045, 17314563}, {13046, 17314819}, {13047, 17315075},
+       {13048, 17315331}, {13049, 17315587}, {13050, 17315843}, {13051, 17316099},
+       {13052, 17316355}, {13053, 17316611}, {13054, 17316867}, {13055, 34094339},
+       {13056, 67649283}, {13057, 67650307}, {13058, 67651331}, {13059, 50875139},
+       {13060, 67653123}, {13061, 50876931}, {13062, 50877699}, {13063, 84432899},
+       {13064, 67656963}, {13065, 50880771}, {13066, 50881539}, {13067, 50882307},
+       {13068, 67660291}, {13069, 67661315}, {13070, 50885123}, {13071, 50885891},
+       {13072, 34109443}, {13073, 50887171}, {13074, 67665155}, {13075, 67666179},
+       {13076, 34112771}, {13077, 84444931}, {13078, 101223427}, {13079, 84447747},
+       {13080, 50891011}, {13081, 84449027}, {13082, 84450307}, {13083, 67674371},
+       {13084, 50898179}, {13085, 50898947}, {13086, 50899715}, {13087, 67677699},
+       {13088, 84455939}, {13089, 67680003}, {13090, 50903811}, {13091, 50904579},
+       {13092, 50905347}, {13093, 34128899}, {13094, 34129411}, {13095, 34118147},
+       {13096, 34129923}, {13097, 50907651}, {13098, 50908419}, {13099, 84463619},
+       {13100, 50910467}, {13101, 67688451}, {13102, 84466691}, {13103, 50913539},
+       {13104, 34137091}, {13105, 34137603}, {13106, 84469763}, {13107, 67693827},
+       {13108, 84472067}, {13109, 50918915}, {13110, 84474115}, {13111, 34143747},
+       {13112, 50921475}, {13113, 50922243}, {13114, 50923011}, {13115, 50923779},
+       {13116, 50924547}, {13117, 67702531}, {13118, 50926339}, {13119, 34149891},
+       {13120, 50927619}, {13121, 50928387}, {13122, 50929155}, {13123, 67707139},
+       {13124, 50930947}, {13125, 50931715}, {13126, 50932483}, {13127, 84487683},
+       {13128, 67711747}, {13129, 34158339}, {13130, 84490499}, {13131, 34160131},
+       {13132, 67715075}, {13133, 67669507}, {13134, 50938883}, {13135, 50939651},
+       {13136, 50940419}, {13137, 67718403}, {13138, 34164995}, {13139, 50942723},
+       {13140, 67720707}, {13141, 34167299}, {13142, 84499459}, {13143, 50893827},
+       {13144, 34169091}, {13145, 34169603}, {13146, 34170115}, {13147, 34170627},
+       {13148, 34171139}, {13149, 34171651}, {13150, 34172163}, {13151, 34172675},
+       {13152, 34173187}, {13153, 34173699}, {13154, 50951427}, {13155, 50952195},
+       {13156, 50952963}, {13157, 50953731}, {13158, 50954499}, {13159, 50955267},
+       {13160, 50956035}, {13161, 50956803}, {13162, 50957571}, {13163, 50958339},
+       {13164, 50959107}, {13165, 50959875}, {13166, 50960643}, {13167, 50961411},
+       {13168, 50962179}, {13169, 50962947}, {13170, 34186499}, {13171, 34187011},
+       {13172, 50964739}, {13173, 34188291}, {13174, 34188803}, {13175, 34189315},
+       {13176, 50967043}, {13177, 50967811}, {13178, 34191363}, {13179, 34191875},
+       {13180, 34192387}, {13181, 34192899}, {13182, 34193411}, {13183, 67748355},
+       {13184, 34185987}, {13185, 34194947}, {13186, 34195459}, {13187, 34195971},
+       {13188, 34196483}, {13189, 34196995}, {13190, 34197507}, {13191, 34198019},
+       {13192, 50975747}, {13193, 67753731}, {13194, 34200323}, {13195, 34200835},
+       {13196, 34201347}, {13197, 34201859}, {13198, 34202371}, {13199, 34202883},
+       {13200, 34203395}, {13201, 50981123}, {13202, 50981891}, {13203, 50980355},
+       {13204, 50982659}, {13205, 34206211}, {13206, 34206723}, {13207, 34207235},
+       {13208, 33556995}, {13209, 34207747}, {13210, 34208259}, {13211, 34208771},
+       {13212, 34209283}, {13213, 34209795}, {13214, 34210307}, {13215, 50988035},
+       {13216, 50988803}, {13217, 34190083}, {13218, 50989571}, {13219, 50990339},
+       {13220, 50991107}, {13221, 34190851}, {13222, 50991875}, {13223, 50992643},
+       {13224, 67770627}, {13225, 34185987}, {13226, 50994435}, {13227, 50995203},
+       {13228, 50995971}, {13229, 50996739}, {13230, 84551939}, {13231, 101330435},
+       {13232, 34223107}, {13233, 34223619}, {13234, 34224131}, {13235, 34224643},
+       {13236, 34225155}, {13237, 34225667}, {13238, 34226179}, {13239, 34226691},
+       {13240, 34227203}, {13241, 34226691}, {13242, 34227715}, {13243, 34228227},
+       {13244, 34228739}, {13245, 34229251}, {13246, 34229763}, {13247, 34229251},
+       {13248, 34230275}, {13249, 34230787}, {13250, 2}, {13251, 34231299},
+       {13252, 33817347}, {13253, 33554947}, {13254, 67786243}, {13255, 2},
+       {13256, 34232835}, {13257, 34233347}, {13258, 34233859}, {13259, 34185731},
+       {13260, 34234371}, {13261, 34234883}, {13262, 34210307}, {13263, 34235395},
+       {13264, 33557251}, {13265, 34235907}, {13266, 51013635}, {13267, 34237187},
+       {13268, 34197507}, {13269, 51014915}, {13270, 51015683}, {13271, 34239235},
+       {13272, 2}, {13273, 51016963}, {13274, 34240515}, {13275, 34221315},
+       {13276, 34241027}, {13277, 34241539}, {13278, 51019267}, {13279, 51020035},
+       {13280, 34243587}, {13281, 34244099}, {13282, 34244611}, {13283, 34245123},
+       {13284, 34245635}, {13285, 34246147}, {13286, 34246659}, {13287, 34247171},
+       {13288, 34247683}, {13289, 51025411}, {13290, 51026179}, {13291, 51026947},
+       {13292, 51027715}, {13293, 51028483}, {13294, 51029251}, {13295, 51030019},
+       {13296, 51030787}, {13297, 51031555}, {13298, 51032323}, {13299, 51033091},
+       {13300, 51033859}, {13301, 51034627}, {13302, 51035395}, {13303, 51036163},
+       {13304, 51036931}, {13305, 51037699}, {13306, 51038467}, {13307, 51039235},
+       {13308, 51040003}, {13309, 51040771}, {13310, 51041539}, {13311, 51042307},
+       {13312, 1}, {42125, 2}, {42128, 1}, {42183, 2},
+       {42192, 1}, {42540, 2}, {42560, 17488643}, {42561, 1},
+       {42562, 17488899}, {42563, 1}, {42564, 17489155}, {42565, 1},
+       {42566, 17489411}, {42567, 1}, {42568, 17489667}, {42569, 1},
+       {42570, 16936451}, {42571, 1}, {42572, 17489923}, {42573, 1},
+       {42574, 17490179}, {42575, 1}, {42576, 17490435}, {42577, 1},
+       {42578, 17490691}, {42579, 1}, {42580, 17490947}, {42581, 1},
+       {42582, 17491203}, {42583, 1}, {42584, 17491459}, {42585, 1},
+       {42586, 17491715}, {42587, 1}, {42588, 17491971}, {42589, 1},
+       {42590, 17492227}, {42591, 1}, {42592, 17492483}, {42593, 1},
+       {42594, 17492739}, {42595, 1}, {42596, 17492995}, {42597, 1},
+       {42598, 17493251}, {42599, 1}, {42600, 17493507}, {42601, 1},
+       {42602, 17493763}, {42603, 1}, {42604, 17494019}, {42605, 1},
+       {42624, 17494275}, {42625, 1}, {42626, 17494531}, {42627, 1},
+       {42628, 17494787}, {42629, 1}, {42630, 17495043}, {42631, 1},
+       {42632, 17495299}, {42633, 1}, {42634, 17495555}, {42635, 1},
+       {42636, 17495811}, {42637, 1}, {42638, 17496067}, {42639, 1},
+       {42640, 17496323}, {42641, 1}, {42642, 17496579}, {42643, 1},
+       {42644, 17496835}, {42645, 1}, {42646, 17497091}, {42647, 1},
+       {42648, 17497347}, {42649, 1}, {42650, 17497603}, {42651, 1},
+       {42652, 16873219}, {42653, 16873731}, {42654, 1}, {42744, 2},
+       {42752, 1}, {42786, 17497859}, {42787, 1}, {42788, 17498115},
+       {42789, 1}, {42790, 17498371}, {42791, 1}, {42792, 17498627},
+       {42793, 1}, {42794, 17498883}, {42795, 1}, {42796, 17499139},
+       {42797, 1}, {42798, 17499395}, {42799, 1}, {42802, 17499651},
+       {42803, 1}, {42804, 17499907}, {42805, 1}, {42806, 17500163},
+       {42807, 1}, {42808, 17500419}, {42809, 1}, {42810, 17500675},
+       {42811, 1}, {42812, 17500931}, {42813, 1}, {42814, 17501187},
+       {42815, 1}, {42816, 17501443}, {42817, 1}, {42818, 17501699},
+       {42819, 1}, {42820, 17501955}, {42821, 1}, {42822, 17502211},
+       {42823, 1}, {42824, 17502467}, {42825, 1}, {42826, 17502723},
+       {42827, 1}, {42828, 17502979}, {42829, 1}, {42830, 17503235},
+       {42831, 1}, {42832, 17503491}, {42833, 1}, {42834, 17503747},
+       {42835, 1}, {42836, 17504003}, {42837, 1}, {42838, 17504259},
+       {42839, 1}, {42840, 17504515}, {42841, 1}, {42842, 17504771},
+       {42843, 1}, {42844, 17505027}, {42845, 1}, {42846, 17505283},
+       {42847, 1}, {42848, 17505539}, {42849, 1}, {42850, 17505795},
+       {42851, 1}, {42852, 17506051}, {42853, 1}, {42854, 17506307},
+       {42855, 1}, {42856, 17506563}, {42857, 1}, {42858, 17506819},
+       {42859, 1}, {42860, 17507075}, {42861, 1}, {42862, 17507331},
+       {42863, 1}, {42864, 17507331}, {42865, 1}, {42873, 17507587},
+       {42874, 1}, {42875, 17507843}, {42876, 1}, {42877, 17508099},
+       {42878, 17508355}, {42879, 1}, {42880, 17508611}, {42881, 1},
+       {42882, 17508867}, {42883, 1}, {42884, 17509123}, {42885, 1},
+       {42886, 17509379}, {42887, 1}, {42891, 17509635}, {42892, 1},
+       {42893, 16951299}, {42894, 1}, {42896, 17509891}, {42897, 1},
+       {42898, 17510147}, {42899, 1}, {42902, 17510403}, {42903, 1},
+       {42904, 17510659}, {42905, 1}, {42906, 17510915}, {42907, 1},
+       {42908, 17511171}, {42909, 1}, {42910, 17511427}, {42911, 1},
+       {42912, 17511683}, {42913, 1}, {42914, 17511939}, {42915, 1},
+       {42916, 17512195}, {42917, 1}, {42918, 17512451}, {42919, 1},
+       {42920, 17512707}, {42921, 1}, {42922, 16841475}, {42923, 16948995},
+       {42924, 16951043}, {42925, 17512963}, {42926, 16951555}, {42927, 1},
+       {42928, 17513219}, {42929, 17513475}, {42930, 16952067}, {42931, 17513731},
+       {42932, 17513987}, {42933, 1}, {42934, 17514243}, {42935, 1},
+       {42936, 17514499}, {42937, 1}, {42938, 17514755}, {42939, 1},
+       {42940, 17515011}, {42941, 1}, {42942, 17515267}, {42943, 1},
+       {42944, 17515523}, {42945, 1}, {42946, 17515779}, {42947, 1},
+       {42948, 17516035}, {42949, 16954371}, {42950, 17516291}, {42951, 17516547},
+       {42952, 1}, {42953, 17516803}, {42954, 1}, {42955, 2},
+       {42960, 17517059}, {42961, 1}, {42962, 2}, {42963, 1},
+       {42964, 2}, {42965, 1}, {42966, 17517315}, {42967, 1},
+       {42968, 17517571}, {42969, 1}, {42970, 2}, {42994, 16777731},
+       {42995, 16778499}, {42996, 16781315}, {42997, 17517827}, {42998, 1},
+       {43000, 16802051}, {43001, 16808195}, {43002, 1}, {43053, 2},
+       {43056, 1}, {43066, 2}, {43072, 1}, {43128, 2},
+       {43136, 1}, {43206, 2}, {43214, 1}, {43226, 2},
+       {43232, 1}, {43348, 2}, {43359, 1}, {43389, 2},
+       {43392, 1}, {43470, 2}, {43471, 1}, {43482, 2},
+       {43486, 1}, {43519, 2}, {43520, 1}, {43575, 2},
+       {43584, 1}, {43598, 2}, {43600, 1}, {43610, 2},
+       {43612, 1}, {43715, 2}, {43739, 1}, {43767, 2},
+       {43777, 1}, {43783, 2}, {43785, 1}, {43791, 2},
+       {43793, 1}, {43799, 2}, {43808, 1}, {43815, 2},
+       {43816, 1}, {43823, 2}, {43824, 1}, {43868, 17498371},
+       {43869, 17518083}, {43870, 17124867}, {43871, 17518339}, {43872, 1},
+       {43881, 17518595}, {43882, 1}, {43884, 2}, {43888, 17518851},
+       {43889, 17519107}, {43890, 17519363}, {43891, 17519619}, {43892, 17519875},
+       {43893, 17520131}, {43894, 17520387}, {43895, 17520643}, {43896, 17520899},
+       {43897, 17521155}, {43898, 17521411}, {43899, 17521667}, {43900, 17521923},
+       {43901, 17522179}, {43902, 17522435}, {43903, 17522691}, {43904, 17522947},
+       {43905, 17523203}, {43906, 17523459}, {43907, 17523715}, {43908, 17523971},
+       {43909, 17524227}, {43910, 17524483}, {43911, 17524739}, {43912, 17524995},
+       {43913, 17525251}, {43914, 17525507}, {43915, 17525763}, {43916, 17526019},
+       {43917, 17526275}, {43918, 17526531}, {43919, 17526787}, {43920, 17527043},
+       {43921, 17527299}, {43922, 17527555}, {43923, 17527811}, {43924, 17528067},
+       {43925, 17528323}, {43926, 17528579}, {43927, 17528835}, {43928, 17529091},
+       {43929, 17529347}, {43930, 17529603}, {43931, 17529859}, {43932, 17530115},
+       {43933, 17530371}, {43934, 17530627}, {43935, 17530883}, {43936, 17531139},
+       {43937, 17531395}, {43938, 17531651}, {43939, 17531907}, {43940, 17532163},
+       {43941, 17532419}, {43942, 17532675}, {43943, 17532931}, {43944, 17533187},
+       {43945, 17533443}, {43946, 17533699}, {43947, 17533955}, {43948, 17534211},
+       {43949, 17534467}, {43950, 17534723}, {43951, 17534979}, {43952, 17535235},
+       {43953, 17535491}, {43954, 17535747}, {43955, 17536003}, {43956, 17536259},
+       {43957, 17536515}, {43958, 17536771}, {43959, 17537027}, {43960, 17537283},
+       {43961, 17537539}, {43962, 17537795}, {43963, 17538051}, {43964, 17538307},
+       {43965, 17538563}, {43966, 17538819}, {43967, 17539075}, {43968, 1},
+       {44014, 2}, {44016, 1}, {44026, 2}, {44032, 1},
+       {55204, 2}, {55216, 1}, {55239, 2}, {55243, 1},
+       {55292, 2}, {63744, 17539331}, {63745, 17539587}, {63746, 17182211},
+       {63747, 17539843}, {63748, 17540099}, {63749, 17540355}, {63750, 17540611},
+       {63751, 17196035}, {63753, 17540867}, {63754, 17184259}, {63755, 17541123},
+       {63756, 17541379}, {63757, 17541635}, {63758, 17541891}, {63759, 17542147},
+       {63760, 17542403}, {63761, 17542659}, {63762, 17542915}, {63763, 17543171},
+       {63764, 17543427}, {63765, 17543683}, {63766, 17543939}, {63767, 17544195},
+       {63768, 17544451}, {63769, 17544707}, {63770, 17544963}, {63771, 17545219},
+       {63772, 17545475}, {63773, 17545731}, {63774, 17545987}, {63775, 17546243},
+       {63776, 17546499}, {63777, 17546755}, {63778, 17547011}, {63779, 17547267},
+       {63780, 17547523}, {63781, 17547779}, {63782, 17548035}, {63783, 17548291},
+       {63784, 17548547}, {63785, 17548803}, {63786, 17549059}, {63787, 17549315},
+       {63788, 17549571}, {63789, 17549827}, {63790, 17550083}, {63791, 17550339},
+       {63792, 17550595}, {63793, 17550851}, {63794, 17551107}, {63795, 17551363},
+       {63796, 17173507}, {63797, 17551619}, {63798, 17551875}, {63799, 17552131},
+       {63800, 17552387}, {63801, 17552643}, {63802, 17552899}, {63803, 17553155},
+       {63804, 17553411}, {63805, 17553667}, {63806, 17553923}, {63807, 17554179},
+       {63808, 17192195}, {63809, 17554435}, {63810, 17554691}, {63811, 17554947},
+       {63812, 17555203}, {63813, 17555459}, {63814, 17555715}, {63815, 17555971},
+       {63816, 17556227}, {63817, 17556483}, {63818, 17556739}, {63819, 17556995},
+       {63820, 17557251}, {63821, 17557507}, {63822, 17557763}, {63823, 17558019},
+       {63824, 17558275}, {63825, 17558531}, {63826, 17558787}, {63827, 17559043},
+       {63828, 17559299}, {63829, 17559555}, {63830, 17559811}, {63831, 17560067},
+       {63832, 17560323}, {63833, 17560579}, {63834, 17560835}, {63835, 17561091},
+       {63836, 17543427}, {63837, 17561347}, {63838, 17561603}, {63839, 17561859},
+       {63840, 17562115}, {63841, 17562371}, {63842, 17562627}, {63843, 17562883},
+       {63844, 17563139}, {63845, 17563395}, {63846, 17563651}, {63847, 17563907},
+       {63848, 17564163}, {63849, 17564419}, {63850, 17564675}, {63851, 17564931},
+       {63852, 17565187}, {63853, 17565443}, {63854, 17565699}, {63855, 17565955},
+       {63856, 17566211}, {63857, 17182723}, {63858, 17566467}, {63859, 17566723},
+       {63860, 17566979}, {63861, 17567235}, {63862, 17567491}, {63863, 17567747},
+       {63864, 17568003}, {63865, 17568259}, {63866, 17568515}, {63867, 17568771},
+       {63868, 17569027}, {63869, 17569283}, {63870, 17569539}, {63871, 17569795},
+       {63872, 17570051}, {63873, 17151235}, {63874, 17570307}, {63875, 17570563},
+       {63876, 17570819}, {63877, 17571075}, {63878, 17571331}, {63879, 17571587},
+       {63880, 17571843}, {63881, 17572099}, {63882, 17146371}, {63883, 17572355},
+       {63884, 17572611}, {63885, 17572867}, {63886, 17573123}, {63887, 17573379},
+       {63888, 17573635}, {63889, 17573891}, {63890, 17574147}, {63891, 17574403},
+       {63892, 17574659}, {63893, 17574915}, {63894, 17575171}, {63895, 17575427},
+       {63896, 17575683}, {63897, 17575939}, {63898, 17576195}, {63899, 17576451},
+       {63900, 17576707}, {63901, 17576963}, {63902, 17577219}, {63903, 17577475},
+       {63904, 17577731}, {63905, 17565955}, {63906, 17577987}, {63907, 17578243},
+       {63908, 17578499}, {63909, 17578755}, {63910, 17579011}, {63911, 17579267},
+       {63912, 17317123}, {63913, 17579523}, {63914, 17561859}, {63915, 17579779},
+       {63916, 17580035}, {63917, 17580291}, {63918, 17580547}, {63919, 17580803},
+       {63920, 17581059}, {63921, 17581315}, {63922, 17581571}, {63923, 17581827},
+       {63924, 17582083}, {63925, 17582339}, {63926, 17582595}, {63927, 17582851},
+       {63928, 17583107}, {63929, 17583363}, {63930, 17583619}, {63931, 17583875},
+       {63932, 17584131}, {63933, 17584387}, {63934, 17584643}, {63935, 17543427},
+       {63936, 17584899}, {63937, 17585155}, {63938, 17585411}, {63939, 17585667},
+       {63940, 17195779}, {63941, 17585923}, {63942, 17586179}, {63943, 17586435},
+       {63944, 17586691}, {63945, 17586947}, {63946, 17587203}, {63947, 17587459},
+       {63948, 17587715}, {63949, 17587971}, {63950, 17588227}, {63951, 17588483},
+       {63952, 17588739}, {63953, 17254403}, {63954, 17588995}, {63955, 17589251},
+       {63956, 17589507}, {63957, 17589763}, {63958, 17590019}, {63959, 17590275},
+       {63960, 17590531}, {63961, 17590787}, {63962, 17591043}, {63963, 17562371},
+       {63964, 17591299}, {63965, 17591555}, {63966, 17591811}, {63967, 17592067},
+       {63968, 17592323}, {63969, 17592579}, {63970, 17592835}, {63971, 17593091},
+       {63972, 17593347}, {63973, 17593603}, {63974, 17593859}, {63975, 17594115},
+       {63976, 17594371}, {63977, 17184003}, {63978, 17594627}, {63979, 17594883},
+       {63980, 17595139}, {63981, 17595395}, {63982, 17595651}, {63983, 17595907},
+       {63984, 17596163}, {63985, 17596419}, {63986, 17596675}, {63987, 17596931},
+       {63988, 17597187}, {63989, 17597443}, {63990, 17597699}, {63991, 17171459},
+       {63992, 17597955}, {63993, 17598211}, {63994, 17598467}, {63995, 17598723},
+       {63996, 17598979}, {63997, 17599235}, {63998, 17599491}, {63999, 17599747},
+       {64000, 17600003}, {64001, 17600259}, {64002, 17600515}, {64003, 17600771},
+       {64004, 17601027}, {64005, 17601283}, {64006, 17601539}, {64007, 17601795},
+       {64008, 17178371}, {64009, 17602051}, {64010, 17179139}, {64011, 17602307},
+       {64012, 17602563}, {64013, 17602819}, {64014, 1}, {64016, 17603075},
+       {64017, 1}, {64018, 17603331}, {64019, 1}, {64021, 17603587},
+       {64022, 17603843}, {64023, 17604099}, {64024, 17604355}, {64025, 17604611},
+       {64026, 17604867}, {64027, 17605123}, {64028, 17605379}, {64029, 17605635},
+       {64030, 17173251}, {64031, 1}, {64032, 17605891}, {64033, 1},
+       {64034, 17606147}, {64035, 1}, {64037, 17606403}, {64038, 17606659},
+       {64039, 1}, {64042, 17606915}, {64043, 17607171}, {64044, 17607427},
+       {64045, 17607683}, {64046, 17607939}, {64047, 17608195}, {64048, 17608451},
+       {64049, 17608707}, {64050, 17608963}, {64051, 17609219}, {64052, 17609475},
+       {64053, 17609731}, {64054, 17609987}, {64055, 17610243}, {64056, 17610499},
+       {64057, 17610755}, {64058, 17611011}, {64059, 17611267}, {64060, 17153027},
+       {64061, 17611523}, {64062, 17611779}, {64063, 17612035}, {64064, 17612291},
+       {64065, 17612547}, {64066, 17612803}, {64067, 17613059}, {64068, 17613315},
+       {64069, 17613571}, {64070, 17613827}, {64071, 17614083}, {64072, 17614339},
+       {64073, 17614595}, {64074, 17614851}, {64075, 17615107}, {64076, 17265155},
+       {64077, 17615363}, {64078, 17615619}, {64079, 17615875}, {64080, 17616131},
+       {64081, 17268227}, {64082, 17616387}, {64083, 17616643}, {64084, 17616899},
+       {64085, 17617155}, {64086, 17617411}, {64087, 17575171}, {64088, 17617667},
+       {64089, 17617923}, {64090, 17618179}, {64091, 17618435}, {64092, 17618691},
+       {64093, 17618947}, {64095, 17619203}, {64096, 17619459}, {64097, 17619715},
+       {64098, 17619971}, {64099, 17620227}, {64100, 17620483}, {64101, 17620739},
+       {64102, 17620995}, {64103, 17606403}, {64104, 17621251}, {64105, 17621507},
+       {64106, 17621763}, {64107, 17622019}, {64108, 17622275}, {64109, 17622531},
+       {64110, 2}, {64112, 17622787}, {64113, 17623043}, {64114, 17623299},
+       {64115, 17623555}, {64116, 17623811}, {64117, 17624067}, {64118, 17624323},
+       {64119, 17624579}, {64120, 17609987}, {64121, 17624835}, {64122, 17625091},
+       {64123, 17625347}, {64124, 17603075}, {64125, 17625603}, {64126, 17625859},
+       {64127, 17626115}, {64128, 17626371}, {64129, 17626627}, {64130, 17626883},
+       {64131, 17627139}, {64132, 17627395}, {64133, 17627651}, {64134, 17627907},
+       {64135, 17628163}, {64136, 17628419}, {64137, 17612035}, {64138, 17628675},
+       {64139, 17612291}, {64140, 17628931}, {64141, 17629187}, {64142, 17629443},
+       {64143, 17629699}, {64144, 17629955}, {64145, 17603331}, {64146, 17548803},
+       {64147, 17630211}, {64148, 17630467}, {64149, 17161475}, {64150, 17566211},
+       {64151, 17587203}, {64152, 17630723}, {64153, 17630979}, {64154, 17614083},
+       {64155, 17631235}, {64156, 17614339}, {64157, 17631491}, {64158, 17631747},
+       {64159, 17632003}, {64160, 17603843}, {64161, 17632259}, {64162, 17632515},
+       {64163, 17632771}, {64164, 17633027}, {64165, 17633283}, {64166, 17604099},
+       {64167, 17633539}, {64168, 17633795}, {64169, 17634051}, {64170, 17634307},
+       {64171, 17634563}, {64172, 17634819}, {64173, 17617411}, {64174, 17635075},
+       {64175, 17635331}, {64176, 17575171}, {64177, 17635587}, {64178, 17618435},
+       {64179, 17635843}, {64180, 17636099}, {64181, 17636355}, {64182, 17636611},
+       {64183, 17636867}, {64184, 17619715}, {64185, 17637123}, {64186, 17606147},
+       {64187, 17637379}, {64188, 17619971}, {64189, 17561347}, {64190, 17637635},
+       {64191, 17620227}, {64192, 17637891}, {64193, 17620739}, {64194, 17638147},
+       {64195, 17638403}, {64196, 17638659}, {64197, 17638915}, {64198, 17639171},
+       {64199, 17621251}, {64200, 17605379}, {64201, 17639427}, {64202, 17621507},
+       {64203, 17639683}, {64204, 17621763}, {64205, 17639939}, {64206, 17196035},
+       {64207, 17640195}, {64208, 17640451}, {64209, 17640707}, {64210, 17640963},
+       {64211, 17641219}, {64212, 17641475}, {64213, 17641731}, {64214, 17641987},
+       {64215, 17642243}, {64216, 17642499}, {64217, 17642755}, {64218, 2},
+       {64256, 34420227}, {64257, 34420739}, {64258, 34421251}, {64259, 51197699},
+       {64260, 51198979}, {64261, 33559043}, {64263, 2}, {64275, 34422531},
+       {64276, 34423043}, {64277, 34423555}, {64278, 34424067}, {64279, 34424579},
+       {64280, 2}, {64285, 34425091}, {64286, 1}, {64287, 34425603},
+       {64288, 17648899}, {64289, 17044227}, {64290, 17044995}, {64291, 17649155},
+       {64292, 17649411}, {64293, 17649667}, {64294, 17649923}, {64295, 17650179},
+       {64296, 17650435}, {64297, 17037059}, {64298, 34427907}, {64299, 34428419},
+       {64300, 51206147}, {64301, 51206915}, {64302, 34430467}, {64303, 34430979},
+       {64304, 34431491}, {64305, 34432003}, {64306, 34432515}, {64307, 34433027},
+       {64308, 34433539}, {64309, 34434051}, {64310, 34434563}, {64311, 2},
+       {64312, 34435075}, {64313, 34435587}, {64314, 34436099}, {64315, 34436611},
+       {64316, 34437123}, {64317, 2}, {64318, 34437635}, {64319, 2},
+       {64320, 34438147}, {64321, 34438659}, {64322, 2}, {64323, 34439171},
+       {64324, 34439683}, {64325, 2}, {64326, 34440195}, {64327, 34440707},
+       {64328, 34441219}, {64329, 34428931}, {64330, 34441731}, {64331, 34442243},
+       {64332, 34442755}, {64333, 34443267}, {64334, 34443779}, {64335, 34444291},
+       {64336, 17667587}, {64338, 17667843}, {64342, 17668099}, {64346, 17668355},
+       {64350, 17668611}, {64354, 17668867}, {64358, 17669123}, {64362, 17669379},
+       {64366, 17669635}, {64370, 17669891}, {64374, 17670147}, {64378, 17670403},
+       {64382, 17670659}, {64386, 17670915}, {64388, 17671171}, {64390, 17671427},
+       {64392, 17671683}, {64394, 17671939}, {64396, 17672195}, {64398, 17672451},
+       {64402, 17672707}, {64406, 17672963}, {64410, 17673219}, {64414, 17673475},
+       {64416, 17673731}, {64420, 17673987}, {64422, 17674243}, {64426, 17674499},
+       {64430, 17674755}, {64432, 17675011}, {64434, 1}, {64451, 2},
+       {64467, 17675267}, {64471, 16911363}, {64473, 17675523}, {64475, 17675779},
+       {64477, 33688579}, {64478, 17676035}, {64480, 17676291}, {64482, 17676547},
+       {64484, 17676803}, {64488, 17677059}, {64490, 34454531}, {64492, 34455043},
+       {64494, 34455555}, {64496, 34456067}, {64498, 34456579}, {64500, 34457091},
+       {64502, 34457603}, {64505, 34458115}, {64508, 17681411}, {64512, 34458883},
+       {64513, 34459395}, {64514, 34459907}, {64515, 34458115}, {64516, 34460419},
+       {64517, 34460931}, {64518, 34461443}, {64519, 34461955}, {64520, 34462467},
+       {64521, 34462979}, {64522, 34463491}, {64523, 34464003}, {64524, 34464515},
+       {64525, 34465027}, {64526, 34465539}, {64527, 34466051}, {64528, 34466563},
+       {64529, 34467075}, {64530, 34467587}, {64531, 34468099}, {64532, 34468611},
+       {64533, 34469123}, {64534, 34469635}, {64535, 34469379}, {64536, 34470147},
+       {64537, 34470659}, {64538, 34471171}, {64539, 34471683}, {64540, 34472195},
+       {64541, 34472707}, {64542, 34473219}, {64543, 34473731}, {64544, 34474243},
+       {64545, 34474755}, {64546, 34475267}, {64547, 34475779}, {64548, 34476291},
+       {64549, 34476803}, {64550, 34477315}, {64551, 34477827}, {64552, 34478339},
+       {64553, 34478851}, {64554, 34479363}, {64555, 34479875}, {64556, 34480387},
+       {64557, 34480899}, {64558, 34481411}, {64559, 34481923}, {64560, 34482435},
+       {64561, 34482947}, {64562, 34483459}, {64563, 34483971}, {64564, 34484483},
+       {64565, 34484995}, {64566, 34485507}, {64567, 34486019}, {64568, 34486531},
+       {64569, 34487043}, {64570, 34487555}, {64571, 34488067}, {64572, 34488579},
+       {64573, 34489091}, {64574, 34489603}, {64575, 34490115}, {64576, 34490627},
+       {64577, 34491139}, {64578, 34491651}, {64579, 34492163}, {64580, 34492675},
+       {64581, 34493187}, {64582, 34469891}, {64583, 34470403}, {64584, 34493699},
+       {64585, 34494211}, {64586, 34494723}, {64587, 34495235}, {64588, 34495747},
+       {64589, 34496259}, {64590, 34496771}, {64591, 34497283}, {64592, 34497795},
+       {64593, 34498307}, {64594, 34498819}, {64595, 34499331}, {64596, 34499843},
+       {64597, 34468867}, {64598, 34500355}, {64599, 34500867}, {64600, 34492931},
+       {64601, 34501379}, {64602, 34500099}, {64603, 34501891}, {64604, 34502403},
+       {64605, 34502915}, {64606, 51280643}, {64607, 51281411}, {64608, 51282179},
+       {64609, 51282947}, {64610, 51283715}, {64611, 51284483}, {64612, 34508035},
+       {64613, 34508547}, {64614, 34459907}, {64615, 34509059}, {64616, 34458115},
+       {64617, 34460419}, {64618, 34509571}, {64619, 34510083}, {64620, 34462467},
+       {64621, 34510595}, {64622, 34462979}, {64623, 34463491}, {64624, 34511107},
+       {64625, 34511619}, {64626, 34465539}, {64627, 34512131}, {64628, 34466051},
+       {64629, 34466563}, {64630, 34512643}, {64631, 34513155}, {64632, 34467587},
+       {64633, 34513667}, {64634, 34468099}, {64635, 34468611}, {64636, 34482947},
+       {64637, 34483459}, {64638, 34484995}, {64639, 34485507}, {64640, 34486019},
+       {64641, 34488067}, {64642, 34488579}, {64643, 34489091}, {64644, 34489603},
+       {64645, 34491651}, {64646, 34492163}, {64647, 34492675}, {64648, 34514179},
+       {64649, 34493699}, {64650, 34514691}, {64651, 34515203}, {64652, 34496771},
+       {64653, 34515715}, {64654, 34497283}, {64655, 34497795}, {64656, 34502915},
+       {64657, 34516227}, {64658, 34516739}, {64659, 34492931}, {64660, 34494979},
+       {64661, 34501379}, {64662, 34500099}, {64663, 34458883}, {64664, 34459395},
+       {64665, 34517251}, {64666, 34459907}, {64667, 34517763}, {64668, 34460931},
+       {64669, 34461443}, {64670, 34461955}, {64671, 34462467}, {64672, 34518275},
+       {64673, 34464003}, {64674, 34464515}, {64675, 34465027}, {64676, 34465539},
+       {64677, 34518787}, {64678, 34467587}, {64679, 34469123}, {64680, 34469635},
+       {64681, 34469379}, {64682, 34470147}, {64683, 34470659}, {64684, 34471683},
+       {64685, 34472195}, {64686, 34472707}, {64687, 34473219}, {64688, 34473731},
+       {64689, 34474243}, {64690, 34519299}, {64691, 34474755}, {64692, 34475267},
+       {64693, 34475779}, {64694, 34476291}, {64695, 34476803}, {64696, 34477315},
+       {64697, 34478339}, {64698, 34478851}, {64699, 34479363}, {64700, 34479875},
+       {64701, 34480387}, {64702, 34480899}, {64703, 34481411}, {64704, 34481923},
+       {64705, 34482435}, {64706, 34483971}, {64707, 34484483}, {64708, 34486531},
+       {64709, 34487043}, {64710, 34487555}, {64711, 34488067}, {64712, 34488579},
+       {64713, 34490115}, {64714, 34490627}, {64715, 34491139}, {64716, 34491651},
+       {64717, 34519811}, {64718, 34493187}, {64719, 34469891}, {64720, 34470403},
+       {64721, 34493699}, {64722, 34495235}, {64723, 34495747}, {64724, 34496259},
+       {64725, 34496771}, {64726, 34520323}, {64727, 34498307}, {64728, 34498819},
+       {64729, 34520835}, {64730, 34468867}, {64731, 34500355}, {64732, 34500867},
+       {64733, 34492931}, {64734, 34498051}, {64735, 34459907}, {64736, 34517763},
+       {64737, 34462467}, {64738, 34518275}, {64739, 34465539}, {64740, 34518787},
+       {64741, 34467587}, {64742, 34521347}, {64743, 34473731}, {64744, 34521859},
+       {64745, 34522371}, {64746, 34522883}, {64747, 34488067}, {64748, 34488579},
+       {64749, 34491651}, {64750, 34496771}, {64751, 34520323}, {64752, 34492931},
+       {64753, 34498051}, {64754, 51300611}, {64755, 51301379}, {64756, 51302147},
+       {64757, 34525699}, {64758, 34526211}, {64759, 34526723}, {64760, 34527235},
+       {64761, 34527747}, {64762, 34528259}, {64763, 34528771}, {64764, 34529283},
+       {64765, 34529795}, {64766, 34530307}, {64767, 34530819}, {64768, 34500611},
+       {64769, 34531331}, {64770, 34531843}, {64771, 34532355}, {64772, 34501123},
+       {64773, 34532867}, {64774, 34533379}, {64775, 34533891}, {64776, 34534403},
+       {64777, 34534915}, {64778, 34535427}, {64779, 34535939}, {64780, 34522371},
+       {64781, 34536451}, {64782, 34536963}, {64783, 34537475}, {64784, 34537987},
+       {64785, 34525699}, {64786, 34526211}, {64787, 34526723}, {64788, 34527235},
+       {64789, 34527747}, {64790, 34528259}, {64791, 34528771}, {64792, 34529283},
+       {64793, 34529795}, {64794, 34530307}, {64795, 34530819}, {64796, 34500611},
+       {64797, 34531331}, {64798, 34531843}, {64799, 34532355}, {64800, 34501123},
+       {64801, 34532867}, {64802, 34533379}, {64803, 34533891}, {64804, 34534403},
+       {64805, 34534915}, {64806, 34535427}, {64807, 34535939}, {64808, 34522371},
+       {64809, 34536451}, {64810, 34536963}, {64811, 34537475}, {64812, 34537987},
+       {64813, 34534915}, {64814, 34535427}, {64815, 34535939}, {64816, 34522371},
+       {64817, 34521859}, {64818, 34522883}, {64819, 34477827}, {64820, 34472195},
+       {64821, 34472707}, {64822, 34473219}, {64823, 34534915}, {64824, 34535427},
+       {64825, 34535939}, {64826, 34477827}, {64827, 34478339}, {64828, 34538499},
+       {64830, 1}, {64848, 51316227}, {64849, 51316995}, {64851, 51317763},
+       {64852, 51318531}, {64853, 51319299}, {64854, 51320067}, {64855, 51320835},
+       {64856, 51246851}, {64858, 51321603}, {64859, 51322371}, {64860, 51323139},
+       {64861, 51323907}, {64862, 51324675}, {64863, 51325443}, {64865, 51326211},
+       {64866, 51326979}, {64868, 51327747}, {64870, 51328515}, {64871, 51329283},
+       {64873, 51330051}, {64874, 51330819}, {64876, 51331587}, {64878, 51332355},
+       {64879, 51333123}, {64881, 51333891}, {64883, 51334659}, {64884, 51335427},
+       {64885, 51336195}, {64886, 51336963}, {64888, 51337731}, {64889, 51338499},
+       {64890, 51339267}, {64891, 51340035}, {64892, 51340803}, {64894, 51341571},
+       {64895, 51342339}, {64896, 51343107}, {64897, 51343875}, {64898, 51344643},
+       {64899, 51345411}, {64901, 51346179}, {64903, 51346947}, {64905, 51347715},
+       {64906, 51247107}, {64907, 51348483}, {64908, 51349251}, {64909, 51270403},
+       {64910, 51247619}, {64911, 51350019}, {64912, 2}, {64914, 51350787},
+       {64915, 51351555}, {64916, 51352323}, {64917, 51353091}, {64918, 51353859},
+       {64919, 51354627}, {64921, 51355395}, {64922, 51356163}, {64923, 51356931},
+       {64924, 51357699}, {64926, 51358467}, {64927, 51359235}, {64928, 51360003},
+       {64929, 51360771}, {64930, 51361539}, {64931, 51362307}, {64932, 51363075},
+       {64933, 51363843}, {64934, 51364611}, {64935, 51365379}, {64936, 51366147},
+       {64937, 51366915}, {64938, 51367683}, {64939, 51368451}, {64940, 51369219},
+       {64941, 51369987}, {64942, 51277571}, {64943, 51370755}, {64944, 51371523},
+       {64945, 51372291}, {64946, 51373059}, {64947, 51373827}, {64948, 51341571},
+       {64949, 51343107}, {64950, 51374595}, {64951, 51375363}, {64952, 51376131},
+       {64953, 51376899}, {64954, 51377667}, {64955, 51378435}, {64956, 51377667},
+       {64957, 51376131}, {64958, 51379203}, {64959, 51379971}, {64960, 51380739},
+       {64961, 51381507}, {64962, 51382275}, {64963, 51378435}, {64964, 51336195},
+       {64965, 51328515}, {64966, 51383043}, {64967, 51383811}, {64968, 2},
+       {64975, 1}, {64976, 2}, {65008, 51384579}, {65009, 51385347},
+       {65010, 68163331}, {65011, 68164355}, {65012, 68165379}, {65013, 68166403},
+       {65014, 68167427}, {65015, 68168451}, {65016, 68169475}, {65017, 51393283},
+       {65018, 303052291}, {65019, 135284739}, {65020, 68177923}, {65021, 1},
+       {65024, 0}, {65040, 17847299}, {65041, 17847555}, {65042, 2},
+       {65043, 17110531}, {65044, 16848643}, {65045, 17032963}, {65046, 17033987},
+       {65047, 17847811}, {65048, 17848067}, {65049, 2}, {65056, 1},
+       {65072, 2}, {65073, 17848323}, {65074, 17848579}, {65075, 17848835},
+       {65077, 17037827}, {65078, 17038083}, {65079, 17849091}, {65080, 17849347},
+       {65081, 17849603}, {65082, 17849859}, {65083, 17850115}, {65084, 17850371},
+       {65085, 17850627}, {65086, 17850883}, {65087, 17067267}, {65088, 17067523},
+       {65089, 17851139}, {65090, 17851395}, {65091, 17851651}, {65092, 17851907},
+       {65093, 1}, {65095, 17852163}, {65096, 17852419}, {65097, 33810691},
+       {65101, 17848835}, {65104, 17847299}, {65105, 17847555}, {65106, 2},
+       {65108, 16848643}, {65109, 17110531}, {65110, 17033987}, {65111, 17032963},
+       {65112, 17848323}, {65113, 17037827}, {65114, 17038083}, {65115, 17849091},
+       {65116, 17849347}, {65117, 17849603}, {65118, 17849859}, {65119, 17852675},
+       {65120, 17852931}, {65121, 17853187}, {65122, 17037059}, {65123, 17853443},
+       {65124, 17853699}, {65125, 17853955}, {65126, 17037571}, {65127, 2},
+       {65128, 17854211}, {65129, 17854467}, {65130, 17854723}, {65131, 17854979},
+       {65132, 2}, {65136, 34632451}, {65137, 34632963}, {65138, 34503427},
+       {65139, 1}, {65140, 34504195}, {65141, 2}, {65142, 34504963},
+       {65143, 34523395}, {65144, 34505731}, {65145, 34524163}, {65146, 34506499},
+       {65147, 34524931}, {65148, 34507267}, {65149, 34633475}, {65150, 34633987},
+       {65151, 34634499}, {65152, 17857795}, {65153, 17858051}, {65155, 17858307},
+       {65157, 17858563}, {65159, 17858819}, {65161, 17677315}, {65165, 16910339},
+       {65167, 17683715}, {65171, 17859075}, {65173, 17686787}, {65177, 17689859},
+       {65181, 17681923}, {65185, 17682435}, {65189, 17684995}, {65193, 17834499},
+       {65195, 17724675}, {65197, 17725187}, {65199, 17731587}, {65201, 17694979},
+       {65205, 17745155}, {65209, 17697027}, {65213, 17698051}, {65217, 17700099},
+       {65221, 17701123}, {65225, 17701635}, {65229, 17702659}, {65233, 17703683},
+       {65237, 17706755}, {65241, 17708803}, {65245, 17711107}, {65249, 17682947},
+       {65253, 17718019}, {65257, 17721091}, {65261, 16910851}, {65263, 17677059},
+       {65265, 16911875}, {65269, 34636547}, {65271, 34637059}, {65273, 34637571},
+       {65275, 34622467}, {65277, 2}, {65279, 0}, {65280, 2},
+       {65281, 17032963}, {65282, 17860867}, {65283, 17852675}, {65284, 17854467},
+       {65285, 17854723}, {65286, 17852931}, {65287, 17861123}, {65288, 17037827},
+       {65289, 17038083}, {65290, 17853187}, {65291, 17037059}, {65292, 17847299},
+       {65293, 17853443}, {65294, 17196547}, {65295, 17038595}, {65296, 17035523},
+       {65297, 16786947}, {65298, 16785155}, {65299, 16785411}, {65300, 16787715},
+       {65301, 17035779}, {65302, 17036035}, {65303, 17036291}, {65304, 17036547},
+       {65305, 17036803}, {65306, 17110531}, {65307, 16848643}, {65308, 17853699},
+       {65309, 17037571}, {65310, 17853955}, {65311, 17033987}, {65312, 17854979},
+       {65313, 16777219}, {65314, 16777475}, {65315, 16777731}, {65316, 16777987},
+       {65317, 16778243}, {65318, 16778499}, {65319, 16778755}, {65320, 16779011},
+       {65321, 16779267}, {65322, 16779523}, {65323, 16779779}, {65324, 16780035},
+       {65325, 16780291}, {65326, 16780547}, {65327, 16780803}, {65328, 16781059},
+       {65329, 16781315}, {65330, 16781571}, {65331, 16781827}, {65332, 16782083},
+       {65333, 16782339}, {65334, 16782595}, {65335, 16782851}, {65336, 16783107},
+       {65337, 16783363}, {65338, 16783619}, {65339, 17852163}, {65340, 17854211},
+       {65341, 17852419}, {65342, 17861379}, {65343, 17848835}, {65344, 17027075},
+       {65345, 16777219}, {65346, 16777475}, {65347, 16777731}, {65348, 16777987},
+       {65349, 16778243}, {65350, 16778499}, {65351, 16778755}, {65352, 16779011},
+       {65353, 16779267}, {65354, 16779523}, {65355, 16779779}, {65356, 16780035},
+       {65357, 16780291}, {65358, 16780547}, {65359, 16780803}, {65360, 16781059},
+       {65361, 16781315}, {65362, 16781571}, {65363, 16781827}, {65364, 16782083},
+       {65365, 16782339}, {65366, 16782595}, {65367, 16782851}, {65368, 16783107},
+       {65369, 16783363}, {65370, 16783619}, {65371, 17849091}, {65372, 17861635},
+       {65373, 17849347}, {65374, 17861891}, {65375, 17862147}, {65376, 17862403},
+       {65377, 17196547}, {65378, 17851139}, {65379, 17851395}, {65380, 17847555},
+       {65381, 17862659}, {65382, 17316867}, {65383, 17319427}, {65384, 17362435},
+       {65385, 17862915}, {65386, 17363971}, {65387, 17323523}, {65388, 17863171},
+       {65389, 17333763}, {65390, 17379587}, {65391, 17329155}, {65392, 17318147},
+       {65393, 17305603}, {65394, 17305859}, {65395, 17306115}, {65396, 17306371},
+       {65397, 17306627}, {65398, 17306883}, {65399, 17307139}, {65400, 17307395},
+       {65401, 17307651}, {65402, 17199107}, {65403, 17307907}, {65404, 17308163},
+       {65405, 17308419}, {65406, 17308675}, {65407, 17308931}, {65408, 17309187},
+       {65409, 17309443}, {65410, 17309699}, {65411, 17309955}, {65412, 17199363},
+       {65413, 17310211}, {65414, 17310467}, {65415, 17310723}, {65416, 17310979},
+       {65417, 17311235}, {65418, 17311491}, {65419, 17311747}, {65420, 17312003},
+       {65421, 17312259}, {65422, 17312515}, {65423, 17312771}, {65424, 17313027},
+       {65425, 17313283}, {65426, 17313539}, {65427, 17313795}, {65428, 17314051},
+       {65429, 17314307}, {65430, 17314563}, {65431, 17314819}, {65432, 17315075},
+       {65433, 17315331}, {65434, 17315587}, {65435, 17315843}, {65436, 17316099},
+       {65437, 17319939}, {65438, 17197827}, {65439, 17198339}, {65440, 2},
+       {65441, 17199619}, {65442, 17199875}, {65443, 17200131}, {65444, 17200387},
+       {65445, 17200643}, {65446, 17200899}, {65447, 17201155}, {65448, 17201411},
+       {65449, 17201667}, {65450, 17201923}, {65451, 17202179}, {65452, 17202435},
+       {65453, 17202691}, {65454, 17202947}, {65455, 17203203}, {65456, 17203459},
+       {65457, 17203715}, {65458, 17203971}, {65459, 17204227}, {65460, 17204483},
+       {65461, 17204739}, {65462, 17204995}, {65463, 17205251}, {65464, 17205507},
+       {65465, 17205763}, {65466, 17206019}, {65467, 17206275}, {65468, 17206531},
+       {65469, 17206787}, {65470, 17207043}, {65471, 2}, {65474, 17207299},
+       {65475, 17207555}, {65476, 17207811}, {65477, 17208067}, {65478, 17208323},
+       {65479, 17208579}, {65480, 2}, {65482, 17208835}, {65483, 17209091},
+       {65484, 17209347}, {65485, 17209603}, {65486, 17209859}, {65487, 17210115},
+       {65488, 2}, {65490, 17210371}, {65491, 17210627}, {65492, 17210883},
+       {65493, 17211139}, {65494, 17211395}, {65495, 17211651}, {65496, 2},
+       {65498, 17211907}, {65499, 17212163}, {65500, 17212419}, {65501, 2},
+       {65504, 17863427}, {65505, 17863683}, {65506, 17863939}, {65507, 33561859},
+       {65508, 17864195}, {65509, 17864451}, {65510, 17864707}, {65511, 2},
+       {65512, 17864963}, {65513, 17865219}, {65514, 17865475}, {65515, 17865731},
+       {65516, 17865987}, {65517, 17866243}, {65518, 17866499}, {65519, 2},
+       {65536, 1}, {65548, 2}, {65549, 1}, {65575, 2},
+       {65576, 1}, {65595, 2}, {65596, 1}, {65598, 2},
+       {65599, 1}, {65614, 2}, {65616, 1}, {65630, 2},
+       {65664, 1}, {65787, 2}, {65792, 1}, {65795, 2},
+       {65799, 1}, {65844, 2}, {65847, 1}, {65935, 2},
+       {65936, 1}, {65949, 2}, {65952, 1}, {65953, 2},
+       {66000, 1}, {66046, 2}, {66176, 1}, {66205, 2},
+       {66208, 1}, {66257, 2}, {66272, 1}, {66300, 2},
+       {66304, 1}, {66340, 2}, {66349, 1}, {66379, 2},
+       {66384, 1}, {66427, 2}, {66432, 1}, {66462, 2},
+       {66463, 1}, {66500, 2}, {66504, 1}, {66518, 2},
+       {66560, 17866755}, {66561, 17867011}, {66562, 17867267}, {66563, 17867523},
+       {66564, 17867779}, {66565, 17868035}, {66566, 17868291}, {66567, 17868547},
+       {66568, 17868803}, {66569, 17869059}, {66570, 17869315}, {66571, 17869571},
+       {66572, 17869827}, {66573, 17870083}, {66574, 17870339}, {66575, 17870595},
+       {66576, 17870851}, {66577, 17871107}, {66578, 17871363}, {66579, 17871619},
+       {66580, 17871875}, {66581, 17872131}, {66582, 17872387}, {66583, 17872643},
+       {66584, 17872899}, {66585, 17873155}, {66586, 17873411}, {66587, 17873667},
+       {66588, 17873923}, {66589, 17874179}, {66590, 17874435}, {66591, 17874691},
+       {66592, 17874947}, {66593, 17875203}, {66594, 17875459}, {66595, 17875715},
+       {66596, 17875971}, {66597, 17876227}, {66598, 17876483}, {66599, 17876739},
+       {66600, 1}, {66718, 2}, {66720, 1}, {66730, 2},
+       {66736, 17876995}, {66737, 17877251}, {66738, 17877507}, {66739, 17877763},
+       {66740, 17878019}, {66741, 17878275}, {66742, 17878531}, {66743, 17878787},
+       {66744, 17879043}, {66745, 17879299}, {66746, 17879555}, {66747, 17879811},
+       {66748, 17880067}, {66749, 17880323}, {66750, 17880579}, {66751, 17880835},
+       {66752, 17881091}, {66753, 17881347}, {66754, 17881603}, {66755, 17881859},
+       {66756, 17882115}, {66757, 17882371}, {66758, 17882627}, {66759, 17882883},
+       {66760, 17883139}, {66761, 17883395}, {66762, 17883651}, {66763, 17883907},
+       {66764, 17884163}, {66765, 17884419}, {66766, 17884675}, {66767, 17884931},
+       {66768, 17885187}, {66769, 17885443}, {66770, 17885699}, {66771, 17885955},
+       {66772, 2}, {66776, 1}, {66812, 2}, {66816, 1},
+       {66856, 2}, {66864, 1}, {66916, 2}, {66927, 1},
+       {66928, 17886211}, {66929, 17886467}, {66930, 17886723}, {66931, 17886979},
+       {66932, 17887235}, {66933, 17887491}, {66934, 17887747}, {66935, 17888003},
+       {66936, 17888259}, {66937, 17888515}, {66938, 17888771}, {66939, 2},
+       {66940, 17889027}, {66941, 17889283}, {66942, 17889539}, {66943, 17889795},
+       {66944, 17890051}, {66945, 17890307}, {66946, 17890563}, {66947, 17890819},
+       {66948, 17891075}, {66949, 17891331}, {66950, 17891587}, {66951, 17891843},
+       {66952, 17892099}, {66953, 17892355}, {66954, 17892611}, {66955, 2},
+       {66956, 17892867}, {66957, 17893123}, {66958, 17893379}, {66959, 17893635},
+       {66960, 17893891}, {66961, 17894147}, {66962, 17894403}, {66963, 2},
+       {66964, 17894659}, {66965, 17894915}, {66966, 2}, {66967, 1},
+       {66978, 2}, {66979, 1}, {66994, 2}, {66995, 1},
+       {67002, 2}, {67003, 1}, {67005, 2}, {67072, 1},
+       {67383, 2}, {67392, 1}, {67414, 2}, {67424, 1},
+       {67432, 2}, {67456, 1}, {67457, 17895171}, {67458, 17895427},
+       {67459, 16791043}, {67460, 17895683}, {67461, 16814083}, {67462, 2},
+       {67463, 17895939}, {67464, 17896195}, {67465, 17896451}, {67466, 17896707},
+       {67467, 16815363}, {67468, 16815619}, {67469, 17896963}, {67470, 17897219},
+       {67471, 17897475}, {67472, 17897731}, {67473, 17897987}, {67474, 17898243},
+       {67475, 16817155}, {67476, 17898499}, {67477, 16802051}, {67478, 17898755},
+       {67479, 17899011}, {67480, 17899267}, {67481, 17899523}, {67482, 17899779},
+       {67483, 17512963}, {67484, 17900035}, {67485, 17900291}, {67486, 17900547},
+       {67487, 17900803}, {67488, 17901059}, {67489, 17901315}, {67490, 16795395},
+       {67491, 17901571}, {67492, 17901827}, {67493, 16781315}, {67494, 17902083},
+       {67495, 17902339}, {67496, 17125379}, {67497, 17902595}, {67498, 16819971},
+       {67499, 17902851}, {67500, 17903107}, {67501, 17903363}, {67502, 17903619},
+       {67503, 16820995}, {67504, 17903875}, {67505, 2}, {67506, 17904131},
+       {67507, 17904387}, {67508, 17904643}, {67509, 17904899}, {67510, 17905155},
+       {67511, 17905411}, {67512, 17905667}, {67513, 17905923}, {67514, 17906179},
+       {67515, 2}, {67584, 1}, {67590, 2}, {67592, 1},
+       {67593, 2}, {67594, 1}, {67638, 2}, {67639, 1},
+       {67641, 2}, {67644, 1}, {67645, 2}, {67647, 1},
+       {67670, 2}, {67671, 1}, {67743, 2}, {67751, 1},
+       {67760, 2}, {67808, 1}, {67827, 2}, {67828, 1},
+       {67830, 2}, {67835, 1}, {67868, 2}, {67871, 1},
+       {67898, 2}, {67903, 1}, {67904, 2}, {67968, 1},
+       {68024, 2}, {68028, 1}, {68048, 2}, {68050, 1},
+       {68100, 2}, {68101, 1}, {68103, 2}, {68108, 1},
+       {68116, 2}, {68117, 1}, {68120, 2}, {68121, 1},
+       {68150, 2}, {68152, 1}, {68155, 2}, {68159, 1},
+       {68169, 2}, {68176, 1}, {68185, 2}, {68192, 1},
+       {68256, 2}, {68288, 1}, {68327, 2}, {68331, 1},
+       {68343, 2}, {68352, 1}, {68406, 2}, {68409, 1},
+       {68438, 2}, {68440, 1}, {68467, 2}, {68472, 1},
+       {68498, 2}, {68505, 1}, {68509, 2}, {68521, 1},
+       {68528, 2}, {68608, 1}, {68681, 2}, {68736, 17906435},
+       {68737, 17906691}, {68738, 17906947}, {68739, 17907203}, {68740, 17907459},
+       {68741, 17907715}, {68742, 17907971}, {68743, 17908227}, {68744, 17908483},
+       {68745, 17908739}, {68746, 17908995}, {68747, 17909251}, {68748, 17909507},
+       {68749, 17909763}, {68750, 17910019}, {68751, 17910275}, {68752, 17910531},
+       {68753, 17910787}, {68754, 17911043}, {68755, 17911299}, {68756, 17911555},
+       {68757, 17911811}, {68758, 17912067}, {68759, 17912323}, {68760, 17912579},
+       {68761, 17912835}, {68762, 17913091}, {68763, 17913347}, {68764, 17913603},
+       {68765, 17913859}, {68766, 17914115}, {68767, 17914371}, {68768, 17914627},
+       {68769, 17914883}, {68770, 17915139}, {68771, 17915395}, {68772, 17915651},
+       {68773, 17915907}, {68774, 17916163}, {68775, 17916419}, {68776, 17916675},
+       {68777, 17916931}, {68778, 17917187}, {68779, 17917443}, {68780, 17917699},
+       {68781, 17917955}, {68782, 17918211}, {68783, 17918467}, {68784, 17918723},
+       {68785, 17918979}, {68786, 17919235}, {68787, 2}, {68800, 1},
+       {68851, 2}, {68858, 1}, {68904, 2}, {68912, 1},
+       {68922, 2}, {69216, 1}, {69247, 2}, {69248, 1},
+       {69290, 2}, {69291, 1}, {69294, 2}, {69296, 1},
+       {69298, 2}, {69373, 1}, {69416, 2}, {69424, 1},
+       {69466, 2}, {69488, 1}, {69514, 2}, {69552, 1},
+       {69580, 2}, {69600, 1}, {69623, 2}, {69632, 1},
+       {69710, 2}, {69714, 1}, {69750, 2}, {69759, 1},
+       {69821, 2}, {69822, 1}, {69827, 2}, {69840, 1},
+       {69865, 2}, {69872, 1}, {69882, 2}, {69888, 1},
+       {69941, 2}, {69942, 1}, {69960, 2}, {69968, 1},
+       {70007, 2}, {70016, 1}, {70112, 2}, {70113, 1},
+       {70133, 2}, {70144, 1}, {70162, 2}, {70163, 1},
+       {70210, 2}, {70272, 1}, {70279, 2}, {70280, 1},
+       {70281, 2}, {70282, 1}, {70286, 2}, {70287, 1},
+       {70302, 2}, {70303, 1}, {70314, 2}, {70320, 1},
+       {70379, 2}, {70384, 1}, {70394, 2}, {70400, 1},
+       {70404, 2}, {70405, 1}, {70413, 2}, {70415, 1},
+       {70417, 2}, {70419, 1}, {70441, 2}, {70442, 1},
+       {70449, 2}, {70450, 1}, {70452, 2}, {70453, 1},
+       {70458, 2}, {70459, 1}, {70469, 2}, {70471, 1},
+       {70473, 2}, {70475, 1}, {70478, 2}, {70480, 1},
+       {70481, 2}, {70487, 1}, {70488, 2}, {70493, 1},
+       {70500, 2}, {70502, 1}, {70509, 2}, {70512, 1},
+       {70517, 2}, {70656, 1}, {70748, 2}, {70749, 1},
+       {70754, 2}, {70784, 1}, {70856, 2}, {70864, 1},
+       {70874, 2}, {71040, 1}, {71094, 2}, {71096, 1},
+       {71134, 2}, {71168, 1}, {71237, 2}, {71248, 1},
+       {71258, 2}, {71264, 1}, {71277, 2}, {71296, 1},
+       {71354, 2}, {71360, 1}, {71370, 2}, {71424, 1},
+       {71451, 2}, {71453, 1}, {71468, 2}, {71472, 1},
+       {71495, 2}, {71680, 1}, {71740, 2}, {71840, 17919491},
+       {71841, 17919747}, {71842, 17920003}, {71843, 17920259}, {71844, 17920515},
+       {71845, 17920771}, {71846, 17921027}, {71847, 17921283}, {71848, 17921539},
+       {71849, 17921795}, {71850, 17922051}, {71851, 17922307}, {71852, 17922563},
+       {71853, 17922819}, {71854, 17923075}, {71855, 17923331}, {71856, 17923587},
+       {71857, 17923843}, {71858, 17924099}, {71859, 17924355}, {71860, 17924611},
+       {71861, 17924867}, {71862, 17925123}, {71863, 17925379}, {71864, 17925635},
+       {71865, 17925891}, {71866, 17926147}, {71867, 17926403}, {71868, 17926659},
+       {71869, 17926915}, {71870, 17927171}, {71871, 17927427}, {71872, 1},
+       {71923, 2}, {71935, 1}, {71943, 2}, {71945, 1},
+       {71946, 2}, {71948, 1}, {71956, 2}, {71957, 1},
+       {71959, 2}, {71960, 1}, {71990, 2}, {71991, 1},
+       {71993, 2}, {71995, 1}, {72007, 2}, {72016, 1},
+       {72026, 2}, {72096, 1}, {72104, 2}, {72106, 1},
+       {72152, 2}, {72154, 1}, {72165, 2}, {72192, 1},
+       {72264, 2}, {72272, 1}, {72355, 2}, {72368, 1},
+       {72441, 2}, {72448, 1}, {72458, 2}, {72704, 1},
+       {72713, 2}, {72714, 1}, {72759, 2}, {72760, 1},
+       {72774, 2}, {72784, 1}, {72813, 2}, {72816, 1},
+       {72848, 2}, {72850, 1}, {72872, 2}, {72873, 1},
+       {72887, 2}, {72960, 1}, {72967, 2}, {72968, 1},
+       {72970, 2}, {72971, 1}, {73015, 2}, {73018, 1},
+       {73019, 2}, {73020, 1}, {73022, 2}, {73023, 1},
+       {73032, 2}, {73040, 1}, {73050, 2}, {73056, 1},
+       {73062, 2}, {73063, 1}, {73065, 2}, {73066, 1},
+       {73103, 2}, {73104, 1}, {73106, 2}, {73107, 1},
+       {73113, 2}, {73120, 1}, {73130, 2}, {73440, 1},
+       {73465, 2}, {73472, 1}, {73489, 2}, {73490, 1},
+       {73531, 2}, {73534, 1}, {73562, 2}, {73648, 1},
+       {73649, 2}, {73664, 1}, {73714, 2}, {73727, 1},
+       {74650, 2}, {74752, 1}, {74863, 2}, {74864, 1},
+       {74869, 2}, {74880, 1}, {75076, 2}, {77712, 1},
+       {77811, 2}, {77824, 1}, {78896, 2}, {78912, 1},
+       {78934, 2}, {82944, 1}, {83527, 2}, {92160, 1},
+       {92729, 2}, {92736, 1}, {92767, 2}, {92768, 1},
+       {92778, 2}, {92782, 1}, {92863, 2}, {92864, 1},
+       {92874, 2}, {92880, 1}, {92910, 2}, {92912, 1},
+       {92918, 2}, {92928, 1}, {92998, 2}, {93008, 1},
+       {93018, 2}, {93019, 1}, {93026, 2}, {93027, 1},
+       {93048, 2}, {93053, 1}, {93072, 2}, {93760, 17927683},
+       {93761, 17927939}, {93762, 17928195}, {93763, 17928451}, {93764, 17928707},
+       {93765, 17928963}, {93766, 17929219}, {93767, 17929475}, {93768, 17929731},
+       {93769, 17929987}, {93770, 17930243}, {93771, 17930499}, {93772, 17930755},
+       {93773, 17931011}, {93774, 17931267}, {93775, 17931523}, {93776, 17931779},
+       {93777, 17932035}, {93778, 17932291}, {93779, 17932547}, {93780, 17932803},
+       {93781, 17933059}, {93782, 17933315}, {93783, 17933571}, {93784, 17933827},
+       {93785, 17934083}, {93786, 17934339}, {93787, 17934595}, {93788, 17934851},
+       {93789, 17935107}, {93790, 17935363}, {93791, 17935619}, {93792, 1},
+       {93851, 2}, {93952, 1}, {94027, 2}, {94031, 1},
+       {94088, 2}, {94095, 1}, {94112, 2}, {94176, 1},
+       {94181, 2}, {94192, 1}, {94194, 2}, {94208, 1},
+       {100344, 2}, {100352, 1}, {101590, 2}, {101632, 1},
+       {101641, 2}, {110576, 1}, {110580, 2}, {110581, 1},
+       {110588, 2}, {110589, 1}, {110591, 2}, {110592, 1},
+       {110883, 2}, {110898, 1}, {110899, 2}, {110928, 1},
+       {110931, 2}, {110933, 1}, {110934, 2}, {110948, 1},
+       {110952, 2}, {110960, 1}, {111356, 2}, {113664, 1},
+       {113771, 2}, {113776, 1}, {113789, 2}, {113792, 1},
+       {113801, 2}, {113808, 1}, {113818, 2}, {113820, 1},
+       {113824, 0}, {113828, 2}, {118528, 1}, {118574, 2},
+       {118576, 1}, {118599, 2}, {118608, 1}, {118724, 2},
+       {118784, 1}, {119030, 2}, {119040, 1}, {119079, 2},
+       {119081, 1}, {119134, 34713091}, {119135, 34713603}, {119136, 51491331},
+       {119137, 51492099}, {119138, 51492867}, {119139, 51493635}, {119140, 51494403},
+       {119141, 1}, {119155, 2}, {119163, 1}, {119227, 34717955},
+       {119228, 34718467}, {119229, 51496195}, {119230, 51496963}, {119231, 51497731},
+       {119232, 51498499}, {119233, 1}, {119275, 2}, {119296, 1},
+       {119366, 2}, {119488, 1}, {119508, 2}, {119520, 1},
+       {119540, 2}, {119552, 1}, {119639, 2}, {119648, 1},
+       {119673, 2}, {119808, 16777219}, {119809, 16777475}, {119810, 16777731},
+       {119811, 16777987}, {119812, 16778243}, {119813, 16778499}, {119814, 16778755},
+       {119815, 16779011}, {119816, 16779267}, {119817, 16779523}, {119818, 16779779},
+       {119819, 16780035}, {119820, 16780291}, {119821, 16780547}, {119822, 16780803},
+       {119823, 16781059}, {119824, 16781315}, {119825, 16781571}, {119826, 16781827},
+       {119827, 16782083}, {119828, 16782339}, {119829, 16782595}, {119830, 16782851},
+       {119831, 16783107}, {119832, 16783363}, {119833, 16783619}, {119834, 16777219},
+       {119835, 16777475}, {119836, 16777731}, {119837, 16777987}, {119838, 16778243},
+       {119839, 16778499}, {119840, 16778755}, {119841, 16779011}, {119842, 16779267},
+       {119843, 16779523}, {119844, 16779779}, {119845, 16780035}, {119846, 16780291},
+       {119847, 16780547}, {119848, 16780803}, {119849, 16781059}, {119850, 16781315},
+       {119851, 16781571}, {119852, 16781827}, {119853, 16782083}, {119854, 16782339},
+       {119855, 16782595}, {119856, 16782851}, {119857, 16783107}, {119858, 16783363},
+       {119859, 16783619}, {119860, 16777219}, {119861, 16777475}, {119862, 16777731},
+       {119863, 16777987}, {119864, 16778243}, {119865, 16778499}, {119866, 16778755},
+       {119867, 16779011}, {119868, 16779267}, {119869, 16779523}, {119870, 16779779},
+       {119871, 16780035}, {119872, 16780291}, {119873, 16780547}, {119874, 16780803},
+       {119875, 16781059}, {119876, 16781315}, {119877, 16781571}, {119878, 16781827},
+       {119879, 16782083}, {119880, 16782339}, {119881, 16782595}, {119882, 16782851},
+       {119883, 16783107}, {119884, 16783363}, {119885, 16783619}, {119886, 16777219},
+       {119887, 16777475}, {119888, 16777731}, {119889, 16777987}, {119890, 16778243},
+       {119891, 16778499}, {119892, 16778755}, {119893, 2}, {119894, 16779267},
+       {119895, 16779523}, {119896, 16779779}, {119897, 16780035}, {119898, 16780291},
+       {119899, 16780547}, {119900, 16780803}, {119901, 16781059}, {119902, 16781315},
+       {119903, 16781571}, {119904, 16781827}, {119905, 16782083}, {119906, 16782339},
+       {119907, 16782595}, {119908, 16782851}, {119909, 16783107}, {119910, 16783363},
+       {119911, 16783619}, {119912, 16777219}, {119913, 16777475}, {119914, 16777731},
+       {119915, 16777987}, {119916, 16778243}, {119917, 16778499}, {119918, 16778755},
+       {119919, 16779011}, {119920, 16779267}, {119921, 16779523}, {119922, 16779779},
+       {119923, 16780035}, {119924, 16780291}, {119925, 16780547}, {119926, 16780803},
+       {119927, 16781059}, {119928, 16781315}, {119929, 16781571}, {119930, 16781827},
+       {119931, 16782083}, {119932, 16782339}, {119933, 16782595}, {119934, 16782851},
+       {119935, 16783107}, {119936, 16783363}, {119937, 16783619}, {119938, 16777219},
+       {119939, 16777475}, {119940, 16777731}, {119941, 16777987}, {119942, 16778243},
+       {119943, 16778499}, {119944, 16778755}, {119945, 16779011}, {119946, 16779267},
+       {119947, 16779523}, {119948, 16779779}, {119949, 16780035}, {119950, 16780291},
+       {119951, 16780547}, {119952, 16780803}, {119953, 16781059}, {119954, 16781315},
+       {119955, 16781571}, {119956, 16781827}, {119957, 16782083}, {119958, 16782339},
+       {119959, 16782595}, {119960, 16782851}, {119961, 16783107}, {119962, 16783363},
+       {119963, 16783619}, {119964, 16777219}, {119965, 2}, {119966, 16777731},
+       {119967, 16777987}, {119968, 2}, {119970, 16778755}, {119971, 2},
+       {119973, 16779523}, {119974, 16779779}, {119975, 2}, {119977, 16780547},
+       {119978, 16780803}, {119979, 16781059}, {119980, 16781315}, {119981, 2},
+       {119982, 16781827}, {119983, 16782083}, {119984, 16782339}, {119985, 16782595},
+       {119986, 16782851}, {119987, 16783107}, {119988, 16783363}, {119989, 16783619},
+       {119990, 16777219}, {119991, 16777475}, {119992, 16777731}, {119993, 16777987},
+       {119994, 2}, {119995, 16778499}, {119996, 2}, {119997, 16779011},
+       {119998, 16779267}, {119999, 16779523}, {120000, 16779779}, {120001, 16780035},
+       {120002, 16780291}, {120003, 16780547}, {120004, 2}, {120005, 16781059},
+       {120006, 16781315}, {120007, 16781571}, {120008, 16781827}, {120009, 16782083},
+       {120010, 16782339}, {120011, 16782595}, {120012, 16782851}, {120013, 16783107},
+       {120014, 16783363}, {120015, 16783619}, {120016, 16777219}, {120017, 16777475},
+       {120018, 16777731}, {120019, 16777987}, {120020, 16778243}, {120021, 16778499},
+       {120022, 16778755}, {120023, 16779011}, {120024, 16779267}, {120025, 16779523},
+       {120026, 16779779}, {120027, 16780035}, {120028, 16780291}, {120029, 16780547},
+       {120030, 16780803}, {120031, 16781059}, {120032, 16781315}, {120033, 16781571},
+       {120034, 16781827}, {120035, 16782083}, {120036, 16782339}, {120037, 16782595},
+       {120038, 16782851}, {120039, 16783107}, {120040, 16783363}, {120041, 16783619},
+       {120042, 16777219}, {120043, 16777475}, {120044, 16777731}, {120045, 16777987},
+       {120046, 16778243}, {120047, 16778499}, {120048, 16778755}, {120049, 16779011},
+       {120050, 16779267}, {120051, 16779523}, {120052, 16779779}, {120053, 16780035},
+       {120054, 16780291}, {120055, 16780547}, {120056, 16780803}, {120057, 16781059},
+       {120058, 16781315}, {120059, 16781571}, {120060, 16781827}, {120061, 16782083},
+       {120062, 16782339}, {120063, 16782595}, {120064, 16782851}, {120065, 16783107},
+       {120066, 16783363}, {120067, 16783619}, {120068, 16777219}, {120069, 16777475},
+       {120070, 2}, {120071, 16777987}, {120072, 16778243}, {120073, 16778499},
+       {120074, 16778755}, {120075, 2}, {120077, 16779523}, {120078, 16779779},
+       {120079, 16780035}, {120080, 16780291}, {120081, 16780547}, {120082, 16780803},
+       {120083, 16781059}, {120084, 16781315}, {120085, 2}, {120086, 16781827},
+       {120087, 16782083}, {120088, 16782339}, {120089, 16782595}, {120090, 16782851},
+       {120091, 16783107}, {120092, 16783363}, {120093, 2}, {120094, 16777219},
+       {120095, 16777475}, {120096, 16777731}, {120097, 16777987}, {120098, 16778243},
+       {120099, 16778499}, {120100, 16778755}, {120101, 16779011}, {120102, 16779267},
+       {120103, 16779523}, {120104, 16779779}, {120105, 16780035}, {120106, 16780291},
+       {120107, 16780547}, {120108, 16780803}, {120109, 16781059}, {120110, 16781315},
+       {120111, 16781571}, {120112, 16781827}, {120113, 16782083}, {120114, 16782339},
+       {120115, 16782595}, {120116, 16782851}, {120117, 16783107}, {120118, 16783363},
+       {120119, 16783619}, {120120, 16777219}, {120121, 16777475}, {120122, 2},
+       {120123, 16777987}, {120124, 16778243}, {120125, 16778499}, {120126, 16778755},
+       {120127, 2}, {120128, 16779267}, {120129, 16779523}, {120130, 16779779},
+       {120131, 16780035}, {120132, 16780291}, {120133, 2}, {120134, 16780803},
+       {120135, 2}, {120138, 16781827}, {120139, 16782083}, {120140, 16782339},
+       {120141, 16782595}, {120142, 16782851}, {120143, 16783107}, {120144, 16783363},
+       {120145, 2}, {120146, 16777219}, {120147, 16777475}, {120148, 16777731},
+       {120149, 16777987}, {120150, 16778243}, {120151, 16778499}, {120152, 16778755},
+       {120153, 16779011}, {120154, 16779267}, {120155, 16779523}, {120156, 16779779},
+       {120157, 16780035}, {120158, 16780291}, {120159, 16780547}, {120160, 16780803},
+       {120161, 16781059}, {120162, 16781315}, {120163, 16781571}, {120164, 16781827},
+       {120165, 16782083}, {120166, 16782339}, {120167, 16782595}, {120168, 16782851},
+       {120169, 16783107}, {120170, 16783363}, {120171, 16783619}, {120172, 16777219},
+       {120173, 16777475}, {120174, 16777731}, {120175, 16777987}, {120176, 16778243},
+       {120177, 16778499}, {120178, 16778755}, {120179, 16779011}, {120180, 16779267},
+       {120181, 16779523}, {120182, 16779779}, {120183, 16780035}, {120184, 16780291},
+       {120185, 16780547}, {120186, 16780803}, {120187, 16781059}, {120188, 16781315},
+       {120189, 16781571}, {120190, 16781827}, {120191, 16782083}, {120192, 16782339},
+       {120193, 16782595}, {120194, 16782851}, {120195, 16783107}, {120196, 16783363},
+       {120197, 16783619}, {120198, 16777219}, {120199, 16777475}, {120200, 16777731},
+       {120201, 16777987}, {120202, 16778243}, {120203, 16778499}, {120204, 16778755},
+       {120205, 16779011}, {120206, 16779267}, {120207, 16779523}, {120208, 16779779},
+       {120209, 16780035}, {120210, 16780291}, {120211, 16780547}, {120212, 16780803},
+       {120213, 16781059}, {120214, 16781315}, {120215, 16781571}, {120216, 16781827},
+       {120217, 16782083}, {120218, 16782339}, {120219, 16782595}, {120220, 16782851},
+       {120221, 16783107}, {120222, 16783363}, {120223, 16783619}, {120224, 16777219},
+       {120225, 16777475}, {120226, 16777731}, {120227, 16777987}, {120228, 16778243},
+       {120229, 16778499}, {120230, 16778755}, {120231, 16779011}, {120232, 16779267},
+       {120233, 16779523}, {120234, 16779779}, {120235, 16780035}, {120236, 16780291},
+       {120237, 16780547}, {120238, 16780803}, {120239, 16781059}, {120240, 16781315},
+       {120241, 16781571}, {120242, 16781827}, {120243, 16782083}, {120244, 16782339},
+       {120245, 16782595}, {120246, 16782851}, {120247, 16783107}, {120248, 16783363},
+       {120249, 16783619}, {120250, 16777219}, {120251, 16777475}, {120252, 16777731},
+       {120253, 16777987}, {120254, 16778243}, {120255, 16778499}, {120256, 16778755},
+       {120257, 16779011}, {120258, 16779267}, {120259, 16779523}, {120260, 16779779},
+       {120261, 16780035}, {120262, 16780291}, {120263, 16780547}, {120264, 16780803},
+       {120265, 16781059}, {120266, 16781315}, {120267, 16781571}, {120268, 16781827},
+       {120269, 16782083}, {120270, 16782339}, {120271, 16782595}, {120272, 16782851},
+       {120273, 16783107}, {120274, 16783363}, {120275, 16783619}, {120276, 16777219},
+       {120277, 16777475}, {120278, 16777731}, {120279, 16777987}, {120280, 16778243},
+       {120281, 16778499}, {120282, 16778755}, {120283, 16779011}, {120284, 16779267},
+       {120285, 16779523}, {120286, 16779779}, {120287, 16780035}, {120288, 16780291},
+       {120289, 16780547}, {120290, 16780803}, {120291, 16781059}, {120292, 16781315},
+       {120293, 16781571}, {120294, 16781827}, {120295, 16782083}, {120296, 16782339},
+       {120297, 16782595}, {120298, 16782851}, {120299, 16783107}, {120300, 16783363},
+       {120301, 16783619}, {120302, 16777219}, {120303, 16777475}, {120304, 16777731},
+       {120305, 16777987}, {120306, 16778243}, {120307, 16778499}, {120308, 16778755},
+       {120309, 16779011}, {120310, 16779267}, {120311, 16779523}, {120312, 16779779},
+       {120313, 16780035}, {120314, 16780291}, {120315, 16780547}, {120316, 16780803},
+       {120317, 16781059}, {120318, 16781315}, {120319, 16781571}, {120320, 16781827},
+       {120321, 16782083}, {120322, 16782339}, {120323, 16782595}, {120324, 16782851},
+       {120325, 16783107}, {120326, 16783363}, {120327, 16783619}, {120328, 16777219},
+       {120329, 16777475}, {120330, 16777731}, {120331, 16777987}, {120332, 16778243},
+       {120333, 16778499}, {120334, 16778755}, {120335, 16779011}, {120336, 16779267},
+       {120337, 16779523}, {120338, 16779779}, {120339, 16780035}, {120340, 16780291},
+       {120341, 16780547}, {120342, 16780803}, {120343, 16781059}, {120344, 16781315},
+       {120345, 16781571}, {120346, 16781827}, {120347, 16782083}, {120348, 16782339},
+       {120349, 16782595}, {120350, 16782851}, {120351, 16783107}, {120352, 16783363},
+       {120353, 16783619}, {120354, 16777219}, {120355, 16777475}, {120356, 16777731},
+       {120357, 16777987}, {120358, 16778243}, {120359, 16778499}, {120360, 16778755},
+       {120361, 16779011}, {120362, 16779267}, {120363, 16779523}, {120364, 16779779},
+       {120365, 16780035}, {120366, 16780291}, {120367, 16780547}, {120368, 16780803},
+       {120369, 16781059}, {120370, 16781315}, {120371, 16781571}, {120372, 16781827},
+       {120373, 16782083}, {120374, 16782339}, {120375, 16782595}, {120376, 16782851},
+       {120377, 16783107}, {120378, 16783363}, {120379, 16783619}, {120380, 16777219},
+       {120381, 16777475}, {120382, 16777731}, {120383, 16777987}, {120384, 16778243},
+       {120385, 16778499}, {120386, 16778755}, {120387, 16779011}, {120388, 16779267},
+       {120389, 16779523}, {120390, 16779779}, {120391, 16780035}, {120392, 16780291},
+       {120393, 16780547}, {120394, 16780803}, {120395, 16781059}, {120396, 16781315},
+       {120397, 16781571}, {120398, 16781827}, {120399, 16782083}, {120400, 16782339},
+       {120401, 16782595}, {120402, 16782851}, {120403, 16783107}, {120404, 16783363},
+       {120405, 16783619}, {120406, 16777219}, {120407, 16777475}, {120408, 16777731},
+       {120409, 16777987}, {120410, 16778243}, {120411, 16778499}, {120412, 16778755},
+       {120413, 16779011}, {120414, 16779267}, {120415, 16779523}, {120416, 16779779},
+       {120417, 16780035}, {120418, 16780291}, {120419, 16780547}, {120420, 16780803},
+       {120421, 16781059}, {120422, 16781315}, {120423, 16781571}, {120424, 16781827},
+       {120425, 16782083}, {120426, 16782339}, {120427, 16782595}, {120428, 16782851},
+       {120429, 16783107}, {120430, 16783363}, {120431, 16783619}, {120432, 16777219},
+       {120433, 16777475}, {120434, 16777731}, {120435, 16777987}, {120436, 16778243},
+       {120437, 16778499}, {120438, 16778755}, {120439, 16779011}, {120440, 16779267},
+       {120441, 16779523}, {120442, 16779779}, {120443, 16780035}, {120444, 16780291},
+       {120445, 16780547}, {120446, 16780803}, {120447, 16781059}, {120448, 16781315},
+       {120449, 16781571}, {120450, 16781827}, {120451, 16782083}, {120452, 16782339},
+       {120453, 16782595}, {120454, 16782851}, {120455, 16783107}, {120456, 16783363},
+       {120457, 16783619}, {120458, 16777219}, {120459, 16777475}, {120460, 16777731},
+       {120461, 16777987}, {120462, 16778243}, {120463, 16778499}, {120464, 16778755},
+       {120465, 16779011}, {120466, 16779267}, {120467, 16779523}, {120468, 16779779},
+       {120469, 16780035}, {120470, 16780291}, {120471, 16780547}, {120472, 16780803},
+       {120473, 16781059}, {120474, 16781315}, {120475, 16781571}, {120476, 16781827},
+       {120477, 16782083}, {120478, 16782339}, {120479, 16782595}, {120480, 16782851},
+       {120481, 16783107}, {120482, 16783363}, {120483, 16783619}, {120484, 17944835},
+       {120485, 17945091}, {120486, 2}, {120488, 16851715}, {120489, 16851971},
+       {120490, 16852227}, {120491, 16852483}, {120492, 16852739}, {120493, 16852995},
+       {120494, 16853251}, {120495, 16853507}, {120496, 16846851}, {120497, 16853763},
+       {120498, 16854019}, {120499, 16786179}, {120500, 16854275}, {120501, 16854531},
+       {120502, 16854787}, {120503, 16855043}, {120504, 16855299}, {120505, 16853507},
+       {120506, 16855555}, {120507, 16855811}, {120508, 16856067}, {120509, 16856323},
+       {120510, 16856579}, {120511, 16856835}, {120512, 16857091}, {120513, 17945347},
+       {120514, 16851715}, {120515, 16851971}, {120516, 16852227}, {120517, 16852483},
+       {120518, 16852739}, {120519, 16852995}, {120520, 16853251}, {120521, 16853507},
+       {120522, 16846851}, {120523, 16853763}, {120524, 16854019}, {120525, 16786179},
+       {120526, 16854275}, {120527, 16854531}, {120528, 16854787}, {120529, 16855043},
+       {120530, 16855299}, {120531, 16855555}, {120533, 16855811}, {120534, 16856067},
+       {120535, 16856323}, {120536, 16856579}, {120537, 16856835}, {120538, 16857091},
+       {120539, 17945603}, {120540, 16852739}, {120541, 16853507}, {120542, 16853763},
+       {120543, 16856323}, {120544, 16855299}, {120545, 16855043}, {120546, 16851715},
+       {120547, 16851971}, {120548, 16852227}, {120549, 16852483}, {120550, 16852739},
+       {120551, 16852995}, {120552, 16853251}, {120553, 16853507}, {120554, 16846851},
+       {120555, 16853763}, {120556, 16854019}, {120557, 16786179}, {120558, 16854275},
+       {120559, 16854531}, {120560, 16854787}, {120561, 16855043}, {120562, 16855299},
+       {120563, 16853507}, {120564, 16855555}, {120565, 16855811}, {120566, 16856067},
+       {120567, 16856323}, {120568, 16856579}, {120569, 16856835}, {120570, 16857091},
+       {120571, 17945347}, {120572, 16851715}, {120573, 16851971}, {120574, 16852227},
+       {120575, 16852483}, {120576, 16852739}, {120577, 16852995}, {120578, 16853251},
+       {120579, 16853507}, {120580, 16846851}, {120581, 16853763}, {120582, 16854019},
+       {120583, 16786179}, {120584, 16854275}, {120585, 16854531}, {120586, 16854787},
+       {120587, 16855043}, {120588, 16855299}, {120589, 16855555}, {120591, 16855811},
+       {120592, 16856067}, {120593, 16856323}, {120594, 16856579}, {120595, 16856835},
+       {120596, 16857091}, {120597, 17945603}, {120598, 16852739}, {120599, 16853507},
+       {120600, 16853763}, {120601, 16856323}, {120602, 16855299}, {120603, 16855043},
+       {120604, 16851715}, {120605, 16851971}, {120606, 16852227}, {120607, 16852483},
+       {120608, 16852739}, {120609, 16852995}, {120610, 16853251}, {120611, 16853507},
+       {120612, 16846851}, {120613, 16853763}, {120614, 16854019}, {120615, 16786179},
+       {120616, 16854275}, {120617, 16854531}, {120618, 16854787}, {120619, 16855043},
+       {120620, 16855299}, {120621, 16853507}, {120622, 16855555}, {120623, 16855811},
+       {120624, 16856067}, {120625, 16856323}, {120626, 16856579}, {120627, 16856835},
+       {120628, 16857091}, {120629, 17945347}, {120630, 16851715}, {120631, 16851971},
+       {120632, 16852227}, {120633, 16852483}, {120634, 16852739}, {120635, 16852995},
+       {120636, 16853251}, {120637, 16853507}, {120638, 16846851}, {120639, 16853763},
+       {120640, 16854019}, {120641, 16786179}, {120642, 16854275}, {120643, 16854531},
+       {120644, 16854787}, {120645, 16855043}, {120646, 16855299}, {120647, 16855555},
+       {120649, 16855811}, {120650, 16856067}, {120651, 16856323}, {120652, 16856579},
+       {120653, 16856835}, {120654, 16857091}, {120655, 17945603}, {120656, 16852739},
+       {120657, 16853507}, {120658, 16853763}, {120659, 16856323}, {120660, 16855299},
+       {120661, 16855043}, {120662, 16851715}, {120663, 16851971}, {120664, 16852227},
+       {120665, 16852483}, {120666, 16852739}, {120667, 16852995}, {120668, 16853251},
+       {120669, 16853507}, {120670, 16846851}, {120671, 16853763}, {120672, 16854019},
+       {120673, 16786179}, {120674, 16854275}, {120675, 16854531}, {120676, 16854787},
+       {120677, 16855043}, {120678, 16855299}, {120679, 16853507}, {120680, 16855555},
+       {120681, 16855811}, {120682, 16856067}, {120683, 16856323}, {120684, 16856579},
+       {120685, 16856835}, {120686, 16857091}, {120687, 17945347}, {120688, 16851715},
+       {120689, 16851971}, {120690, 16852227}, {120691, 16852483}, {120692, 16852739},
+       {120693, 16852995}, {120694, 16853251}, {120695, 16853507}, {120696, 16846851},
+       {120697, 16853763}, {120698, 16854019}, {120699, 16786179}, {120700, 16854275},
+       {120701, 16854531}, {120702, 16854787}, {120703, 16855043}, {120704, 16855299},
+       {120705, 16855555}, {120707, 16855811}, {120708, 16856067}, {120709, 16856323},
+       {120710, 16856579}, {120711, 16856835}, {120712, 16857091}, {120713, 17945603},
+       {120714, 16852739}, {120715, 16853507}, {120716, 16853763}, {120717, 16856323},
+       {120718, 16855299}, {120719, 16855043}, {120720, 16851715}, {120721, 16851971},
+       {120722, 16852227}, {120723, 16852483}, {120724, 16852739}, {120725, 16852995},
+       {120726, 16853251}, {120727, 16853507}, {120728, 16846851}, {120729, 16853763},
+       {120730, 16854019}, {120731, 16786179}, {120732, 16854275}, {120733, 16854531},
+       {120734, 16854787}, {120735, 16855043}, {120736, 16855299}, {120737, 16853507},
+       {120738, 16855555}, {120739, 16855811}, {120740, 16856067}, {120741, 16856323},
+       {120742, 16856579}, {120743, 16856835}, {120744, 16857091}, {120745, 17945347},
+       {120746, 16851715}, {120747, 16851971}, {120748, 16852227}, {120749, 16852483},
+       {120750, 16852739}, {120751, 16852995}, {120752, 16853251}, {120753, 16853507},
+       {120754, 16846851}, {120755, 16853763}, {120756, 16854019}, {120757, 16786179},
+       {120758, 16854275}, {120759, 16854531}, {120760, 16854787}, {120761, 16855043},
+       {120762, 16855299}, {120763, 16855555}, {120765, 16855811}, {120766, 16856067},
+       {120767, 16856323}, {120768, 16856579}, {120769, 16856835}, {120770, 16857091},
+       {120771, 17945603}, {120772, 16852739}, {120773, 16853507}, {120774, 16853763},
+       {120775, 16856323}, {120776, 16855299}, {120777, 16855043}, {120778, 16858627},
+       {120780, 2}, {120782, 17035523}, {120783, 16786947}, {120784, 16785155},
+       {120785, 16785411}, {120786, 16787715}, {120787, 17035779}, {120788, 17036035},
+       {120789, 17036291}, {120790, 17036547}, {120791, 17036803}, {120792, 17035523},
+       {120793, 16786947}, {120794, 16785155}, {120795, 16785411}, {120796, 16787715},
+       {120797, 17035779}, {120798, 17036035}, {120799, 17036291}, {120800, 17036547},
+       {120801, 17036803}, {120802, 17035523}, {120803, 16786947}, {120804, 16785155},
+       {120805, 16785411}, {120806, 16787715}, {120807, 17035779}, {120808, 17036035},
+       {120809, 17036291}, {120810, 17036547}, {120811, 17036803}, {120812, 17035523},
+       {120813, 16786947}, {120814, 16785155}, {120815, 16785411}, {120816, 16787715},
+       {120817, 17035779}, {120818, 17036035}, {120819, 17036291}, {120820, 17036547},
+       {120821, 17036803}, {120822, 17035523}, {120823, 16786947}, {120824, 16785155},
+       {120825, 16785411}, {120826, 16787715}, {120827, 17035779}, {120828, 17036035},
+       {120829, 17036291}, {120830, 17036547}, {120831, 17036803}, {120832, 1},
+       {121484, 2}, {121499, 1}, {121504, 2}, {121505, 1},
+       {121520, 2}, {122624, 1}, {122655, 2}, {122661, 1},
+       {122667, 2}, {122880, 1}, {122887, 2}, {122888, 1},
+       {122905, 2}, {122907, 1}, {122914, 2}, {122915, 1},
+       {122917, 2}, {122918, 1}, {122923, 2}, {122928, 16866563},
+       {122929, 16866819}, {122930, 16867075}, {122931, 16867331}, {122932, 16867587},
+       {122933, 16867843}, {122934, 16868099}, {122935, 16868355}, {122936, 16868611},
+       {122937, 16869123}, {122938, 16869379}, {122939, 16869635}, {122940, 16870147},
+       {122941, 16870403}, {122942, 16870659}, {122943, 16870915}, {122944, 16871171},
+       {122945, 16871427}, {122946, 16871683}, {122947, 16871939}, {122948, 16872195},
+       {122949, 16872451}, {122950, 16872707}, {122951, 16873475}, {122952, 16873987},
+       {122953, 16874243}, {122954, 17495299}, {122955, 16888835}, {122956, 16864003},
+       {122957, 16864515}, {122958, 16890883}, {122959, 16883715}, {122960, 17945859},
+       {122961, 16866563}, {122962, 16866819}, {122963, 16867075}, {122964, 16867331},
+       {122965, 16867587}, {122966, 16867843}, {122967, 16868099}, {122968, 16868355},
+       {122969, 16868611}, {122970, 16869123}, {122971, 16869379}, {122972, 16870147},
+       {122973, 16870403}, {122974, 16870915}, {122975, 16871427}, {122976, 16871683},
+       {122977, 16871939}, {122978, 16872195}, {122979, 16872451}, {122980, 16872707},
+       {122981, 16873219}, {122982, 16873475}, {122983, 16879875}, {122984, 16864003},
+       {122985, 16863747}, {122986, 16866307}, {122987, 16883203}, {122988, 17490435},
+       {122989, 16883971}, {122990, 2}, {123023, 1}, {123024, 2},
+       {123136, 1}, {123181, 2}, {123184, 1}, {123198, 2},
+       {123200, 1}, {123210, 2}, {123214, 1}, {123216, 2},
+       {123536, 1}, {123567, 2}, {123584, 1}, {123642, 2},
+       {123647, 1}, {123648, 2}, {124112, 1}, {124154, 2},
+       {124896, 1}, {124903, 2}, {124904, 1}, {124908, 2},
+       {124909, 1}, {124911, 2}, {124912, 1}, {124927, 2},
+       {124928, 1}, {125125, 2}, {125127, 1}, {125143, 2},
+       {125184, 17946115}, {125185, 17946371}, {125186, 17946627}, {125187, 17946883},
+       {125188, 17947139}, {125189, 17947395}, {125190, 17947651}, {125191, 17947907},
+       {125192, 17948163}, {125193, 17948419}, {125194, 17948675}, {125195, 17948931},
+       {125196, 17949187}, {125197, 17949443}, {125198, 17949699}, {125199, 17949955},
+       {125200, 17950211}, {125201, 17950467}, {125202, 17950723}, {125203, 17950979},
+       {125204, 17951235}, {125205, 17951491}, {125206, 17951747}, {125207, 17952003},
+       {125208, 17952259}, {125209, 17952515}, {125210, 17952771}, {125211, 17953027},
+       {125212, 17953283}, {125213, 17953539}, {125214, 17953795}, {125215, 17954051},
+       {125216, 17954307}, {125217, 17954563}, {125218, 1}, {125260, 2},
+       {125264, 1}, {125274, 2}, {125278, 1}, {125280, 2},
+       {126065, 1}, {126133, 2}, {126209, 1}, {126270, 2},
+       {126464, 16910339}, {126465, 17683715}, {126466, 17681923}, {126467, 17834499},
+       {126468, 2}, {126469, 16910851}, {126470, 17731587}, {126471, 17682435},
+       {126472, 17700099}, {126473, 16911875}, {126474, 17708803}, {126475, 17711107},
+       {126476, 17682947}, {126477, 17718019}, {126478, 17694979}, {126479, 17701635},
+       {126480, 17703683}, {126481, 17697027}, {126482, 17706755}, {126483, 17725187},
+       {126484, 17745155}, {126485, 17686787}, {126486, 17689859}, {126487, 17684995},
+       {126488, 17724675}, {126489, 17698051}, {126490, 17701123}, {126491, 17702659},
+       {126492, 17954819}, {126493, 17673475}, {126494, 17955075}, {126495, 17955331},
+       {126496, 2}, {126497, 17683715}, {126498, 17681923}, {126499, 2},
+       {126500, 17721091}, {126501, 2}, {126503, 17682435}, {126504, 2},
+       {126505, 16911875}, {126506, 17708803}, {126507, 17711107}, {126508, 17682947},
+       {126509, 17718019}, {126510, 17694979}, {126511, 17701635}, {126512, 17703683},
+       {126513, 17697027}, {126514, 17706755}, {126515, 2}, {126516, 17745155},
+       {126517, 17686787}, {126518, 17689859}, {126519, 17684995}, {126520, 2},
+       {126521, 17698051}, {126522, 2}, {126523, 17702659}, {126524, 2},
+       {126530, 17681923}, {126531, 2}, {126535, 17682435}, {126536, 2},
+       {126537, 16911875}, {126538, 2}, {126539, 17711107}, {126540, 2},
+       {126541, 17718019}, {126542, 17694979}, {126543, 17701635}, {126544, 2},
+       {126545, 17697027}, {126546, 17706755}, {126547, 2}, {126548, 17745155},
+       {126549, 2}, {126551, 17684995}, {126552, 2}, {126553, 17698051},
+       {126554, 2}, {126555, 17702659}, {126556, 2}, {126557, 17673475},
+       {126558, 2}, {126559, 17955331}, {126560, 2}, {126561, 17683715},
+       {126562, 17681923}, {126563, 2}, {126564, 17721091}, {126565, 2},
+       {126567, 17682435}, {126568, 17700099}, {126569, 16911875}, {126570, 17708803},
+       {126571, 2}, {126572, 17682947}, {126573, 17718019}, {126574, 17694979},
+       {126575, 17701635}, {126576, 17703683}, {126577, 17697027}, {126578, 17706755},
+       {126579, 2}, {126580, 17745155}, {126581, 17686787}, {126582, 17689859},
+       {126583, 17684995}, {126584, 2}, {126585, 17698051}, {126586, 17701123},
+       {126587, 17702659}, {126588, 17954819}, {126589, 2}, {126590, 17955075},
+       {126591, 2}, {126592, 16910339}, {126593, 17683715}, {126594, 17681923},
+       {126595, 17834499}, {126596, 17721091}, {126597, 16910851}, {126598, 17731587},
+       {126599, 17682435}, {126600, 17700099}, {126601, 16911875}, {126602, 2},
+       {126603, 17711107}, {126604, 17682947}, {126605, 17718019}, {126606, 17694979},
+       {126607, 17701635}, {126608, 17703683}, {126609, 17697027}, {126610, 17706755},
+       {126611, 17725187}, {126612, 17745155}, {126613, 17686787}, {126614, 17689859},
+       {126615, 17684995}, {126616, 17724675}, {126617, 17698051}, {126618, 17701123},
+       {126619, 17702659}, {126620, 2}, {126625, 17683715}, {126626, 17681923},
+       {126627, 17834499}, {126628, 2}, {126629, 16910851}, {126630, 17731587},
+       {126631, 17682435}, {126632, 17700099}, {126633, 16911875}, {126634, 2},
+       {126635, 17711107}, {126636, 17682947}, {126637, 17718019}, {126638, 17694979},
+       {126639, 17701635}, {126640, 17703683}, {126641, 17697027}, {126642, 17706755},
+       {126643, 17725187}, {126644, 17745155}, {126645, 17686787}, {126646, 17689859},
+       {126647, 17684995}, {126648, 17724675}, {126649, 17698051}, {126650, 17701123},
+       {126651, 17702659}, {126652, 2}, {126704, 1}, {126706, 2},
+       {126976, 1}, {127020, 2}, {127024, 1}, {127124, 2},
+       {127136, 1}, {127151, 2}, {127153, 1}, {127168, 2},
+       {127169, 1}, {127184, 2}, {127185, 1}, {127222, 2},
+       {127233, 34732803}, {127234, 34733315}, {127235, 34733827}, {127236, 34734339},
+       {127237, 34734851}, {127238, 34735363}, {127239, 34735875}, {127240, 34736387},
+       {127241, 34736899}, {127242, 34737411}, {127243, 1}, {127248, 50644995},
+       {127249, 50645763}, {127250, 50646531}, {127251, 50647299}, {127252, 50648067},
+       {127253, 50648835}, {127254, 50649603}, {127255, 50650371}, {127256, 50651139},
+       {127257, 50651907}, {127258, 50652675}, {127259, 50653443}, {127260, 50654211},
+       {127261, 50654979}, {127262, 50655747}, {127263, 50656515}, {127264, 50657283},
+       {127265, 50658051}, {127266, 50658819}, {127267, 50659587}, {127268, 50660355},
+       {127269, 50661123}, {127270, 50661891}, {127271, 50662659}, {127272, 50663427},
+       {127273, 50664195}, {127274, 51515139}, {127275, 16777731}, {127276, 16781571},
+       {127277, 33554947}, {127278, 34738691}, {127279, 1}, {127280, 16777219},
+       {127281, 16777475}, {127282, 16777731}, {127283, 16777987}, {127284, 16778243},
+       {127285, 16778499}, {127286, 16778755}, {127287, 16779011}, {127288, 16779267},
+       {127289, 16779523}, {127290, 16779779}, {127291, 16780035}, {127292, 16780291},
+       {127293, 16780547}, {127294, 16780803}, {127295, 16781059}, {127296, 16781315},
+       {127297, 16781571}, {127298, 16781827}, {127299, 16782083}, {127300, 16782339},
+       {127301, 16782595}, {127302, 16782851}, {127303, 16783107}, {127304, 16783363},
+       {127305, 16783619}, {127306, 34739203}, {127307, 34226691}, {127308, 34739715},
+       {127309, 33752579}, {127310, 51517443}, {127311, 34740995}, {127312, 1},
+       {127338, 34209539}, {127339, 34189571}, {127340, 34741507}, {127341, 1},
+       {127376, 34742019}, {127377, 1}, {127406, 2}, {127462, 1},
+       {127488, 34742531}, {127489, 34743043}, {127490, 17307907}, {127491, 2},
+       {127504, 17157891}, {127505, 17966339}, {127506, 17966595}, {127507, 17351683},
+       {127508, 17143299}, {127509, 17966851}, {127510, 17967107}, {127511, 17225475},
+       {127512, 17967363}, {127513, 17967619}, {127514, 17967875}, {127515, 17584643},
+       {127516, 17968131}, {127517, 17968387}, {127518, 17968643}, {127519, 17968899},
+       {127520, 17969155}, {127521, 17969411}, {127522, 17167107}, {127523, 17969667},
+       {127524, 17969923}, {127525, 17970179}, {127526, 17970435}, {127527, 17970691},
+       {127528, 17970947}, {127529, 17141763}, {127530, 17223427}, {127531, 17971203},
+       {127532, 17288707}, {127533, 17224195}, {127534, 17288963}, {127535, 17971459},
+       {127536, 17181443}, {127537, 17971715}, {127538, 17971971}, {127539, 17972227},
+       {127540, 17972483}, {127541, 17972739}, {127542, 17264387}, {127543, 17160451},
+       {127544, 17972995}, {127545, 17973251}, {127546, 17973507}, {127547, 17973763},
+       {127548, 2}, {127552, 51528451}, {127553, 51529219}, {127554, 51529987},
+       {127555, 51530755}, {127556, 51531523}, {127557, 51532291}, {127558, 51533059},
+       {127559, 51533827}, {127560, 51534595}, {127561, 2}, {127568, 17980931},
+       {127569, 17981187}, {127570, 2}, {127584, 1}, {127590, 2},
+       {127744, 1}, {128728, 2}, {128732, 1}, {128749, 2},
+       {128752, 1}, {128765, 2}, {128768, 1}, {128887, 2},
+       {128891, 1}, {128986, 2}, {128992, 1}, {129004, 2},
+       {129008, 1}, {129009, 2}, {129024, 1}, {129036, 2},
+       {129040, 1}, {129096, 2}, {129104, 1}, {129114, 2},
+       {129120, 1}, {129160, 2}, {129168, 1}, {129198, 2},
+       {129200, 1}, {129202, 2}, {129280, 1}, {129620, 2},
+       {129632, 1}, {129646, 2}, {129648, 1}, {129661, 2},
+       {129664, 1}, {129673, 2}, {129680, 1}, {129726, 2},
+       {129727, 1}, {129734, 2}, {129742, 1}, {129756, 2},
+       {129760, 1}, {129769, 2}, {129776, 1}, {129785, 2},
+       {129792, 1}, {129939, 2}, {129940, 1}, {129995, 2},
+       {130032, 17035523}, {130033, 16786947}, {130034, 16785155}, {130035, 16785411},
+       {130036, 16787715}, {130037, 17035779}, {130038, 17036035}, {130039, 17036291},
+       {130040, 17036547}, {130041, 17036803}, {130042, 2}, {131072, 1},
+       {173792, 2}, {173824, 1}, {177978, 2}, {177984, 1},
+       {178206, 2}, {178208, 1}, {183970, 2}, {183984, 1},
+       {191457, 2}, {194560, 17981443}, {194561, 17981699}, {194562, 17981955},
+       {194563, 17982211}, {194564, 17982467}, {194565, 17608451}, {194566, 17982723},
+       {194567, 17982979}, {194568, 17983235}, {194569, 17983491}, {194570, 17608707},
+       {194571, 17983747}, {194572, 17984003}, {194573, 17984259}, {194574, 17608963},
+       {194575, 17984515}, {194576, 17984771}, {194577, 17985027}, {194578, 17985283},
+       {194579, 17985539}, {194580, 17985795}, {194581, 17968643}, {194582, 17986051},
+       {194583, 17986307}, {194584, 17986563}, {194585, 17986819}, {194586, 17987075},
+       {194587, 17623043}, {194588, 17987331}, {194589, 17145859}, {194590, 17987587},
+       {194591, 17987843}, {194592, 17988099}, {194593, 17988355}, {194594, 17973251},
+       {194595, 17988611}, {194596, 17988867}, {194597, 17624323}, {194598, 17609219},
+       {194599, 17609475}, {194600, 17624579}, {194601, 17989123}, {194602, 17989379},
+       {194603, 17562883}, {194604, 17989635}, {194605, 17609731}, {194606, 17989891},
+       {194607, 17990147}, {194608, 17990403}, {194609, 17990659}, {194612, 17990915},
+       {194613, 17991171}, {194614, 17991427}, {194615, 17991683}, {194616, 17991939},
+       {194617, 17992195}, {194618, 17992451}, {194619, 17992707}, {194620, 17992963},
+       {194621, 17993219}, {194622, 17993475}, {194623, 17993731}, {194624, 17993987},
+       {194625, 17994243}, {194626, 17994499}, {194627, 17994755}, {194628, 17995011},
+       {194629, 17995267}, {194631, 17625091}, {194632, 17995523}, {194633, 17995779},
+       {194634, 17996035}, {194635, 17996291}, {194636, 17610243}, {194637, 17996547},
+       {194638, 17996803}, {194639, 17997059}, {194640, 17600003}, {194641, 17997315},
+       {194642, 17997571}, {194643, 17997827}, {194644, 17998083}, {194645, 17998339},
+       {194646, 17998595}, {194647, 17998851}, {194648, 17999107}, {194649, 17999363},
+       {194650, 17999619}, {194651, 17999875}, {194652, 18000131}, {194653, 17966851},
+       {194654, 18000387}, {194655, 18000643}, {194656, 18000899}, {194657, 18001155},
+       {194658, 18001411}, {194659, 18001667}, {194660, 18001923}, {194661, 18002179},
+       {194662, 18002435}, {194663, 18002691}, {194664, 2}, {194665, 18002947},
+       {194666, 18003203}, {194668, 18003459}, {194669, 18003715}, {194670, 18003971},
+       {194671, 17561859}, {194672, 18004227}, {194673, 18004483}, {194674, 18004739},
+       {194675, 18004995}, {194676, 2}, {194677, 17152515}, {194678, 18005251},
+       {194679, 18005507}, {194680, 17153027}, {194681, 18005763}, {194682, 18006019},
+       {194683, 18006275}, {194684, 18006531}, {194685, 18006787}, {194686, 18007043},
+       {194687, 18007299}, {194688, 18007555}, {194689, 18007811}, {194690, 18008067},
+       {194691, 18008323}, {194692, 18008579}, {194693, 18008835}, {194694, 18009091},
+       {194695, 18009347}, {194696, 18009603}, {194697, 18009859}, {194698, 18010115},
+       {194699, 18010371}, {194700, 18010627}, {194701, 18010883}, {194702, 17548547},
+       {194703, 18011139}, {194704, 17155587}, {194705, 18011395}, {194707, 18011651},
+       {194708, 18011907}, {194710, 18012163}, {194711, 18012419}, {194712, 18012675},
+       {194713, 18012931}, {194714, 18013187}, {194715, 18013443}, {194716, 18013699},
+       {194717, 18013955}, {194718, 18014211}, {194719, 18014467}, {194720, 18014723},
+       {194721, 18014979}, {194722, 18015235}, {194723, 17611523}, {194724, 18015491},
+       {194725, 18015747}, {194726, 18016003}, {194727, 18016259}, {194728, 17628163},
+       {194729, 18016259}, {194730, 18016515}, {194731, 17612035}, {194732, 18016771},
+       {194733, 18017027}, {194734, 18017283}, {194735, 18017539}, {194736, 17612291},
+       {194737, 17541635}, {194738, 17414915}, {194739, 18017795}, {194740, 18018051},
+       {194741, 18018307}, {194742, 18018563}, {194743, 18018819}, {194744, 18019075},
+       {194745, 18019331}, {194746, 18019587}, {194747, 18019843}, {194748, 18020099},
+       {194749, 18020355}, {194750, 18020611}, {194751, 18020867}, {194752, 18021123},
+       {194753, 18021379}, {194754, 18021635}, {194755, 18021891}, {194756, 18022147},
+       {194757, 18022403}, {194758, 18022659}, {194759, 18022915}, {194760, 17612547},
+       {194761, 18023171}, {194762, 18023427}, {194763, 18023683}, {194764, 18023939},
+       {194765, 18024195}, {194766, 18024451}, {194767, 17613059}, {194768, 18024707},
+       {194769, 18024963}, {194770, 18025219}, {194771, 18025475}, {194772, 18025731},
+       {194773, 18025987}, {194774, 18026243}, {194775, 18026499}, {194776, 17548803},
+       {194777, 17630211}, {194778, 18026755}, {194779, 18027011}, {194780, 18027267},
+       {194781, 18027523}, {194782, 18027779}, {194783, 18028035}, {194784, 18028291},
+       {194785, 18028547}, {194786, 17613315}, {194787, 18028803}, {194788, 18029059},
+       {194789, 18029315}, {194790, 18029571}, {194791, 17640963}, {194792, 18029827},
+       {194793, 18030083}, {194794, 18030339}, {194795, 18030595}, {194796, 18030851},
+       {194797, 18031107}, {194798, 18031363}, {194799, 18031619}, {194800, 18031875},
+       {194801, 18032131}, {194802, 18032387}, {194803, 18032643}, {194804, 18032899},
+       {194805, 17566211}, {194806, 18033155}, {194807, 18033411}, {194808, 18033667},
+       {194809, 18033923}, {194810, 18034179}, {194811, 18034435}, {194812, 18034691},
+       {194813, 18034947}, {194814, 18035203}, {194815, 18035459}, {194816, 18035715},
+       {194817, 17613571}, {194818, 17587203}, {194819, 18035971}, {194820, 18036227},
+       {194821, 18036483}, {194822, 18036739}, {194823, 18036995}, {194824, 18037251},
+       {194825, 18037507}, {194826, 18037763}, {194827, 17630979}, {194828, 18038019},
+       {194829, 18038275}, {194830, 18038531}, {194831, 18038787}, {194832, 18039043},
+       {194833, 18039299}, {194834, 18039555}, {194835, 18039811}, {194836, 17631235},
+       {194837, 18040067}, {194838, 18040323}, {194839, 18040579}, {194840, 18040835},
+       {194841, 18041091}, {194842, 18041347}, {194843, 18041603}, {194844, 18041859},
+       {194845, 18042115}, {194846, 18042371}, {194847, 2}, {194848, 18042627},
+       {194849, 17631747}, {194850, 18042883}, {194851, 18043139}, {194852, 18043395},
+       {194853, 18043651}, {194854, 18043907}, {194855, 18044163}, {194856, 18044419},
+       {194857, 18044675}, {194858, 18044931}, {194859, 18045187}, {194860, 18045443},
+       {194862, 18045699}, {194863, 18045955}, {194864, 17632259}, {194865, 18046211},
+       {194866, 18046467}, {194867, 18046723}, {194868, 18046979}, {194869, 18047235},
+       {194870, 18047491}, {194871, 18047747}, {194872, 17562627}, {194873, 18048003},
+       {194874, 18048259}, {194875, 18048515}, {194876, 18048771}, {194877, 18049027},
+       {194878, 18049283}, {194879, 18049539}, {194880, 17633795}, {194881, 18049795},
+       {194882, 18050051}, {194883, 18050307}, {194884, 18050563}, {194885, 18050819},
+       {194886, 18051075}, {194888, 17634051}, {194889, 17641475}, {194890, 18051331},
+       {194891, 18051587}, {194892, 18051843}, {194893, 18052099}, {194894, 18052355},
+       {194895, 17553155}, {194896, 17634563}, {194897, 18052611}, {194898, 18052867},
+       {194899, 17616131}, {194900, 18053123}, {194901, 18053379}, {194902, 17605123},
+       {194903, 18053635}, {194904, 18053891}, {194905, 17616899}, {194906, 18054147},
+       {194907, 18054403}, {194908, 18054659}, {194909, 18054915}, {194911, 2},
+       {194912, 18055171}, {194913, 18055427}, {194914, 18055683}, {194915, 18055939},
+       {194916, 18056195}, {194917, 18056451}, {194918, 18056707}, {194919, 18056963},
+       {194920, 18057219}, {194921, 18057475}, {194922, 18057731}, {194923, 18057987},
+       {194924, 18058243}, {194925, 18058499}, {194926, 18058755}, {194927, 18059011},
+       {194928, 18059267}, {194929, 18059523}, {194930, 18059779}, {194931, 18060035},
+       {194932, 18060291}, {194933, 18060547}, {194934, 18060803}, {194935, 18061059},
+       {194936, 18061315}, {194937, 18061571}, {194938, 17618435}, {194939, 18061827},
+       {194940, 18062083}, {194941, 18062339}, {194942, 18062595}, {194943, 18062851},
+       {194944, 18063107}, {194945, 18063363}, {194946, 18063619}, {194947, 18063875},
+       {194948, 18064131}, {194949, 18064387}, {194950, 18064643}, {194951, 18064899},
+       {194952, 18065155}, {194953, 18065411}, {194954, 18065667}, {194955, 18011651},
+       {194956, 18065923}, {194957, 18066179}, {194958, 18066435}, {194959, 18066691},
+       {194960, 18066947}, {194961, 18067203}, {194962, 18067459}, {194963, 18067715},
+       {194964, 18067971}, {194965, 18068227}, {194966, 18068483}, {194967, 18068739},
+       {194968, 17566979}, {194969, 18068995}, {194970, 18069251}, {194971, 18069507},
+       {194972, 18069763}, {194973, 18070019}, {194974, 18070275}, {194975, 17619203},
+       {194976, 18070531}, {194977, 18070787}, {194978, 18071043}, {194979, 18071299},
+       {194980, 18071555}, {194981, 18071811}, {194982, 18072067}, {194983, 18072323},
+       {194984, 18072579}, {194985, 18072835}, {194986, 18073091}, {194987, 18073347},
+       {194988, 18073603}, {194989, 18073859}, {194990, 18074115}, {194991, 18074371},
+       {194992, 18074627}, {194993, 18074883}, {194994, 18075139}, {194995, 18075395},
+       {194996, 17551875}, {194997, 18075651}, {194998, 18075907}, {194999, 18076163},
+       {195000, 18076419}, {195001, 18076675}, {195002, 18076931}, {195003, 17636355},
+       {195004, 18077187}, {195005, 18077443}, {195006, 18077699}, {195007, 2},
+       {195008, 18077955}, {195009, 18078211}, {195010, 18078467}, {195011, 18078723},
+       {195012, 17178627}, {195013, 18078979}, {195014, 18079235}, {195015, 18079491},
+       {195016, 18079747}, {195017, 18080003}, {195018, 18080259}, {195019, 18080515},
+       {195020, 18080771}, {195021, 18081027}, {195022, 18081283}, {195023, 18081539},
+       {195024, 17637635}, {195025, 17637891}, {195026, 17180419}, {195027, 18081795},
+       {195028, 18082051}, {195029, 18082307}, {195030, 18082563}, {195031, 18082819},
+       {195032, 18083075}, {195033, 18083331}, {195034, 18083587}, {195035, 18083843},
+       {195036, 18084099}, {195037, 18084355}, {195038, 18084611}, {195039, 17638147},
+       {195040, 18084867}, {195041, 18085123}, {195042, 18085379}, {195043, 18085635},
+       {195044, 18085891}, {195045, 18086147}, {195046, 18086403}, {195047, 18086659},
+       {195048, 18086915}, {195049, 18087171}, {195050, 18087427}, {195051, 18087683},
+       {195052, 18087939}, {195053, 18088195}, {195054, 18088451}, {195055, 18088707},
+       {195056, 18088963}, {195057, 18089219}, {195058, 18089475}, {195059, 18089731},
+       {195060, 18089987}, {195061, 18090243}, {195062, 18090499}, {195063, 18090755},
+       {195064, 18091011}, {195065, 18091267}, {195066, 18091523}, {195067, 18091779},
+       {195068, 18092035}, {195069, 18092291}, {195070, 17639683}, {195072, 18092547},
+       {195073, 18092803}, {195074, 18093059}, {195075, 18093315}, {195076, 18093571},
+       {195077, 18093827}, {195078, 18094083}, {195079, 18094339}, {195080, 18094595},
+       {195081, 18094851}, {195082, 17639939}, {195083, 18095107}, {195084, 18095363},
+       {195085, 18095619}, {195086, 18095875}, {195087, 18096131}, {195088, 18096387},
+       {195089, 18096643}, {195090, 18096899}, {195091, 18097155}, {195092, 18097411},
+       {195093, 17192707}, {195094, 18097667}, {195095, 17193731}, {195096, 18097923},
+       {195097, 18098179}, {195098, 18098435}, {195099, 18098691}, {195100, 17195011},
+       {195101, 18098947}, {195102, 2}, {196608, 1}, {201547, 2},
+       {201552, 1}, {205744, 2}, {917760, 0}, {918000, 2}
+};
+
+
+} // namespace ada::idna
+#endif // ADA_IDNA_TABLES_H
+
+/* end file src/mapping_tables.cpp */
+
+namespace ada::idna {
+
+// This can be greatly accelerated. For now we just use a simply
+// binary search. In practice, you should *not* do that.
+uint32_t find_range_index(uint32_t key) {
+  ////////////////
+  // This could be implemented with std::lower_bound, but we roll our own
+  // because we want to allow further optimizations in the future.
+  ////////////////
+  uint32_t len = std::size(table);
+  uint32_t low = 0;
+  uint32_t high = len - 1;
+  while (low <= high) {
+    uint32_t middle_index = (low + high) >> 1;  // cannot overflow
+    uint32_t middle_value = table[middle_index][0];
+    if (middle_value < key) {
+      low = middle_index + 1;
+    } else if (middle_value > key) {
+      high = middle_index - 1;
+    } else {
+      return middle_index;  // perfect match
+    }
+  }
+  return low == 0 ? 0 : low - 1;
+}
+
+bool ascii_has_upper_case(char* input, size_t length) {
+  auto broadcast = [](uint8_t v) -> uint64_t {
+    return 0x101010101010101ull * v;
+  };
+  uint64_t broadcast_80 = broadcast(0x80);
+  uint64_t broadcast_Ap = broadcast(128 - 'A');
+  uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1);
+  size_t i = 0;
+
+  uint64_t runner{0};
+
+  for (; i + 7 < length; i += 8) {
+    uint64_t word{};
+    memcpy(&word, input + i, sizeof(word));
+    runner |= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80);
+  }
+  if (i < length) {
+    uint64_t word{};
+    memcpy(&word, input + i, length - i);
+    runner |= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80);
+  }
+  return runner != 0;
+}
+
+void ascii_map(char* input, size_t length) {
+  auto broadcast = [](uint8_t v) -> uint64_t {
+    return 0x101010101010101ull * v;
+  };
+  uint64_t broadcast_80 = broadcast(0x80);
+  uint64_t broadcast_Ap = broadcast(128 - 'A');
+  uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1);
+  size_t i = 0;
+
+  for (; i + 7 < length; i += 8) {
+    uint64_t word{};
+    memcpy(&word, input + i, sizeof(word));
+    word ^=
+        (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
+    memcpy(input + i, &word, sizeof(word));
+  }
+  if (i < length) {
+    uint64_t word{};
+    memcpy(&word, input + i, length - i);
+    word ^=
+        (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
+    memcpy(input + i, &word, length - i);
+  }
+}
+
+// Map the characters according to IDNA, returning the empty string on error.
+std::u32string map(std::u32string_view input) {
+  //  [Map](https://www.unicode.org/reports/tr46/#ProcessingStepMap).
+  //  For each code point in the domain_name string, look up the status
+  //  value in Section 5, [IDNA Mapping
+  //  Table](https://www.unicode.org/reports/tr46/#IDNA_Mapping_Table),
+  //  and take the following actions:
+  //    * disallowed: Leave the code point unchanged in the string, and
+  //    record that there was an error.
+  //    * ignored: Remove the code point from the string. This is
+  //    equivalent to mapping the code point to an empty string.
+  //    * mapped: Replace the code point in the string by the value for
+  //    the mapping in Section 5, [IDNA Mapping
+  //    Table](https://www.unicode.org/reports/tr46/#IDNA_Mapping_Table).
+  //    * valid: Leave the code point unchanged in the string.
+  static std::u32string error = U"";
+  std::u32string answer;
+  answer.reserve(input.size());
+  for (char32_t x : input) {
+    size_t index = find_range_index(x);
+    uint32_t descriptor = table[index][1];
+    uint8_t code = uint8_t(descriptor);
+    switch (code) {
+      case 0:
+        break;  // nothing to do, ignored
+      case 1:
+        answer.push_back(x);  // valid, we just copy it to output
+        break;
+      case 2:
+        return error;  // disallowed
+      // case 3 :
+      default:
+        // We have a mapping
+        {
+          size_t char_count = (descriptor >> 24);
+          uint16_t char_index = uint16_t(descriptor >> 8);
+          for (size_t idx = char_index; idx < char_index + char_count; idx++) {
+            answer.push_back(mappings[idx]);
+          }
+        }
+    }
+  }
+  return answer;
+}
+}  // namespace ada::idna
+/* end file src/mapping.cpp */
+/* begin file src/normalization.cpp */
+/* begin file src/normalization_tables.cpp */
+// IDNA  15.0.0
+
+// clang-format off
+#ifndef ADA_IDNA_NORMALIZATION_TABLES_H
+#define ADA_IDNA_NORMALIZATION_TABLES_H
+#include <cstdint>
+
+/**
+ * Unicode Standard Annex #15
+ *
+ * UNICODE NORMALIZATION FORMS
+ * https://www.unicode.org/reports/tr15/
+ *
+ * See https://github.com/uni-algo/uni-algo/blob/c612968c5ed3ace39bde4c894c24286c5f2c7fe2/include/uni_algo/impl/data/data_norm.h for reference.
+ */
+
+namespace ada::idna {
+
+const uint8_t decomposition_index[4352] = {
+    0,  1,  2,  3,  4,  5,  6,  7,  7,  8,  9,  10, 11, 12, 13, 14, 15, 7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  16, 7,  17, 18, 19, 20, 21, 22, 23, 24, 7,
+    7,  7,  7,  7,  25, 7,  26, 27, 28, 29, 30, 31, 32, 33, 7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  34, 35, 7,  7,  7,
+    36, 7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  37, 38, 39, 40, 41, 42, 43, 7,  7,  7,  7,  7,  7,  7,  44, 7,  7,
+    7,  7,  7,  7,  7,  7,  45, 46, 7,  47, 48, 49, 7,  7,  7,  50, 7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  51, 7,  52, 53, 54, 55, 56, 7,  7,  7,
+    7,  7,  7,  7,  7,  57, 7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  58,
+    59, 7,  60, 61, 62, 7,  7,  7,  7,  7,  7,  7,  7,  63, 7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    64, 65, 66, 7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,  7,
+    7};
+
+const uint16_t decomposition_block[67][257] = {
+    {4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+     4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   5,   8,   8,   8,   8,
+     8,   8,   8,   9,   16,  17,  20,  20,  20,  20,  21,  28,  28,  29,  33,
+     37,  45,  48,  48,  49,  57,  61,  64,  65,  77,  89,  100, 100, 108, 116,
+     124, 132, 140, 148, 148, 156, 164, 172, 180, 188, 196, 204, 212, 220, 220,
+     228, 236, 244, 252, 260, 268, 268, 268, 276, 284, 292, 300, 308, 308, 308,
+     316, 324, 332, 340, 348, 356, 356, 364, 372, 380, 388, 396, 404, 412, 420,
+     428, 428, 436, 444, 452, 460, 468, 476, 476, 476, 484, 492, 500, 508, 516,
+     516, 524},
+    {524,  532,  540,  548,  556,  564,  572,  580,  588,  596,  604,  612,
+     620,  628,  636,  644,  652,  652,  652,  660,  668,  676,  684,  692,
+     700,  708,  716,  724,  732,  740,  748,  756,  764,  772,  780,  788,
+     796,  804,  812,  812,  812,  820,  828,  836,  844,  852,  860,  868,
+     876,  884,  885,  893,  900,  908,  916,  924,  932,  932,  940,  948,
+     956,  964,  972,  981,  989,  996,  996,  996,  1004, 1012, 1020, 1028,
+     1036, 1045, 1052, 1052, 1052, 1060, 1068, 1076, 1084, 1092, 1100, 1100,
+     1100, 1108, 1116, 1124, 1132, 1140, 1148, 1156, 1164, 1172, 1180, 1188,
+     1196, 1204, 1212, 1220, 1228, 1236, 1244, 1244, 1244, 1252, 1260, 1268,
+     1276, 1284, 1292, 1300, 1308, 1316, 1324, 1332, 1340, 1348, 1356, 1364,
+     1372, 1380, 1388, 1396, 1404, 1412, 1420, 1429, 1432, 1432, 1432, 1432,
+     1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432,
+     1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432, 1432,
+     1432, 1432, 1432, 1432, 1432, 1440, 1448, 1448, 1448, 1448, 1448, 1448,
+     1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1456, 1464, 1464, 1464,
+     1464, 1464, 1464, 1464, 1464, 1464, 1464, 1464, 1464, 1464, 1464, 1464,
+     1464, 1464, 1464, 1464, 1465, 1477, 1489, 1501, 1509, 1517, 1525, 1533,
+     1541, 1548, 1556, 1564, 1572, 1580, 1588, 1596, 1604, 1612, 1624, 1636,
+     1648, 1660, 1672, 1684, 1696, 1708, 1708, 1720, 1732, 1744, 1756, 1764,
+     1772, 1772, 1772, 1780, 1788, 1796, 1804, 1812, 1820, 1832, 1844, 1852,
+     1860, 1869, 1877, 1885, 1892, 1900, 1908, 1908, 1908, 1916, 1924, 1936,
+     1948, 1956, 1964, 1972, 1980},
+    {1980, 1988, 1996, 2004, 2012, 2020, 2028, 2036, 2044, 2052, 2060, 2068,
+     2076, 2084, 2092, 2100, 2108, 2116, 2124, 2132, 2140, 2148, 2156, 2164,
+     2172, 2180, 2188, 2196, 2204, 2204, 2204, 2212, 2220, 2220, 2220, 2220,
+     2220, 2220, 2220, 2228, 2236, 2244, 2252, 2264, 2276, 2288, 2300, 2308,
+     2316, 2328, 2340, 2348, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356,
+     2356, 2356, 2356, 2356, 2356, 2356, 2356, 2356, 2357, 2361, 2365, 2369,
+     2373, 2377, 2381, 2385, 2389, 2392, 2392, 2392, 2392, 2392, 2392, 2392,
+     2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392,
+     2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392, 2392,
+     2393, 2401, 2409, 2417, 2425, 2433, 2440, 2440, 2441, 2445, 2449, 2453,
+     2457, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460},
+    {2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460, 2460,
+     2460, 2460, 2460, 2460, 2460, 2464, 2468, 2468, 2472, 2480, 2480, 2480,
+     2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480,
+     2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480,
+     2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480,
+     2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2480, 2484, 2484, 2484,
+     2484, 2484, 2485, 2492, 2492, 2492, 2492, 2496, 2496, 2496, 2496, 2496,
+     2497, 2506, 2512, 2520, 2524, 2532, 2540, 2548, 2548, 2556, 2556, 2564,
+     2572, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584,
+     2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584, 2584,
+     2584, 2584, 2584, 2592, 2600, 2608, 2616, 2624, 2632, 2644, 2644, 2644,
+     2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644,
+     2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2644, 2652,
+     2660, 2668, 2676, 2684, 2685, 2689, 2693, 2698, 2706, 2713, 2717, 2720,
+     2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720,
+     2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720, 2720,
+     2721, 2725, 2729, 2732, 2733, 2737, 2740, 2740, 2740, 2741, 2744, 2744,
+     2744, 2744, 2744, 2744, 2744},
+    {2744, 2752, 2760, 2760, 2768, 2768, 2768, 2768, 2776, 2776, 2776, 2776,
+     2776, 2784, 2792, 2800, 2800, 2800, 2800, 2800, 2800, 2800, 2800, 2800,
+     2800, 2800, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808,
+     2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808,
+     2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2808, 2816, 2816,
+     2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816,
+     2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2816, 2824, 2832, 2832,
+     2840, 2840, 2840, 2840, 2848, 2848, 2848, 2848, 2848, 2856, 2864, 2872,
+     2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872,
+     2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2872, 2880,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888, 2888,
+     2888, 2888, 2896, 2904, 2904, 2904, 2904, 2904, 2904, 2904, 2904, 2904,
+     2904, 2904, 2904, 2904, 2904, 2912, 2920, 2928, 2936, 2936, 2936, 2944,
+     2952, 2952, 2952, 2960, 2968, 2976, 2984, 2992, 3000, 3000, 3000, 3008,
+     3016, 3024, 3032, 3040, 3048, 3048, 3048, 3056, 3064, 3072, 3080, 3088,
+     3096, 3104, 3112, 3120, 3128, 3136, 3144, 3144, 3144, 3152, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160},
+    {3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160, 3160,
+     3160, 3160, 3160, 3161, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168},
+    {3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168,
+     3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3168, 3176,
+     3184, 3192, 3200, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208,
+     3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3208, 3209, 3217, 3225,
+     3233, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240, 3240,
+     3240, 3248, 3248, 3256, 3256, 3256, 3256, 3256, 3256, 3256, 3256, 3256,
+     3256, 3256, 3256, 3256, 3256, 3256, 3256, 3256, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264, 3264,
+     3264, 3264, 3264, 3264, 3264, 3264, 3272, 3272, 3272, 3272, 3272, 3272,
+     3272, 3272, 3280, 3280, 3280, 3288, 3288, 3288, 3288, 3288, 3288, 3288,
+     3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288,
+     3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288, 3288,
+     3288, 3288, 3288, 3288, 3288, 3296, 3304, 3312, 3320, 3328, 3336, 3344,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352, 3352,
+     3360, 3368, 3368, 3368, 3368, 3368, 3368, 3368, 3368, 3368, 3368, 3368,
+     3368, 3368, 3368, 3368, 3368, 3376, 3384, 3384, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392},
+    {3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392, 3392,
+     3392, 3392, 3392, 3392, 3400, 3400, 3400, 3408, 3408, 3408, 3408, 3408,
+     3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408,
+     3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408, 3408,
+     3408, 3408, 3408, 3408, 3408, 3408, 3416, 3424, 3432, 3432, 3432, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440},
+    {3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440, 3440,
+     3440, 3448, 3448, 3448, 3456, 3464, 3464, 3464, 3464, 3464, 3464, 3464,
+     3464, 3464, 3464, 3464, 3464, 3464, 3464, 3464, 3464, 3472, 3480, 3480,
+     3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480,
+     3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480,
+     3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480,
+     3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480, 3480,
+     3480, 3480, 3480, 3480, 3480, 3488, 3488, 3488, 3488, 3488, 3488, 3488,
+     3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488,
+     3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488,
+     3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488,
+     3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3488, 3496,
+     3504, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512},
+    {3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512, 3512,
+     3512, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520, 3520,
+     3520, 3528, 3528, 3528, 3528, 3528, 3528, 3528, 3536, 3544, 3544, 3552,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564},
+    {3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564, 3564,
+     3564, 3564, 3564, 3572, 3580, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588, 3588,
+     3588, 3588, 3588, 3596, 3596, 3604, 3616, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624},
+    {3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624, 3624,
+     3624, 3624, 3624, 3625, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632,
+     3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3632, 3633,
+     3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640,
+     3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640,
+     3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640, 3640,
+     3640, 3640, 3640, 3640, 3641, 3649, 3656, 3656, 3656, 3656, 3656, 3656,
+     3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656,
+     3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656,
+     3656, 3656, 3656, 3656, 3656},
+    {3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656, 3656,
+     3657, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660,
+     3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660,
+     3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660,
+     3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660,
+     3660, 3660, 3660, 3660, 3660, 3660, 3660, 3660, 3668, 3668, 3668, 3668,
+     3668, 3668, 3668, 3668, 3668, 3668, 3676, 3676, 3676, 3676, 3676, 3684,
+     3684, 3684, 3684, 3684, 3692, 3692, 3692, 3692, 3692, 3700, 3700, 3700,
+     3700, 3700, 3700, 3700, 3700, 3700, 3700, 3700, 3700, 3700, 3708, 3708,
+     3708, 3708, 3708, 3708, 3708, 3708, 3708, 3708, 3716, 3716, 3724, 3733,
+     3744, 3753, 3764, 3764, 3764, 3764, 3764, 3764, 3764, 3764, 3772, 3772,
+     3772, 3772, 3772, 3772, 3772, 3772, 3772, 3772, 3772, 3772, 3772, 3772,
+     3772, 3772, 3772, 3772, 3780, 3780, 3780, 3780, 3780, 3780, 3780, 3780,
+     3780, 3780, 3788, 3788, 3788, 3788, 3788, 3796, 3796, 3796, 3796, 3796,
+     3804, 3804, 3804, 3804, 3804, 3812, 3812, 3812, 3812, 3812, 3812, 3812,
+     3812, 3812, 3812, 3812, 3812, 3812, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820},
+    {3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820, 3820,
+     3820, 3820, 3820, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828, 3828,
+     3829, 3832, 3832, 3832, 3832},
+    {3832, 3832, 3832, 3832, 3832, 3832, 3832, 3840, 3840, 3848, 3848, 3856,
+     3856, 3864, 3864, 3872, 3872, 3872, 3872, 3880, 3880, 3880, 3880, 3880,
+     3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880,
+     3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880,
+     3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880, 3880,
+     3888, 3888, 3896, 3896, 3896, 3904, 3912, 3912, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920},
+    {3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920,
+     3920, 3920, 3920, 3920, 3920, 3920, 3920, 3920, 3921, 3925, 3929, 3932,
+     3933, 3937, 3941, 3945, 3949, 3953, 3957, 3961, 3965, 3969, 3973, 3976,
+     3977, 3981, 3985, 3989, 3993, 3997, 4001, 4005, 4009, 4013, 4017, 4021,
+     4025, 4029, 4033, 4037, 4041, 4045, 4048, 4049, 4053, 4057, 4061, 4065,
+     4069, 4073, 4077, 4081, 4085, 4089, 4093, 4097, 4101, 4105, 4109, 4113,
+     4117, 4121, 4125, 4129, 4133, 4137, 4141, 4145, 4149, 4153, 4157, 4160,
+     4160, 4160, 4160, 4160, 4160, 4160, 4160, 4160, 4160, 4160, 4160, 4160,
+     4161, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164,
+     4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164,
+     4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4164, 4165,
+     4169, 4173, 4177, 4181, 4185, 4189, 4193, 4197, 4201, 4205, 4209, 4213,
+     4217, 4221, 4225, 4229, 4233, 4237, 4241, 4245, 4249, 4253, 4257, 4261,
+     4265, 4269, 4273, 4277, 4281, 4285, 4289, 4293, 4297, 4301, 4305, 4309,
+     4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312,
+     4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312,
+     4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312,
+     4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312,
+     4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312, 4312,
+     4312, 4312, 4312, 4312, 4312},
+    {4312, 4320, 4328, 4336, 4344, 4352, 4360, 4368, 4376, 4388, 4400, 4408,
+     4416, 4424, 4432, 4440, 4448, 4456, 4464, 4472, 4480, 4492, 4504, 4516,
+     4528, 4536, 4544, 4552, 4560, 4572, 4584, 4592, 4600, 4608, 4616, 4624,
+     4632, 4640, 4648, 4656, 4664, 4672, 4680, 4688, 4696, 4704, 4712, 4724,
+     4736, 4744, 4752, 4760, 4768, 4776, 4784, 4792, 4800, 4812, 4824, 4832,
+     4840, 4848, 4856, 4864, 4872, 4880, 4888, 4896, 4904, 4912, 4920, 4928,
+     4936, 4944, 4952, 4960, 4968, 4980, 4992, 5004, 5016, 5028, 5040, 5052,
+     5064, 5072, 5080, 5088, 5096, 5104, 5112, 5120, 5128, 5140, 5152, 5160,
+     5168, 5176, 5184, 5192, 5200, 5212, 5224, 5236, 5248, 5260, 5272, 5280,
+     5288, 5296, 5304, 5312, 5320, 5328, 5336, 5344, 5352, 5360, 5368, 5376,
+     5384, 5396, 5408, 5420, 5432, 5440, 5448, 5456, 5464, 5472, 5480, 5488,
+     5496, 5504, 5512, 5520, 5528, 5536, 5544, 5552, 5560, 5568, 5576, 5584,
+     5592, 5600, 5608, 5616, 5624, 5632, 5640, 5648, 5656, 5664, 5673, 5682,
+     5688, 5688, 5688, 5688, 5688, 5696, 5704, 5712, 5720, 5732, 5744, 5756,
+     5768, 5780, 5792, 5804, 5816, 5828, 5840, 5852, 5864, 5876, 5888, 5900,
+     5912, 5924, 5936, 5948, 5960, 5968, 5976, 5984, 5992, 6000, 6008, 6020,
+     6032, 6044, 6056, 6068, 6080, 6092, 6104, 6116, 6128, 6136, 6144, 6152,
+     6160, 6168, 6176, 6184, 6192, 6204, 6216, 6228, 6240, 6252, 6264, 6276,
+     6288, 6300, 6312, 6324, 6336, 6348, 6360, 6372, 6384, 6396, 6408, 6420,
+     6432, 6440, 6448, 6456, 6464, 6476, 6488, 6500, 6512, 6524, 6536, 6548,
+     6560, 6572, 6584, 6592, 6600, 6608, 6616, 6624, 6632, 6640, 6648, 6648,
+     6648, 6648, 6648, 6648, 6648},
+    {6648, 6656, 6664, 6676, 6688, 6700, 6712, 6724, 6736, 6744, 6752, 6764,
+     6776, 6788, 6800, 6812, 6824, 6832, 6840, 6852, 6864, 6876, 6888, 6888,
+     6888, 6896, 6904, 6916, 6928, 6940, 6952, 6952, 6952, 6960, 6968, 6980,
+     6992, 7004, 7016, 7028, 7040, 7048, 7056, 7068, 7080, 7092, 7104, 7116,
+     7128, 7136, 7144, 7156, 7168, 7180, 7192, 7204, 7216, 7224, 7232, 7244,
+     7256, 7268, 7280, 7292, 7304, 7312, 7320, 7332, 7344, 7356, 7368, 7368,
+     7368, 7376, 7384, 7396, 7408, 7420, 7432, 7432, 7432, 7440, 7448, 7460,
+     7472, 7484, 7496, 7508, 7520, 7520, 7528, 7528, 7540, 7540, 7552, 7552,
+     7564, 7572, 7580, 7592, 7604, 7616, 7628, 7640, 7652, 7660, 7668, 7680,
+     7692, 7704, 7716, 7728, 7740, 7748, 7756, 7764, 7772, 7780, 7788, 7796,
+     7804, 7812, 7820, 7828, 7836, 7844, 7852, 7852, 7852, 7864, 7876, 7892,
+     7908, 7924, 7940, 7956, 7972, 7984, 7996, 8012, 8028, 8044, 8060, 8076,
+     8092, 8104, 8116, 8132, 8148, 8164, 8180, 8196, 8212, 8224, 8236, 8252,
+     8268, 8284, 8300, 8316, 8332, 8344, 8356, 8372, 8388, 8404, 8420, 8436,
+     8452, 8464, 8476, 8492, 8508, 8524, 8540, 8556, 8572, 8580, 8588, 8600,
+     8608, 8620, 8620, 8628, 8640, 8648, 8656, 8664, 8672, 8681, 8688, 8693,
+     8701, 8710, 8716, 8728, 8736, 8748, 8748, 8756, 8768, 8776, 8784, 8792,
+     8800, 8810, 8818, 8826, 8832, 8840, 8848, 8860, 8872, 8872, 8872, 8880,
+     8892, 8900, 8908, 8916, 8924, 8926, 8934, 8942, 8948, 8956, 8964, 8976,
+     8988, 8996, 9004, 9012, 9024, 9032, 9040, 9048, 9056, 9066, 9074, 9080,
+     9084, 9084, 9084, 9096, 9104, 9116, 9116, 9124, 9136, 9144, 9152, 9160,
+     9168, 9178, 9181, 9188, 9190},
+    {9190, 9194, 9197, 9201, 9205, 9209, 9213, 9217, 9221, 9225, 9229, 9232,
+     9232, 9232, 9232, 9232, 9232, 9233, 9236, 9236, 9236, 9236, 9236, 9237,
+     9244, 9244, 9244, 9244, 9244, 9244, 9244, 9244, 9244, 9244, 9244, 9244,
+     9245, 9249, 9257, 9268, 9268, 9268, 9268, 9268, 9268, 9268, 9268, 9269,
+     9272, 9272, 9272, 9273, 9281, 9292, 9293, 9301, 9312, 9312, 9312, 9312,
+     9313, 9320, 9321, 9328, 9328, 9328, 9328, 9328, 9328, 9328, 9328, 9329,
+     9337, 9345, 9352, 9352, 9352, 9352, 9352, 9352, 9352, 9352, 9352, 9352,
+     9352, 9352, 9352, 9353, 9368, 9368, 9368, 9368, 9368, 9368, 9368, 9369,
+     9372, 9372, 9372, 9372, 9372, 9372, 9372, 9372, 9372, 9372, 9372, 9372,
+     9372, 9372, 9372, 9372, 9373, 9377, 9380, 9380, 9381, 9385, 9389, 9393,
+     9397, 9401, 9405, 9409, 9413, 9417, 9421, 9425, 9429, 9433, 9437, 9441,
+     9445, 9449, 9453, 9457, 9461, 9465, 9469, 9473, 9477, 9481, 9485, 9488,
+     9489, 9493, 9497, 9501, 9505, 9509, 9513, 9517, 9521, 9525, 9529, 9533,
+     9537, 9540, 9540, 9540, 9540, 9540, 9540, 9540, 9540, 9540, 9540, 9540,
+     9541, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548, 9548,
+     9548, 9548, 9548, 9548, 9549},
+    {9549,  9561,  9573,  9577,  9584,  9585,  9597,  9609,  9612,  9613,
+     9621,  9625,  9629,  9633,  9637,  9641,  9645,  9649,  9653,  9657,
+     9660,  9661,  9665,  9672,  9672,  9673,  9677,  9681,  9685,  9689,
+     9692,  9692,  9693,  9701,  9713,  9720,  9721,  9724,  9724,  9728,
+     9729,  9732,  9732,  9736,  9745,  9749,  9752,  9753,  9757,  9761,
+     9764,  9765,  9769,  9773,  9777,  9781,  9785,  9789,  9792,  9793,
+     9805,  9809,  9813,  9817,  9821,  9824,  9824,  9824,  9824,  9825,
+     9829,  9833,  9837,  9841,  9844,  9844,  9844,  9844,  9844,  9844,
+     9845,  9857,  9869,  9885,  9897,  9909,  9921,  9933,  9945,  9957,
+     9969,  9981,  9993,  10005, 10017, 10029, 10037, 10041, 10049, 10061,
+     10069, 10073, 10081, 10093, 10109, 10117, 10121, 10129, 10141, 10145,
+     10149, 10153, 10157, 10161, 10169, 10181, 10189, 10193, 10201, 10213,
+     10229, 10237, 10241, 10249, 10261, 10265, 10269, 10273, 10276, 10276,
+     10276, 10276, 10276, 10276, 10276, 10276, 10276, 10277, 10288, 10288,
+     10288, 10288, 10288, 10288, 10288, 10288, 10288, 10288, 10288, 10288,
+     10288, 10288, 10288, 10288, 10288, 10296, 10304, 10304, 10304, 10304,
+     10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304, 10304,
+     10304, 10304, 10304, 10304, 10304, 10312, 10312, 10312, 10312, 10312,
+     10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312,
+     10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312, 10312,
+     10312, 10312, 10312, 10312, 10312, 10312, 10320, 10328, 10336, 10336,
+     10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336,
+     10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336,
+     10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336,
+     10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336, 10336,
+     10336, 10336, 10336, 10336, 10336, 10336, 10336},
+    {10336, 10336, 10336, 10336, 10336, 10344, 10344, 10344, 10344, 10344,
+     10352, 10352, 10352, 10360, 10360, 10360, 10360, 10360, 10360, 10360,
+     10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360, 10360,
+     10360, 10360, 10360, 10360, 10360, 10360, 10360, 10368, 10368, 10376,
+     10376, 10376, 10376, 10376, 10377, 10385, 10396, 10397, 10405, 10416,
+     10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416, 10416,
+     10416, 10416, 10416, 10416, 10416, 10416, 10424, 10424, 10424, 10432,
+     10432, 10432, 10440, 10440, 10448, 10448, 10448, 10448, 10448, 10448,
+     10448, 10448, 10448, 10448, 10448, 10448, 10448, 10448, 10448, 10448,
+     10448, 10448, 10448, 10448, 10448, 10448, 10448, 10456, 10456, 10464,
+     10464, 10464, 10464, 10464, 10464, 10464, 10464, 10464, 10464, 10464,
+     10472, 10480, 10488, 10496, 10504, 10504, 10504, 10512, 10520, 10520,
+     10520, 10528, 10536, 10536, 10536, 10536, 10536, 10536, 10536, 10544,
+     10552, 10552, 10552, 10560, 10568, 10568, 10568, 10576, 10584, 10584,
+     10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584,
+     10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584,
+     10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584, 10584,
+     10584, 10584, 10584, 10592, 10600, 10608, 10616, 10616, 10616, 10616,
+     10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616,
+     10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616,
+     10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616,
+     10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616, 10616,
+     10616, 10616, 10616, 10616, 10616, 10624, 10632, 10640, 10648, 10648,
+     10648, 10648, 10648, 10648, 10648, 10656, 10664, 10672, 10680, 10680,
+     10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680,
+     10680, 10680, 10680, 10680, 10680, 10680, 10680},
+    {10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680,
+     10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680,
+     10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680,
+     10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680, 10680,
+     10680, 10680, 10684, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688},
+    {10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688, 10688,
+     10688, 10688, 10688, 10688, 10688, 10688, 10689, 10693, 10697, 10701,
+     10705, 10709, 10713, 10717, 10721, 10725, 10733, 10741, 10749, 10757,
+     10765, 10773, 10781, 10789, 10797, 10805, 10813, 10825, 10837, 10849,
+     10861, 10873, 10885, 10897, 10909, 10921, 10937, 10953, 10969, 10985,
+     11001, 11017, 11033, 11049, 11065, 11081, 11097, 11105, 11113, 11121,
+     11129, 11137, 11145, 11153, 11161, 11169, 11181, 11193, 11205, 11217,
+     11229, 11241, 11253, 11265, 11277, 11289, 11301, 11313, 11325, 11337,
+     11349, 11361, 11373, 11385, 11397, 11409, 11421, 11433, 11445, 11457,
+     11469, 11481, 11493, 11505, 11517, 11529, 11541, 11553, 11565, 11577,
+     11589, 11601, 11613, 11617, 11621, 11625, 11629, 11633, 11637, 11641,
+     11645, 11649, 11653, 11657, 11661, 11665, 11669, 11673, 11677, 11681,
+     11685, 11689, 11693, 11697, 11701, 11705, 11709, 11713, 11717, 11721,
+     11725, 11729, 11733, 11737, 11741, 11745, 11749, 11753, 11757, 11761,
+     11765, 11769, 11773, 11777, 11781, 11785, 11789, 11793, 11797, 11801,
+     11805, 11809, 11813, 11817, 11821, 11824, 11824, 11824, 11824, 11824,
+     11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824,
+     11824, 11824, 11824, 11824, 11824, 11824, 11824},
+    {11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824, 11824,
+     11824, 11824, 11825, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840, 11840,
+     11840, 11840, 11840, 11840, 11840, 11840, 11841, 11853, 11861, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872, 11872,
+     11872, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880},
+    {11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880, 11880,
+     11880, 11880, 11880, 11880, 11881, 11885, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888},
+    {11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888, 11888,
+     11888, 11889, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892},
+    {11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892,
+     11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11892, 11893,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896, 11896,
+     11896, 11896, 11896, 11897, 11900, 11900, 11900, 11900, 11900, 11900,
+     11900, 11900, 11900, 11900, 11900, 11900, 11901},
+    {11901, 11905, 11909, 11913, 11917, 11921, 11925, 11929, 11933, 11937,
+     11941, 11945, 11949, 11953, 11957, 11961, 11965, 11969, 11973, 11977,
+     11981, 11985, 11989, 11993, 11997, 12001, 12005, 12009, 12013, 12017,
+     12021, 12025, 12029, 12033, 12037, 12041, 12045, 12049, 12053, 12057,
+     12061, 12065, 12069, 12073, 12077, 12081, 12085, 12089, 12093, 12097,
+     12101, 12105, 12109, 12113, 12117, 12121, 12125, 12129, 12133, 12137,
+     12141, 12145, 12149, 12153, 12157, 12161, 12165, 12169, 12173, 12177,
+     12181, 12185, 12189, 12193, 12197, 12201, 12205, 12209, 12213, 12217,
+     12221, 12225, 12229, 12233, 12237, 12241, 12245, 12249, 12253, 12257,
+     12261, 12265, 12269, 12273, 12277, 12281, 12285, 12289, 12293, 12297,
+     12301, 12305, 12309, 12313, 12317, 12321, 12325, 12329, 12333, 12337,
+     12341, 12345, 12349, 12353, 12357, 12361, 12365, 12369, 12373, 12377,
+     12381, 12385, 12389, 12393, 12397, 12401, 12405, 12409, 12413, 12417,
+     12421, 12425, 12429, 12433, 12437, 12441, 12445, 12449, 12453, 12457,
+     12461, 12465, 12469, 12473, 12477, 12481, 12485, 12489, 12493, 12497,
+     12501, 12505, 12509, 12513, 12517, 12521, 12525, 12529, 12533, 12537,
+     12541, 12545, 12549, 12553, 12557, 12561, 12565, 12569, 12573, 12577,
+     12581, 12585, 12589, 12593, 12597, 12601, 12605, 12609, 12613, 12617,
+     12621, 12625, 12629, 12633, 12637, 12641, 12645, 12649, 12653, 12657,
+     12661, 12665, 12669, 12673, 12677, 12681, 12685, 12689, 12693, 12697,
+     12701, 12705, 12709, 12713, 12717, 12721, 12725, 12729, 12733, 12737,
+     12741, 12745, 12749, 12753, 12756, 12756, 12756, 12756, 12756, 12756,
+     12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756,
+     12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756,
+     12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756, 12756,
+     12756, 12756, 12756, 12756, 12756, 12756, 12757},
+    {12757, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760,
+     12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760,
+     12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760,
+     12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760,
+     12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760, 12760,
+     12760, 12760, 12760, 12760, 12761, 12764, 12765, 12769, 12773, 12776,
+     12776, 12776, 12776, 12776, 12776, 12776, 12776, 12776, 12776, 12776,
+     12776, 12776, 12776, 12776, 12776, 12776, 12776, 12784, 12784, 12792,
+     12792, 12800, 12800, 12808, 12808, 12816, 12816, 12824, 12824, 12832,
+     12832, 12840, 12840, 12848, 12848, 12856, 12856, 12864, 12864, 12872,
+     12872, 12872, 12880, 12880, 12888, 12888, 12896, 12896, 12896, 12896,
+     12896, 12896, 12896, 12904, 12912, 12912, 12920, 12928, 12928, 12936,
+     12944, 12944, 12952, 12960, 12960, 12968, 12976, 12976, 12976, 12976,
+     12976, 12976, 12976, 12976, 12976, 12976, 12976, 12976, 12976, 12976,
+     12976, 12976, 12976, 12976, 12976, 12976, 12976, 12976, 12976, 12984,
+     12984, 12984, 12984, 12984, 12984, 12985, 12993, 13000, 13000, 13009,
+     13016, 13016, 13016, 13016, 13016, 13016, 13016, 13016, 13016, 13016,
+     13016, 13016, 13016, 13024, 13024, 13032, 13032, 13040, 13040, 13048,
+     13048, 13056, 13056, 13064, 13064, 13072, 13072, 13080, 13080, 13088,
+     13088, 13096, 13096, 13104, 13104, 13112, 13112, 13112, 13120, 13120,
+     13128, 13128, 13136, 13136, 13136, 13136, 13136, 13136, 13136, 13144,
+     13152, 13152, 13160, 13168, 13168, 13176, 13184, 13184, 13192, 13200,
+     13200, 13208, 13216, 13216, 13216, 13216, 13216, 13216, 13216, 13216,
+     13216, 13216, 13216, 13216, 13216, 13216, 13216, 13216, 13216, 13216,
+     13216, 13216, 13216, 13216, 13216, 13224, 13224, 13224, 13232, 13240,
+     13248, 13256, 13256, 13256, 13256, 13265, 13272},
+    {13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272,
+     13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272,
+     13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272,
+     13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272,
+     13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13272, 13273,
+     13277, 13281, 13285, 13289, 13293, 13297, 13301, 13305, 13309, 13313,
+     13317, 13321, 13325, 13329, 13333, 13337, 13341, 13345, 13349, 13353,
+     13357, 13361, 13365, 13369, 13373, 13377, 13381, 13385, 13389, 13393,
+     13397, 13401, 13405, 13409, 13413, 13417, 13421, 13425, 13429, 13433,
+     13437, 13441, 13445, 13449, 13453, 13457, 13461, 13465, 13469, 13473,
+     13477, 13481, 13485, 13489, 13493, 13497, 13501, 13505, 13509, 13513,
+     13517, 13521, 13525, 13529, 13533, 13537, 13541, 13545, 13549, 13553,
+     13557, 13561, 13565, 13569, 13573, 13577, 13581, 13585, 13589, 13593,
+     13597, 13601, 13605, 13609, 13613, 13617, 13621, 13625, 13629, 13633,
+     13637, 13641, 13645, 13648, 13648, 13648, 13649, 13653, 13657, 13661,
+     13665, 13669, 13673, 13677, 13681, 13685, 13689, 13693, 13697, 13701,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704, 13704,
+     13704, 13704, 13704, 13704, 13704, 13704, 13705},
+    {13705, 13717, 13729, 13741, 13753, 13765, 13777, 13789, 13801, 13813,
+     13825, 13837, 13849, 13861, 13873, 13889, 13905, 13921, 13937, 13953,
+     13969, 13985, 14001, 14017, 14033, 14049, 14065, 14081, 14097, 14113,
+     14141, 14164, 14165, 14177, 14189, 14201, 14213, 14225, 14237, 14249,
+     14261, 14273, 14285, 14297, 14309, 14321, 14333, 14345, 14357, 14369,
+     14381, 14393, 14405, 14417, 14429, 14441, 14453, 14465, 14477, 14489,
+     14501, 14513, 14525, 14537, 14549, 14561, 14573, 14585, 14597, 14601,
+     14605, 14609, 14612, 14612, 14612, 14612, 14612, 14612, 14612, 14612,
+     14613, 14625, 14633, 14641, 14649, 14657, 14665, 14673, 14681, 14689,
+     14697, 14705, 14713, 14721, 14729, 14737, 14745, 14749, 14753, 14757,
+     14761, 14765, 14769, 14773, 14777, 14781, 14785, 14789, 14793, 14797,
+     14801, 14809, 14817, 14825, 14833, 14841, 14849, 14857, 14865, 14873,
+     14881, 14889, 14897, 14905, 14913, 14933, 14949, 14956, 14957, 14961,
+     14965, 14969, 14973, 14977, 14981, 14985, 14989, 14993, 14997, 15001,
+     15005, 15009, 15013, 15017, 15021, 15025, 15029, 15033, 15037, 15041,
+     15045, 15049, 15053, 15057, 15061, 15065, 15069, 15073, 15077, 15081,
+     15085, 15089, 15093, 15097, 15101, 15105, 15109, 15113, 15117, 15121,
+     15125, 15129, 15133, 15137, 15141, 15145, 15149, 15153, 15161, 15169,
+     15177, 15185, 15193, 15201, 15209, 15217, 15225, 15233, 15241, 15249,
+     15257, 15265, 15273, 15281, 15289, 15297, 15305, 15313, 15321, 15329,
+     15337, 15345, 15357, 15369, 15381, 15389, 15401, 15409, 15421, 15425,
+     15429, 15433, 15437, 15441, 15445, 15449, 15453, 15457, 15461, 15465,
+     15469, 15473, 15477, 15481, 15485, 15489, 15493, 15497, 15501, 15505,
+     15509, 15513, 15517, 15521, 15525, 15529, 15533, 15537, 15541, 15545,
+     15549, 15553, 15557, 15561, 15565, 15569, 15573, 15577, 15581, 15585,
+     15589, 15593, 15597, 15601, 15605, 15609, 15617},
+    {15617, 15637, 15653, 15673, 15685, 15705, 15717, 15729, 15753, 15769,
+     15781, 15793, 15805, 15821, 15837, 15853, 15869, 15885, 15901, 15917,
+     15941, 15949, 15973, 15997, 16017, 16033, 16057, 16081, 16097, 16109,
+     16121, 16137, 16153, 16173, 16193, 16205, 16217, 16233, 16245, 16257,
+     16265, 16273, 16285, 16297, 16321, 16337, 16357, 16381, 16397, 16409,
+     16421, 16445, 16461, 16485, 16497, 16517, 16529, 16545, 16557, 16573,
+     16593, 16609, 16629, 16645, 16653, 16673, 16685, 16697, 16713, 16725,
+     16737, 16749, 16769, 16785, 16793, 16817, 16829, 16849, 16865, 16881,
+     16893, 16905, 16921, 16929, 16945, 16965, 16973, 16997, 17009, 17017,
+     17025, 17033, 17041, 17049, 17057, 17065, 17073, 17081, 17089, 17101,
+     17113, 17125, 17137, 17149, 17161, 17173, 17185, 17197, 17209, 17221,
+     17233, 17245, 17257, 17269, 17281, 17289, 17297, 17309, 17317, 17325,
+     17333, 17345, 17357, 17365, 17373, 17381, 17389, 17397, 17413, 17421,
+     17429, 17437, 17445, 17453, 17461, 17469, 17477, 17489, 17505, 17513,
+     17521, 17529, 17537, 17545, 17553, 17561, 17573, 17585, 17597, 17609,
+     17617, 17625, 17633, 17641, 17649, 17657, 17665, 17673, 17681, 17689,
+     17701, 17713, 17721, 17733, 17745, 17757, 17765, 17777, 17789, 17805,
+     17813, 17825, 17837, 17849, 17861, 17881, 17905, 17913, 17921, 17929,
+     17937, 17945, 17953, 17961, 17969, 17977, 17985, 17993, 18001, 18009,
+     18017, 18025, 18033, 18041, 18049, 18065, 18073, 18081, 18089, 18105,
+     18117, 18125, 18133, 18141, 18149, 18157, 18165, 18173, 18181, 18189,
+     18197, 18209, 18217, 18225, 18237, 18249, 18257, 18273, 18285, 18293,
+     18301, 18309, 18317, 18329, 18341, 18349, 18357, 18365, 18373, 18381,
+     18389, 18397, 18405, 18413, 18425, 18437, 18449, 18461, 18473, 18485,
+     18497, 18509, 18521, 18533, 18545, 18557, 18569, 18581, 18593, 18605,
+     18617, 18629, 18641, 18653, 18665, 18677, 18688},
+    {18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688, 18688,
+     18688, 18688, 18688, 18688, 18688, 18688, 18689, 18693, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696},
+    {18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696, 18696,
+     18696, 18696, 18697, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700, 18700,
+     18700, 18700, 18701, 18705, 18709, 18712, 18712, 18712, 18713, 18717,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720},
+    {18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720, 18720,
+     18720, 18720, 18721, 18725, 18729, 18733, 18736, 18736, 18736, 18736,
+     18736, 18736, 18736, 18736, 18736, 18737, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740, 18740,
+     18740, 18740, 18740, 18740, 18740, 18740, 18740},
+    {18740, 18744, 18748, 18752, 18756, 18760, 18764, 18768, 18772, 18776,
+     18780, 18784, 18788, 18792, 18796, 18800, 18804, 18808, 18812, 18816,
+     18820, 18824, 18828, 18832, 18836, 18840, 18844, 18848, 18852, 18856,
+     18860, 18864, 18868, 18872, 18876, 18880, 18884, 18888, 18892, 18896,
+     18900, 18904, 18908, 18912, 18916, 18920, 18924, 18928, 18932, 18936,
+     18940, 18944, 18948, 18952, 18956, 18960, 18964, 18968, 18972, 18976,
+     18980, 18984, 18988, 18992, 18996, 19000, 19004, 19008, 19012, 19016,
+     19020, 19024, 19028, 19032, 19036, 19040, 19044, 19048, 19052, 19056,
+     19060, 19064, 19068, 19072, 19076, 19080, 19084, 19088, 19092, 19096,
+     19100, 19104, 19108, 19112, 19116, 19120, 19124, 19128, 19132, 19136,
+     19140, 19144, 19148, 19152, 19156, 19160, 19164, 19168, 19172, 19176,
+     19180, 19184, 19188, 19192, 19196, 19200, 19204, 19208, 19212, 19216,
+     19220, 19224, 19228, 19232, 19236, 19240, 19244, 19248, 19252, 19256,
+     19260, 19264, 19268, 19272, 19276, 19280, 19284, 19288, 19292, 19296,
+     19300, 19304, 19308, 19312, 19316, 19320, 19324, 19328, 19332, 19336,
+     19340, 19344, 19348, 19352, 19356, 19360, 19364, 19368, 19372, 19376,
+     19380, 19384, 19388, 19392, 19396, 19400, 19404, 19408, 19412, 19416,
+     19420, 19424, 19428, 19432, 19436, 19440, 19444, 19448, 19452, 19456,
+     19460, 19464, 19468, 19472, 19476, 19480, 19484, 19488, 19492, 19496,
+     19500, 19504, 19508, 19512, 19516, 19520, 19524, 19528, 19532, 19536,
+     19540, 19544, 19548, 19552, 19556, 19560, 19564, 19568, 19572, 19576,
+     19580, 19584, 19588, 19592, 19596, 19600, 19604, 19608, 19612, 19616,
+     19620, 19624, 19628, 19632, 19636, 19640, 19644, 19648, 19652, 19656,
+     19660, 19664, 19668, 19672, 19676, 19680, 19684, 19688, 19692, 19696,
+     19700, 19704, 19708, 19712, 19716, 19720, 19724, 19728, 19732, 19736,
+     19740, 19744, 19748, 19752, 19756, 19760, 19764},
+    {19764, 19768, 19772, 19776, 19780, 19784, 19788, 19792, 19796, 19800,
+     19804, 19808, 19812, 19816, 19820, 19820, 19820, 19824, 19824, 19828,
+     19828, 19828, 19832, 19836, 19840, 19844, 19848, 19852, 19856, 19860,
+     19864, 19868, 19868, 19872, 19872, 19876, 19876, 19876, 19880, 19884,
+     19884, 19884, 19884, 19888, 19892, 19896, 19900, 19904, 19908, 19912,
+     19916, 19920, 19924, 19928, 19932, 19936, 19940, 19944, 19948, 19952,
+     19956, 19960, 19964, 19968, 19972, 19976, 19980, 19984, 19988, 19992,
+     19996, 20000, 20004, 20008, 20012, 20016, 20020, 20024, 20028, 20032,
+     20036, 20040, 20044, 20048, 20052, 20056, 20060, 20064, 20068, 20072,
+     20076, 20080, 20084, 20088, 20092, 20096, 20100, 20104, 20108, 20112,
+     20116, 20120, 20124, 20128, 20132, 20136, 20140, 20144, 20148, 20152,
+     20156, 20156, 20156, 20160, 20164, 20168, 20172, 20176, 20180, 20184,
+     20188, 20192, 20196, 20200, 20204, 20208, 20212, 20216, 20220, 20224,
+     20228, 20232, 20236, 20240, 20244, 20248, 20252, 20256, 20260, 20264,
+     20268, 20272, 20276, 20280, 20284, 20288, 20292, 20296, 20300, 20304,
+     20308, 20312, 20316, 20320, 20324, 20328, 20332, 20336, 20340, 20344,
+     20348, 20352, 20356, 20360, 20364, 20368, 20372, 20376, 20380, 20384,
+     20388, 20392, 20396, 20400, 20404, 20408, 20412, 20416, 20420, 20424,
+     20428, 20432, 20436, 20440, 20444, 20448, 20452, 20456, 20460, 20464,
+     20468, 20472, 20476, 20480, 20484, 20488, 20492, 20496, 20500, 20504,
+     20508, 20512, 20516, 20520, 20524, 20528, 20532, 20536, 20540, 20544,
+     20548, 20552, 20556, 20560, 20564, 20568, 20572, 20576, 20580, 20580,
+     20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580,
+     20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580,
+     20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580, 20580,
+     20580, 20580, 20580, 20580, 20580, 20580, 20581},
+    {20581, 20589, 20597, 20605, 20617, 20629, 20637, 20644, 20644, 20644,
+     20644, 20644, 20644, 20644, 20644, 20644, 20644, 20644, 20644, 20645,
+     20653, 20661, 20669, 20677, 20684, 20684, 20684, 20684, 20684, 20684,
+     20692, 20692, 20701, 20705, 20709, 20713, 20717, 20721, 20725, 20729,
+     20733, 20737, 20740, 20748, 20756, 20768, 20780, 20788, 20796, 20804,
+     20812, 20820, 20828, 20836, 20844, 20852, 20852, 20860, 20868, 20876,
+     20884, 20892, 20892, 20900, 20900, 20908, 20916, 20916, 20924, 20932,
+     20932, 20940, 20948, 20956, 20964, 20972, 20980, 20988, 20996, 21005,
+     21013, 21017, 21021, 21025, 21029, 21033, 21037, 21041, 21045, 21049,
+     21053, 21057, 21061, 21065, 21069, 21073, 21077, 21081, 21085, 21089,
+     21093, 21097, 21101, 21105, 21109, 21113, 21117, 21121, 21125, 21129,
+     21133, 21137, 21141, 21145, 21149, 21153, 21157, 21161, 21165, 21169,
+     21173, 21177, 21181, 21185, 21189, 21193, 21197, 21201, 21205, 21209,
+     21213, 21217, 21221, 21225, 21229, 21233, 21237, 21241, 21245, 21249,
+     21253, 21257, 21261, 21265, 21269, 21273, 21277, 21281, 21285, 21289,
+     21293, 21297, 21301, 21305, 21309, 21313, 21317, 21321, 21325, 21329,
+     21333, 21337, 21341, 21345, 21349, 21357, 21365, 21369, 21373, 21377,
+     21381, 21385, 21389, 21393, 21397, 21401, 21405, 21413, 21420, 21420,
+     21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420,
+     21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420,
+     21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420, 21420,
+     21420, 21421, 21425, 21429, 21433, 21437, 21441, 21445, 21449, 21453,
+     21457, 21461, 21469, 21473, 21477, 21481, 21485, 21489, 21493, 21497,
+     21501, 21505, 21509, 21513, 21517, 21529, 21541, 21553, 21565, 21577,
+     21589, 21601, 21613, 21625, 21637, 21649, 21661, 21673, 21685, 21697,
+     21709, 21721, 21733, 21737, 21741, 21745, 21749},
+    {21749, 21761, 21773, 21785, 21797, 21809, 21817, 21825, 21833, 21841,
+     21849, 21857, 21865, 21873, 21881, 21889, 21897, 21905, 21913, 21921,
+     21929, 21937, 21945, 21953, 21961, 21969, 21977, 21985, 21993, 22001,
+     22009, 22017, 22025, 22033, 22041, 22049, 22057, 22065, 22073, 22081,
+     22089, 22097, 22105, 22113, 22121, 22129, 22137, 22145, 22153, 22161,
+     22169, 22177, 22185, 22193, 22201, 22209, 22217, 22225, 22233, 22241,
+     22249, 22257, 22265, 22273, 22281, 22289, 22297, 22305, 22313, 22321,
+     22329, 22337, 22345, 22353, 22361, 22369, 22377, 22385, 22393, 22401,
+     22409, 22417, 22425, 22433, 22441, 22449, 22457, 22465, 22473, 22481,
+     22489, 22497, 22505, 22513, 22521, 22533, 22545, 22557, 22569, 22581,
+     22593, 22605, 22617, 22629, 22641, 22653, 22665, 22673, 22681, 22689,
+     22697, 22705, 22713, 22721, 22729, 22737, 22745, 22753, 22761, 22769,
+     22777, 22785, 22793, 22801, 22809, 22817, 22825, 22833, 22841, 22849,
+     22857, 22865, 22873, 22881, 22889, 22897, 22905, 22913, 22921, 22929,
+     22937, 22945, 22953, 22961, 22969, 22977, 22985, 22993, 23001, 23009,
+     23017, 23025, 23037, 23049, 23061, 23073, 23085, 23093, 23101, 23109,
+     23117, 23125, 23133, 23141, 23149, 23157, 23165, 23173, 23181, 23189,
+     23197, 23205, 23213, 23221, 23229, 23237, 23245, 23253, 23261, 23269,
+     23277, 23285, 23293, 23301, 23309, 23317, 23325, 23333, 23341, 23349,
+     23357, 23365, 23373, 23381, 23389, 23397, 23405, 23413, 23421, 23429,
+     23437, 23445, 23453, 23461, 23469, 23477, 23485, 23493, 23501, 23509,
+     23517, 23525, 23533, 23541, 23549, 23557, 23565, 23573, 23581, 23589,
+     23597, 23605, 23613, 23621, 23633, 23645, 23653, 23661, 23669, 23677,
+     23685, 23693, 23701, 23709, 23717, 23725, 23733, 23741, 23749, 23757,
+     23765, 23773, 23781, 23793, 23805, 23817, 23825, 23833, 23841, 23849,
+     23857, 23865, 23873, 23881, 23889, 23897, 23905},
+    {23905, 23913, 23921, 23929, 23937, 23945, 23953, 23961, 23969, 23977,
+     23985, 23993, 24001, 24009, 24017, 24025, 24033, 24041, 24049, 24057,
+     24065, 24073, 24081, 24089, 24097, 24105, 24113, 24121, 24129, 24137,
+     24145, 24153, 24161, 24169, 24177, 24185, 24193, 24201, 24209, 24217,
+     24225, 24233, 24241, 24249, 24257, 24265, 24273, 24281, 24289, 24297,
+     24305, 24313, 24321, 24329, 24337, 24345, 24353, 24361, 24369, 24377,
+     24385, 24393, 24400, 24400, 24400, 24400, 24400, 24400, 24400, 24400,
+     24400, 24400, 24400, 24400, 24400, 24400, 24400, 24400, 24400, 24400,
+     24401, 24413, 24425, 24437, 24449, 24461, 24473, 24485, 24497, 24509,
+     24521, 24533, 24545, 24557, 24569, 24581, 24593, 24605, 24617, 24629,
+     24641, 24653, 24665, 24677, 24689, 24701, 24713, 24725, 24737, 24749,
+     24761, 24773, 24785, 24797, 24809, 24821, 24833, 24845, 24857, 24869,
+     24881, 24893, 24905, 24917, 24929, 24941, 24953, 24965, 24977, 24989,
+     25001, 25013, 25025, 25037, 25049, 25061, 25073, 25085, 25097, 25109,
+     25121, 25133, 25145, 25157, 25168, 25168, 25169, 25181, 25193, 25205,
+     25217, 25229, 25241, 25253, 25265, 25277, 25289, 25301, 25313, 25325,
+     25337, 25349, 25361, 25373, 25385, 25397, 25409, 25421, 25433, 25445,
+     25457, 25469, 25481, 25493, 25505, 25517, 25529, 25541, 25553, 25565,
+     25577, 25589, 25601, 25613, 25625, 25637, 25649, 25661, 25673, 25685,
+     25697, 25709, 25721, 25733, 25745, 25757, 25769, 25781, 25793, 25805,
+     25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816,
+     25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816,
+     25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816,
+     25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816, 25816,
+     25817, 25829, 25841, 25857, 25873, 25889, 25905, 25921, 25937, 25953,
+     25965, 26037, 26069, 26084, 26084, 26084, 26084},
+    {26084, 26084, 26084, 26084, 26084, 26084, 26084, 26084, 26084, 26084,
+     26084, 26084, 26084, 26084, 26084, 26084, 26085, 26089, 26093, 26097,
+     26101, 26105, 26109, 26113, 26117, 26121, 26132, 26132, 26132, 26132,
+     26132, 26132, 26132, 26132, 26132, 26132, 26132, 26132, 26132, 26132,
+     26132, 26132, 26132, 26132, 26132, 26132, 26132, 26132, 26133, 26141,
+     26145, 26149, 26153, 26157, 26161, 26165, 26169, 26173, 26177, 26181,
+     26185, 26189, 26193, 26197, 26201, 26205, 26209, 26213, 26217, 26220,
+     26220, 26221, 26225, 26229, 26237, 26245, 26253, 26261, 26265, 26269,
+     26273, 26277, 26281, 26284, 26285, 26289, 26293, 26297, 26301, 26305,
+     26309, 26313, 26317, 26321, 26325, 26329, 26333, 26337, 26341, 26345,
+     26349, 26353, 26357, 26360, 26361, 26365, 26369, 26373, 26376, 26376,
+     26376, 26376, 26377, 26385, 26393, 26400, 26401, 26408, 26409, 26417,
+     26425, 26433, 26441, 26449, 26457, 26465, 26473, 26481, 26489, 26493,
+     26501, 26509, 26517, 26525, 26533, 26541, 26549, 26557, 26565, 26573,
+     26581, 26589, 26593, 26597, 26601, 26605, 26609, 26613, 26617, 26621,
+     26625, 26629, 26633, 26637, 26641, 26645, 26649, 26653, 26657, 26661,
+     26665, 26669, 26673, 26677, 26681, 26685, 26689, 26693, 26697, 26701,
+     26705, 26709, 26713, 26717, 26721, 26725, 26729, 26733, 26737, 26741,
+     26745, 26749, 26753, 26757, 26761, 26765, 26769, 26773, 26777, 26781,
+     26785, 26789, 26793, 26797, 26801, 26805, 26809, 26813, 26817, 26821,
+     26825, 26829, 26833, 26837, 26841, 26845, 26849, 26853, 26857, 26861,
+     26865, 26869, 26873, 26877, 26881, 26885, 26889, 26893, 26897, 26901,
+     26905, 26909, 26913, 26917, 26921, 26925, 26929, 26933, 26937, 26941,
+     26945, 26949, 26953, 26957, 26961, 26965, 26969, 26973, 26977, 26981,
+     26985, 26989, 26993, 26997, 27001, 27005, 27017, 27029, 27041, 27053,
+     27065, 27077, 27085, 27092, 27092, 27092, 27092},
+    {27092, 27093, 27097, 27101, 27105, 27109, 27113, 27117, 27121, 27125,
+     27129, 27133, 27137, 27141, 27145, 27149, 27153, 27157, 27161, 27165,
+     27169, 27173, 27177, 27181, 27185, 27189, 27193, 27197, 27201, 27205,
+     27209, 27213, 27217, 27221, 27225, 27229, 27233, 27237, 27241, 27245,
+     27249, 27253, 27257, 27261, 27265, 27269, 27273, 27277, 27281, 27285,
+     27289, 27293, 27297, 27301, 27305, 27309, 27313, 27317, 27321, 27325,
+     27329, 27333, 27337, 27341, 27345, 27349, 27353, 27357, 27361, 27365,
+     27369, 27373, 27377, 27381, 27385, 27389, 27393, 27397, 27401, 27405,
+     27409, 27413, 27417, 27421, 27425, 27429, 27433, 27437, 27441, 27445,
+     27449, 27453, 27457, 27461, 27465, 27469, 27473, 27477, 27481, 27485,
+     27489, 27493, 27497, 27501, 27505, 27509, 27513, 27517, 27521, 27525,
+     27529, 27533, 27537, 27541, 27545, 27549, 27553, 27557, 27561, 27565,
+     27569, 27573, 27577, 27581, 27585, 27589, 27593, 27597, 27601, 27605,
+     27609, 27613, 27617, 27621, 27625, 27629, 27633, 27637, 27641, 27645,
+     27649, 27653, 27657, 27661, 27665, 27669, 27673, 27677, 27681, 27685,
+     27689, 27693, 27697, 27701, 27705, 27709, 27713, 27717, 27721, 27725,
+     27729, 27733, 27737, 27741, 27745, 27749, 27753, 27757, 27761, 27765,
+     27769, 27773, 27777, 27781, 27785, 27789, 27793, 27797, 27801, 27805,
+     27809, 27813, 27817, 27821, 27825, 27829, 27833, 27837, 27841, 27845,
+     27849, 27852, 27852, 27852, 27853, 27857, 27861, 27865, 27869, 27873,
+     27876, 27876, 27877, 27881, 27885, 27889, 27893, 27897, 27900, 27900,
+     27901, 27905, 27909, 27913, 27917, 27921, 27924, 27924, 27925, 27929,
+     27933, 27936, 27936, 27936, 27937, 27941, 27945, 27949, 27957, 27961,
+     27965, 27968, 27969, 27973, 27977, 27981, 27985, 27989, 27993, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996},
+    {27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996,
+     27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27996, 27997,
+     28001, 28005, 28009, 28013, 28016, 28017, 28021, 28025, 28029, 28033,
+     28037, 28041, 28045, 28049, 28053, 28057, 28061, 28065, 28069, 28073,
+     28077, 28081, 28085, 28089, 28093, 28097, 28101, 28105, 28109, 28113,
+     28117, 28121, 28125, 28129, 28133, 28137, 28141, 28145, 28149, 28153,
+     28157, 28161, 28165, 28169, 28173, 28177, 28181, 28184, 28185, 28189,
+     28193, 28197, 28201, 28205, 28209, 28213, 28217, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220},
+    {28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220, 28220,
+     28220, 28220, 28220, 28220, 28220, 28228, 28228, 28236, 28236, 28236,
+     28236, 28236, 28236, 28236, 28236, 28236, 28236, 28236, 28236, 28236,
+     28236, 28236, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244},
+    {28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244, 28244,
+     28244, 28244, 28244, 28244, 28244, 28244, 28244, 28252, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260},
+    {28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260, 28260,
+     28260, 28260, 28260, 28260, 28260, 28260, 28268, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276},
+    {28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276,
+     28276, 28276, 28276, 28276, 28276, 28276, 28276, 28276, 28284, 28292,
+     28292, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300},
+    {28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300, 28300,
+     28300, 28300, 28300, 28300, 28300, 28300, 28300, 28308, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316},
+    {28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316, 28316,
+     28316, 28316, 28316, 28316, 28316, 28316, 28316, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324},
+    {28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324, 28324,
+     28324, 28324, 28324, 28324, 28324, 28332, 28340, 28352, 28364, 28376,
+     28388, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400,
+     28400, 28400, 28400, 28400, 28400, 28400, 28400, 28400, 28408, 28416,
+     28428, 28440, 28452, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464},
+    {28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464, 28464,
+     28464, 28464, 28464, 28464, 28464, 28464, 28465},
+    {28465, 28469, 28473, 28477, 28481, 28485, 28489, 28493, 28497, 28501,
+     28505, 28509, 28513, 28517, 28521, 28525, 28529, 28533, 28537, 28541,
+     28545, 28549, 28553, 28557, 28561, 28565, 28569, 28573, 28577, 28581,
+     28585, 28589, 28593, 28597, 28601, 28605, 28609, 28613, 28617, 28621,
+     28625, 28629, 28633, 28637, 28641, 28645, 28649, 28653, 28657, 28661,
+     28665, 28669, 28673, 28677, 28681, 28685, 28689, 28693, 28697, 28701,
+     28705, 28709, 28713, 28717, 28721, 28725, 28729, 28733, 28737, 28741,
+     28745, 28749, 28753, 28757, 28761, 28765, 28769, 28773, 28777, 28781,
+     28785, 28789, 28793, 28797, 28801, 28804, 28805, 28809, 28813, 28817,
+     28821, 28825, 28829, 28833, 28837, 28841, 28845, 28849, 28853, 28857,
+     28861, 28865, 28869, 28873, 28877, 28881, 28885, 28889, 28893, 28897,
+     28901, 28905, 28909, 28913, 28917, 28921, 28925, 28929, 28933, 28937,
+     28941, 28945, 28949, 28953, 28957, 28961, 28965, 28969, 28973, 28977,
+     28981, 28985, 28989, 28993, 28997, 29001, 29005, 29009, 29013, 29017,
+     29021, 29025, 29029, 29033, 29037, 29041, 29045, 29049, 29053, 29057,
+     29061, 29065, 29069, 29073, 29077, 29081, 29085, 29088, 29089, 29093,
+     29096, 29096, 29097, 29100, 29100, 29101, 29105, 29108, 29108, 29109,
+     29113, 29117, 29121, 29124, 29125, 29129, 29133, 29137, 29141, 29145,
+     29149, 29153, 29157, 29161, 29165, 29169, 29172, 29173, 29176, 29177,
+     29181, 29185, 29189, 29193, 29197, 29201, 29204, 29205, 29209, 29213,
+     29217, 29221, 29225, 29229, 29233, 29237, 29241, 29245, 29249, 29253,
+     29257, 29261, 29265, 29269, 29273, 29277, 29281, 29285, 29289, 29293,
+     29297, 29301, 29305, 29309, 29313, 29317, 29321, 29325, 29329, 29333,
+     29337, 29341, 29345, 29349, 29353, 29357, 29361, 29365, 29369, 29373,
+     29377, 29381, 29385, 29389, 29393, 29397, 29401, 29405, 29409, 29413,
+     29417, 29421, 29425, 29429, 29433, 29437, 29441},
+    {29441, 29445, 29449, 29453, 29457, 29461, 29464, 29465, 29469, 29473,
+     29477, 29480, 29480, 29481, 29485, 29489, 29493, 29497, 29501, 29505,
+     29509, 29512, 29513, 29517, 29521, 29525, 29529, 29533, 29537, 29540,
+     29541, 29545, 29549, 29553, 29557, 29561, 29565, 29569, 29573, 29577,
+     29581, 29585, 29589, 29593, 29597, 29601, 29605, 29609, 29613, 29617,
+     29621, 29625, 29629, 29633, 29637, 29641, 29645, 29649, 29652, 29653,
+     29657, 29661, 29665, 29668, 29669, 29673, 29677, 29681, 29685, 29688,
+     29689, 29692, 29692, 29692, 29693, 29697, 29701, 29705, 29709, 29713,
+     29717, 29720, 29721, 29725, 29729, 29733, 29737, 29741, 29745, 29749,
+     29753, 29757, 29761, 29765, 29769, 29773, 29777, 29781, 29785, 29789,
+     29793, 29797, 29801, 29805, 29809, 29813, 29817, 29821, 29825, 29829,
+     29833, 29837, 29841, 29845, 29849, 29853, 29857, 29861, 29865, 29869,
+     29873, 29877, 29881, 29885, 29889, 29893, 29897, 29901, 29905, 29909,
+     29913, 29917, 29921, 29925, 29929, 29933, 29937, 29941, 29945, 29949,
+     29953, 29957, 29961, 29965, 29969, 29973, 29977, 29981, 29985, 29989,
+     29993, 29997, 30001, 30005, 30009, 30013, 30017, 30021, 30025, 30029,
+     30033, 30037, 30041, 30045, 30049, 30053, 30057, 30061, 30065, 30069,
+     30073, 30077, 30081, 30085, 30089, 30093, 30097, 30101, 30105, 30109,
+     30113, 30117, 30121, 30125, 30129, 30133, 30137, 30141, 30145, 30149,
+     30153, 30157, 30161, 30165, 30169, 30173, 30177, 30181, 30185, 30189,
+     30193, 30197, 30201, 30205, 30209, 30213, 30217, 30221, 30225, 30229,
+     30233, 30237, 30241, 30245, 30249, 30253, 30257, 30261, 30265, 30269,
+     30273, 30277, 30281, 30285, 30289, 30293, 30297, 30301, 30305, 30309,
+     30313, 30317, 30321, 30325, 30329, 30333, 30337, 30341, 30345, 30349,
+     30353, 30357, 30361, 30365, 30369, 30373, 30377, 30381, 30385, 30389,
+     30393, 30397, 30401, 30405, 30409, 30413, 30417},
+    {30417, 30421, 30425, 30429, 30433, 30437, 30441, 30445, 30449, 30453,
+     30457, 30461, 30465, 30469, 30473, 30477, 30481, 30485, 30489, 30493,
+     30497, 30501, 30505, 30509, 30513, 30517, 30521, 30525, 30529, 30533,
+     30537, 30541, 30545, 30549, 30553, 30557, 30561, 30565, 30569, 30573,
+     30577, 30581, 30585, 30589, 30593, 30597, 30601, 30605, 30609, 30613,
+     30617, 30621, 30625, 30629, 30633, 30637, 30641, 30645, 30649, 30653,
+     30657, 30661, 30665, 30669, 30673, 30677, 30681, 30685, 30689, 30693,
+     30697, 30701, 30705, 30709, 30713, 30717, 30721, 30725, 30729, 30733,
+     30737, 30741, 30745, 30749, 30753, 30757, 30761, 30765, 30769, 30773,
+     30777, 30781, 30785, 30789, 30793, 30797, 30801, 30805, 30809, 30813,
+     30817, 30821, 30825, 30829, 30833, 30837, 30841, 30845, 30849, 30853,
+     30857, 30861, 30865, 30869, 30873, 30877, 30881, 30885, 30889, 30893,
+     30897, 30901, 30905, 30909, 30913, 30917, 30921, 30925, 30929, 30933,
+     30937, 30941, 30945, 30949, 30953, 30957, 30961, 30965, 30969, 30973,
+     30977, 30981, 30985, 30989, 30993, 30997, 31001, 31005, 31009, 31013,
+     31017, 31021, 31025, 31029, 31033, 31037, 31041, 31045, 31049, 31053,
+     31057, 31061, 31065, 31069, 31073, 31077, 31080, 31080, 31081, 31085,
+     31089, 31093, 31097, 31101, 31105, 31109, 31113, 31117, 31121, 31125,
+     31129, 31133, 31137, 31141, 31145, 31149, 31153, 31157, 31161, 31165,
+     31169, 31173, 31177, 31181, 31185, 31189, 31193, 31197, 31201, 31205,
+     31209, 31213, 31217, 31221, 31225, 31229, 31233, 31237, 31241, 31245,
+     31249, 31253, 31257, 31261, 31265, 31269, 31273, 31277, 31281, 31285,
+     31289, 31293, 31297, 31301, 31305, 31309, 31313, 31317, 31321, 31325,
+     31329, 31333, 31337, 31341, 31345, 31349, 31353, 31357, 31361, 31365,
+     31369, 31373, 31377, 31381, 31385, 31389, 31393, 31397, 31401, 31405,
+     31409, 31413, 31417, 31421, 31425, 31429, 31433},
+    {31433, 31437, 31441, 31445, 31449, 31453, 31457, 31461, 31465, 31469,
+     31473, 31477, 31481, 31485, 31489, 31493, 31497, 31501, 31505, 31509,
+     31513, 31517, 31521, 31525, 31529, 31533, 31537, 31541, 31545, 31549,
+     31553, 31557, 31561, 31565, 31569, 31573, 31577, 31581, 31585, 31589,
+     31593, 31597, 31601, 31605, 31609, 31613, 31617, 31621, 31625, 31629,
+     31633, 31637, 31641, 31645, 31649, 31653, 31657, 31661, 31665, 31669,
+     31673, 31677, 31681, 31685, 31689, 31693, 31697, 31701, 31705, 31709,
+     31713, 31717, 31721, 31725, 31729, 31733, 31737, 31741, 31745, 31749,
+     31753, 31757, 31761, 31765, 31769, 31773, 31777, 31781, 31785, 31789,
+     31793, 31797, 31801, 31805, 31809, 31813, 31817, 31821, 31825, 31829,
+     31833, 31837, 31841, 31845, 31849, 31853, 31857, 31861, 31865, 31869,
+     31873, 31877, 31881, 31885, 31889, 31893, 31897, 31901, 31905, 31909,
+     31913, 31917, 31921, 31925, 31929, 31933, 31937, 31941, 31945, 31949,
+     31953, 31957, 31961, 31965, 31969, 31973, 31977, 31981, 31985, 31989,
+     31993, 31997, 32001, 32005, 32009, 32013, 32017, 32021, 32025, 32029,
+     32033, 32037, 32041, 32045, 32049, 32053, 32057, 32061, 32065, 32069,
+     32073, 32077, 32081, 32085, 32089, 32093, 32097, 32101, 32105, 32109,
+     32113, 32117, 32121, 32125, 32129, 32133, 32137, 32141, 32145, 32149,
+     32153, 32157, 32161, 32165, 32169, 32173, 32177, 32181, 32185, 32189,
+     32193, 32197, 32201, 32205, 32209, 32213, 32217, 32221, 32225, 32229,
+     32233, 32237, 32241, 32245, 32248, 32248, 32249, 32253, 32257, 32261,
+     32265, 32269, 32273, 32277, 32281, 32285, 32289, 32293, 32297, 32301,
+     32305, 32309, 32313, 32317, 32321, 32325, 32329, 32333, 32337, 32341,
+     32345, 32349, 32353, 32357, 32361, 32365, 32369, 32373, 32377, 32381,
+     32385, 32389, 32393, 32397, 32401, 32405, 32409, 32413, 32417, 32421,
+     32425, 32429, 32433, 32437, 32441, 32445, 32448},
+    {32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448,
+     32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448,
+     32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448,
+     32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448,
+     32448, 32448, 32448, 32448, 32448, 32448, 32448, 32448, 32449, 32453,
+     32457, 32461, 32465, 32469, 32473, 32477, 32481, 32485, 32489, 32493,
+     32497, 32501, 32505, 32509, 32513, 32517, 32521, 32525, 32529, 32533,
+     32537, 32541, 32545, 32549, 32553, 32557, 32561, 32565, 32569, 32573,
+     32577, 32581, 32585, 32589, 32593, 32597, 32601, 32605, 32609, 32613,
+     32617, 32621, 32625, 32629, 32633, 32637, 32641, 32645, 32649, 32653,
+     32657, 32661, 32665, 32669, 32673, 32677, 32681, 32685, 32689, 32693,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696},
+    {32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696, 32696,
+     32696, 32696, 32696, 32696, 32696, 32696, 32697},
+    {32697, 32701, 32705, 32709, 32712, 32713, 32717, 32721, 32725, 32729,
+     32733, 32737, 32741, 32745, 32749, 32753, 32757, 32761, 32765, 32769,
+     32773, 32777, 32781, 32785, 32789, 32793, 32797, 32801, 32805, 32809,
+     32813, 32817, 32820, 32821, 32825, 32828, 32829, 32832, 32832, 32833,
+     32836, 32837, 32841, 32845, 32849, 32853, 32857, 32861, 32865, 32869,
+     32873, 32876, 32877, 32881, 32885, 32889, 32892, 32893, 32896, 32897,
+     32900, 32900, 32900, 32900, 32900, 32900, 32901, 32904, 32904, 32904,
+     32904, 32905, 32908, 32909, 32912, 32913, 32916, 32917, 32921, 32925,
+     32928, 32929, 32933, 32936, 32937, 32940, 32940, 32941, 32944, 32945,
+     32948, 32949, 32952, 32953, 32956, 32957, 32960, 32961, 32965, 32968,
+     32969, 32972, 32972, 32973, 32977, 32981, 32985, 32988, 32989, 32993,
+     32997, 33001, 33005, 33009, 33013, 33016, 33017, 33021, 33025, 33029,
+     33032, 33033, 33037, 33041, 33045, 33048, 33049, 33052, 33053, 33057,
+     33061, 33065, 33069, 33073, 33077, 33081, 33085, 33089, 33092, 33093,
+     33097, 33101, 33105, 33109, 33113, 33117, 33121, 33125, 33129, 33133,
+     33137, 33141, 33145, 33149, 33153, 33157, 33160, 33160, 33160, 33160,
+     33160, 33161, 33165, 33169, 33172, 33173, 33177, 33181, 33185, 33189,
+     33192, 33193, 33197, 33201, 33205, 33209, 33213, 33217, 33221, 33225,
+     33229, 33233, 33237, 33241, 33245, 33249, 33253, 33257, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260},
+    {33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260, 33260,
+     33260, 33260, 33260, 33260, 33260, 33260, 33261},
+    {33261, 33269, 33277, 33285, 33293, 33301, 33309, 33317, 33325, 33333,
+     33341, 33348, 33348, 33348, 33348, 33348, 33349, 33361, 33373, 33385,
+     33397, 33409, 33421, 33433, 33445, 33457, 33469, 33481, 33493, 33505,
+     33517, 33529, 33541, 33553, 33565, 33577, 33589, 33601, 33613, 33625,
+     33637, 33649, 33661, 33673, 33677, 33681, 33689, 33696, 33697, 33701,
+     33705, 33709, 33713, 33717, 33721, 33725, 33729, 33733, 33737, 33741,
+     33745, 33749, 33753, 33757, 33761, 33765, 33769, 33773, 33777, 33781,
+     33785, 33789, 33793, 33797, 33801, 33809, 33817, 33825, 33833, 33845,
+     33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852,
+     33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852, 33852,
+     33852, 33852, 33852, 33852, 33852, 33852, 33853, 33861, 33869, 33876,
+     33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876,
+     33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876,
+     33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876, 33876,
+     33876, 33876, 33876, 33876, 33877, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884, 33884,
+     33884, 33884, 33884, 33884, 33884, 33884, 33885},
+    {33885, 33893, 33901, 33904, 33904, 33904, 33904, 33904, 33904, 33904,
+     33904, 33904, 33904, 33904, 33904, 33904, 33905, 33909, 33913, 33917,
+     33925, 33929, 33933, 33937, 33941, 33945, 33949, 33953, 33957, 33961,
+     33965, 33969, 33973, 33977, 33981, 33985, 33989, 33993, 33997, 34001,
+     34005, 34009, 34013, 34017, 34021, 34025, 34029, 34033, 34037, 34041,
+     34045, 34049, 34053, 34057, 34061, 34065, 34069, 34073, 34077, 34081,
+     34084, 34084, 34084, 34084, 34085, 34097, 34109, 34121, 34133, 34145,
+     34157, 34169, 34181, 34192, 34192, 34192, 34192, 34192, 34192, 34192,
+     34193, 34197, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200},
+    {34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200, 34200,
+     34201, 34205, 34209, 34213, 34217, 34221, 34225, 34229, 34233, 34237,
+     34240, 34240, 34240, 34240, 34240, 34240, 34240},
+    {34240, 34244, 34248, 34252, 34256, 34260, 34264, 34268, 34272, 34276,
+     34280, 34284, 34288, 34292, 34296, 34300, 34304, 34308, 34312, 34316,
+     34320, 34324, 34328, 34332, 34336, 34340, 34344, 34348, 34352, 34356,
+     34360, 34364, 34368, 34372, 34376, 34380, 34384, 34388, 34392, 34396,
+     34400, 34404, 34408, 34412, 34416, 34420, 34424, 34428, 34432, 34436,
+     34440, 34444, 34448, 34452, 34456, 34460, 34464, 34468, 34472, 34476,
+     34480, 34484, 34488, 34492, 34496, 34500, 34504, 34508, 34512, 34516,
+     34520, 34524, 34528, 34532, 34536, 34540, 34544, 34548, 34552, 34556,
+     34560, 34564, 34568, 34572, 34576, 34580, 34584, 34588, 34592, 34596,
+     34600, 34604, 34608, 34612, 34616, 34620, 34624, 34628, 34632, 34636,
+     34640, 34644, 34648, 34652, 34656, 34660, 34664, 34668, 34672, 34676,
+     34680, 34684, 34688, 34692, 34696, 34700, 34704, 34708, 34712, 34716,
+     34720, 34724, 34728, 34732, 34736, 34740, 34744, 34748, 34752, 34756,
+     34760, 34764, 34768, 34772, 34776, 34780, 34784, 34788, 34792, 34796,
+     34800, 34804, 34808, 34812, 34816, 34820, 34824, 34828, 34832, 34836,
+     34840, 34844, 34848, 34852, 34856, 34860, 34864, 34868, 34872, 34876,
+     34880, 34884, 34888, 34892, 34896, 34900, 34904, 34908, 34912, 34916,
+     34920, 34924, 34928, 34932, 34936, 34940, 34944, 34948, 34952, 34956,
+     34960, 34964, 34968, 34972, 34976, 34980, 34984, 34988, 34992, 34996,
+     35000, 35004, 35008, 35012, 35016, 35020, 35024, 35028, 35032, 35036,
+     35040, 35044, 35048, 35052, 35056, 35060, 35064, 35068, 35072, 35076,
+     35080, 35084, 35088, 35092, 35096, 35100, 35104, 35108, 35112, 35116,
+     35120, 35124, 35128, 35132, 35136, 35140, 35144, 35148, 35152, 35156,
+     35160, 35164, 35168, 35172, 35176, 35180, 35184, 35188, 35192, 35196,
+     35200, 35204, 35208, 35212, 35216, 35220, 35224, 35228, 35232, 35236,
+     35240, 35244, 35248, 35252, 35256, 35260, 35264},
+    {35264, 35268, 35272, 35276, 35280, 35284, 35288, 35292, 35296, 35300,
+     35304, 35308, 35312, 35316, 35320, 35324, 35328, 35332, 35336, 35340,
+     35344, 35348, 35352, 35356, 35360, 35364, 35368, 35372, 35376, 35380,
+     35384, 35388, 35392, 35396, 35400, 35404, 35408, 35412, 35416, 35420,
+     35424, 35428, 35432, 35436, 35440, 35444, 35448, 35452, 35456, 35460,
+     35464, 35468, 35472, 35476, 35480, 35484, 35488, 35492, 35496, 35500,
+     35504, 35508, 35512, 35516, 35520, 35524, 35528, 35532, 35536, 35540,
+     35544, 35548, 35552, 35556, 35560, 35564, 35568, 35572, 35576, 35580,
+     35584, 35588, 35592, 35596, 35600, 35604, 35608, 35612, 35616, 35620,
+     35624, 35628, 35632, 35636, 35640, 35644, 35648, 35652, 35656, 35660,
+     35664, 35668, 35672, 35676, 35680, 35684, 35688, 35692, 35696, 35700,
+     35704, 35708, 35712, 35716, 35720, 35724, 35728, 35732, 35736, 35740,
+     35744, 35748, 35752, 35756, 35760, 35764, 35768, 35772, 35776, 35780,
+     35784, 35788, 35792, 35796, 35800, 35804, 35808, 35812, 35816, 35820,
+     35824, 35828, 35832, 35836, 35840, 35844, 35848, 35852, 35856, 35860,
+     35864, 35868, 35872, 35876, 35880, 35884, 35888, 35892, 35896, 35900,
+     35904, 35908, 35912, 35916, 35920, 35924, 35928, 35932, 35936, 35940,
+     35944, 35948, 35952, 35956, 35960, 35964, 35968, 35972, 35976, 35980,
+     35984, 35988, 35992, 35996, 36000, 36004, 36008, 36012, 36016, 36020,
+     36024, 36028, 36032, 36036, 36040, 36044, 36048, 36052, 36056, 36060,
+     36064, 36068, 36072, 36076, 36080, 36084, 36088, 36092, 36096, 36100,
+     36104, 36108, 36112, 36116, 36120, 36124, 36128, 36132, 36136, 36140,
+     36144, 36148, 36152, 36156, 36160, 36164, 36168, 36172, 36176, 36180,
+     36184, 36188, 36192, 36196, 36200, 36204, 36208, 36212, 36216, 36220,
+     36224, 36228, 36232, 36236, 36240, 36244, 36248, 36252, 36256, 36260,
+     36264, 36268, 36272, 36276, 36280, 36284, 36288},
+    {36288, 36292, 36296, 36300, 36304, 36308, 36312, 36316, 36320, 36324,
+     36328, 36332, 36336, 36340, 36344, 36348, 36352, 36356, 36360, 36364,
+     36368, 36372, 36376, 36380, 36384, 36388, 36392, 36396, 36400, 36404,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408, 36408,
+     36408, 36408, 36408, 36408, 36408, 36408, 36408}};
+const char32_t decomposition_data[9102] = {
+    0,      32,     32,     776,    97,     32,     772,    50,     51,
+    32,     769,    956,    32,     807,    49,     111,    49,     8260,
+    52,     49,     8260,   50,     51,     8260,   52,     65,     768,
+    65,     769,    65,     770,    65,     771,    65,     776,    65,
+    778,    67,     807,    69,     768,    69,     769,    69,     770,
+    69,     776,    73,     768,    73,     769,    73,     770,    73,
+    776,    78,     771,    79,     768,    79,     769,    79,     770,
+    79,     771,    79,     776,    85,     768,    85,     769,    85,
+    770,    85,     776,    89,     769,    97,     768,    97,     769,
+    97,     770,    97,     771,    97,     776,    97,     778,    99,
+    807,    101,    768,    101,    769,    101,    770,    101,    776,
+    105,    768,    105,    769,    105,    770,    105,    776,    110,
+    771,    111,    768,    111,    769,    111,    770,    111,    771,
+    111,    776,    117,    768,    117,    769,    117,    770,    117,
+    776,    121,    769,    121,    776,    65,     772,    97,     772,
+    65,     774,    97,     774,    65,     808,    97,     808,    67,
+    769,    99,     769,    67,     770,    99,     770,    67,     775,
+    99,     775,    67,     780,    99,     780,    68,     780,    100,
+    780,    69,     772,    101,    772,    69,     774,    101,    774,
+    69,     775,    101,    775,    69,     808,    101,    808,    69,
+    780,    101,    780,    71,     770,    103,    770,    71,     774,
+    103,    774,    71,     775,    103,    775,    71,     807,    103,
+    807,    72,     770,    104,    770,    73,     771,    105,    771,
+    73,     772,    105,    772,    73,     774,    105,    774,    73,
+    808,    105,    808,    73,     775,    73,     74,     105,    106,
+    74,     770,    106,    770,    75,     807,    107,    807,    76,
+    769,    108,    769,    76,     807,    108,    807,    76,     780,
+    108,    780,    76,     183,    108,    183,    78,     769,    110,
+    769,    78,     807,    110,    807,    78,     780,    110,    780,
+    700,    110,    79,     772,    111,    772,    79,     774,    111,
+    774,    79,     779,    111,    779,    82,     769,    114,    769,
+    82,     807,    114,    807,    82,     780,    114,    780,    83,
+    769,    115,    769,    83,     770,    115,    770,    83,     807,
+    115,    807,    83,     780,    115,    780,    84,     807,    116,
+    807,    84,     780,    116,    780,    85,     771,    117,    771,
+    85,     772,    117,    772,    85,     774,    117,    774,    85,
+    778,    117,    778,    85,     779,    117,    779,    85,     808,
+    117,    808,    87,     770,    119,    770,    89,     770,    121,
+    770,    89,     776,    90,     769,    122,    769,    90,     775,
+    122,    775,    90,     780,    122,    780,    115,    79,     795,
+    111,    795,    85,     795,    117,    795,    68,     90,     780,
+    68,     122,    780,    100,    122,    780,    76,     74,     76,
+    106,    108,    106,    78,     74,     78,     106,    110,    106,
+    65,     780,    97,     780,    73,     780,    105,    780,    79,
+    780,    111,    780,    85,     780,    117,    780,    85,     776,
+    772,    117,    776,    772,    85,     776,    769,    117,    776,
+    769,    85,     776,    780,    117,    776,    780,    85,     776,
+    768,    117,    776,    768,    65,     776,    772,    97,     776,
+    772,    65,     775,    772,    97,     775,    772,    198,    772,
+    230,    772,    71,     780,    103,    780,    75,     780,    107,
+    780,    79,     808,    111,    808,    79,     808,    772,    111,
+    808,    772,    439,    780,    658,    780,    106,    780,    68,
+    90,     68,     122,    100,    122,    71,     769,    103,    769,
+    78,     768,    110,    768,    65,     778,    769,    97,     778,
+    769,    198,    769,    230,    769,    216,    769,    248,    769,
+    65,     783,    97,     783,    65,     785,    97,     785,    69,
+    783,    101,    783,    69,     785,    101,    785,    73,     783,
+    105,    783,    73,     785,    105,    785,    79,     783,    111,
+    783,    79,     785,    111,    785,    82,     783,    114,    783,
+    82,     785,    114,    785,    85,     783,    117,    783,    85,
+    785,    117,    785,    83,     806,    115,    806,    84,     806,
+    116,    806,    72,     780,    104,    780,    65,     775,    97,
+    775,    69,     807,    101,    807,    79,     776,    772,    111,
+    776,    772,    79,     771,    772,    111,    771,    772,    79,
+    775,    111,    775,    79,     775,    772,    111,    775,    772,
+    89,     772,    121,    772,    104,    614,    106,    114,    633,
+    635,    641,    119,    121,    32,     774,    32,     775,    32,
+    778,    32,     808,    32,     771,    32,     779,    611,    108,
+    115,    120,    661,    768,    769,    787,    776,    769,    697,
+    32,     837,    59,     32,     769,    168,    769,    913,    769,
+    183,    917,    769,    919,    769,    921,    769,    927,    769,
+    933,    769,    937,    769,    953,    776,    769,    921,    776,
+    933,    776,    945,    769,    949,    769,    951,    769,    953,
+    769,    965,    776,    769,    953,    776,    965,    776,    959,
+    769,    965,    769,    969,    769,    946,    952,    933,    978,
+    769,    978,    776,    966,    960,    954,    961,    962,    920,
+    949,    931,    1045,   768,    1045,   776,    1043,   769,    1030,
+    776,    1050,   769,    1048,   768,    1059,   774,    1048,   774,
+    1080,   774,    1077,   768,    1077,   776,    1075,   769,    1110,
+    776,    1082,   769,    1080,   768,    1091,   774,    1140,   783,
+    1141,   783,    1046,   774,    1078,   774,    1040,   774,    1072,
+    774,    1040,   776,    1072,   776,    1045,   774,    1077,   774,
+    1240,   776,    1241,   776,    1046,   776,    1078,   776,    1047,
+    776,    1079,   776,    1048,   772,    1080,   772,    1048,   776,
+    1080,   776,    1054,   776,    1086,   776,    1256,   776,    1257,
+    776,    1069,   776,    1101,   776,    1059,   772,    1091,   772,
+    1059,   776,    1091,   776,    1059,   779,    1091,   779,    1063,
+    776,    1095,   776,    1067,   776,    1099,   776,    1381,   1410,
+    1575,   1619,   1575,   1620,   1608,   1620,   1575,   1621,   1610,
+    1620,   1575,   1652,   1608,   1652,   1735,   1652,   1610,   1652,
+    1749,   1620,   1729,   1620,   1746,   1620,   2344,   2364,   2352,
+    2364,   2355,   2364,   2325,   2364,   2326,   2364,   2327,   2364,
+    2332,   2364,   2337,   2364,   2338,   2364,   2347,   2364,   2351,
+    2364,   2503,   2494,   2503,   2519,   2465,   2492,   2466,   2492,
+    2479,   2492,   2610,   2620,   2616,   2620,   2582,   2620,   2583,
+    2620,   2588,   2620,   2603,   2620,   2887,   2902,   2887,   2878,
+    2887,   2903,   2849,   2876,   2850,   2876,   2962,   3031,   3014,
+    3006,   3015,   3006,   3014,   3031,   3142,   3158,   3263,   3285,
+    3270,   3285,   3270,   3286,   3270,   3266,   3270,   3266,   3285,
+    3398,   3390,   3399,   3390,   3398,   3415,   3545,   3530,   3545,
+    3535,   3545,   3535,   3530,   3545,   3551,   3661,   3634,   3789,
+    3762,   3755,   3737,   3755,   3745,   3851,   3906,   4023,   3916,
+    4023,   3921,   4023,   3926,   4023,   3931,   4023,   3904,   4021,
+    3953,   3954,   3953,   3956,   4018,   3968,   4018,   3953,   3968,
+    4019,   3968,   4019,   3953,   3968,   3953,   3968,   3986,   4023,
+    3996,   4023,   4001,   4023,   4006,   4023,   4011,   4023,   3984,
+    4021,   4133,   4142,   4316,   6917,   6965,   6919,   6965,   6921,
+    6965,   6923,   6965,   6925,   6965,   6929,   6965,   6970,   6965,
+    6972,   6965,   6974,   6965,   6975,   6965,   6978,   6965,   65,
+    198,    66,     68,     69,     398,    71,     72,     73,     74,
+    75,     76,     77,     78,     79,     546,    80,     82,     84,
+    85,     87,     97,     592,    593,    7426,   98,     100,    101,
+    601,    603,    604,    103,    107,    109,    331,    111,    596,
+    7446,   7447,   112,    116,    117,    7453,   623,    118,    7461,
+    946,    947,    948,    966,    967,    105,    114,    117,    118,
+    946,    947,    961,    966,    967,    1085,   594,    99,     597,
+    240,    604,    102,    607,    609,    613,    616,    617,    618,
+    7547,   669,    621,    7557,   671,    625,    624,    626,    627,
+    628,    629,    632,    642,    643,    427,    649,    650,    7452,
+    651,    652,    122,    656,    657,    658,    952,    65,     805,
+    97,     805,    66,     775,    98,     775,    66,     803,    98,
+    803,    66,     817,    98,     817,    67,     807,    769,    99,
+    807,    769,    68,     775,    100,    775,    68,     803,    100,
+    803,    68,     817,    100,    817,    68,     807,    100,    807,
+    68,     813,    100,    813,    69,     772,    768,    101,    772,
+    768,    69,     772,    769,    101,    772,    769,    69,     813,
+    101,    813,    69,     816,    101,    816,    69,     807,    774,
+    101,    807,    774,    70,     775,    102,    775,    71,     772,
+    103,    772,    72,     775,    104,    775,    72,     803,    104,
+    803,    72,     776,    104,    776,    72,     807,    104,    807,
+    72,     814,    104,    814,    73,     816,    105,    816,    73,
+    776,    769,    105,    776,    769,    75,     769,    107,    769,
+    75,     803,    107,    803,    75,     817,    107,    817,    76,
+    803,    108,    803,    76,     803,    772,    108,    803,    772,
+    76,     817,    108,    817,    76,     813,    108,    813,    77,
+    769,    109,    769,    77,     775,    109,    775,    77,     803,
+    109,    803,    78,     775,    110,    775,    78,     803,    110,
+    803,    78,     817,    110,    817,    78,     813,    110,    813,
+    79,     771,    769,    111,    771,    769,    79,     771,    776,
+    111,    771,    776,    79,     772,    768,    111,    772,    768,
+    79,     772,    769,    111,    772,    769,    80,     769,    112,
+    769,    80,     775,    112,    775,    82,     775,    114,    775,
+    82,     803,    114,    803,    82,     803,    772,    114,    803,
+    772,    82,     817,    114,    817,    83,     775,    115,    775,
+    83,     803,    115,    803,    83,     769,    775,    115,    769,
+    775,    83,     780,    775,    115,    780,    775,    83,     803,
+    775,    115,    803,    775,    84,     775,    116,    775,    84,
+    803,    116,    803,    84,     817,    116,    817,    84,     813,
+    116,    813,    85,     804,    117,    804,    85,     816,    117,
+    816,    85,     813,    117,    813,    85,     771,    769,    117,
+    771,    769,    85,     772,    776,    117,    772,    776,    86,
+    771,    118,    771,    86,     803,    118,    803,    87,     768,
+    119,    768,    87,     769,    119,    769,    87,     776,    119,
+    776,    87,     775,    119,    775,    87,     803,    119,    803,
+    88,     775,    120,    775,    88,     776,    120,    776,    89,
+    775,    121,    775,    90,     770,    122,    770,    90,     803,
+    122,    803,    90,     817,    122,    817,    104,    817,    116,
+    776,    119,    778,    121,    778,    97,     702,    383,    775,
+    65,     803,    97,     803,    65,     777,    97,     777,    65,
+    770,    769,    97,     770,    769,    65,     770,    768,    97,
+    770,    768,    65,     770,    777,    97,     770,    777,    65,
+    770,    771,    97,     770,    771,    65,     803,    770,    97,
+    803,    770,    65,     774,    769,    97,     774,    769,    65,
+    774,    768,    97,     774,    768,    65,     774,    777,    97,
+    774,    777,    65,     774,    771,    97,     774,    771,    65,
+    803,    774,    97,     803,    774,    69,     803,    101,    803,
+    69,     777,    101,    777,    69,     771,    101,    771,    69,
+    770,    769,    101,    770,    769,    69,     770,    768,    101,
+    770,    768,    69,     770,    777,    101,    770,    777,    69,
+    770,    771,    101,    770,    771,    69,     803,    770,    101,
+    803,    770,    73,     777,    105,    777,    73,     803,    105,
+    803,    79,     803,    111,    803,    79,     777,    111,    777,
+    79,     770,    769,    111,    770,    769,    79,     770,    768,
+    111,    770,    768,    79,     770,    777,    111,    770,    777,
+    79,     770,    771,    111,    770,    771,    79,     803,    770,
+    111,    803,    770,    79,     795,    769,    111,    795,    769,
+    79,     795,    768,    111,    795,    768,    79,     795,    777,
+    111,    795,    777,    79,     795,    771,    111,    795,    771,
+    79,     795,    803,    111,    795,    803,    85,     803,    117,
+    803,    85,     777,    117,    777,    85,     795,    769,    117,
+    795,    769,    85,     795,    768,    117,    795,    768,    85,
+    795,    777,    117,    795,    777,    85,     795,    771,    117,
+    795,    771,    85,     795,    803,    117,    795,    803,    89,
+    768,    121,    768,    89,     803,    121,    803,    89,     777,
+    121,    777,    89,     771,    121,    771,    945,    787,    945,
+    788,    945,    787,    768,    945,    788,    768,    945,    787,
+    769,    945,    788,    769,    945,    787,    834,    945,    788,
+    834,    913,    787,    913,    788,    913,    787,    768,    913,
+    788,    768,    913,    787,    769,    913,    788,    769,    913,
+    787,    834,    913,    788,    834,    949,    787,    949,    788,
+    949,    787,    768,    949,    788,    768,    949,    787,    769,
+    949,    788,    769,    917,    787,    917,    788,    917,    787,
+    768,    917,    788,    768,    917,    787,    769,    917,    788,
+    769,    951,    787,    951,    788,    951,    787,    768,    951,
+    788,    768,    951,    787,    769,    951,    788,    769,    951,
+    787,    834,    951,    788,    834,    919,    787,    919,    788,
+    919,    787,    768,    919,    788,    768,    919,    787,    769,
+    919,    788,    769,    919,    787,    834,    919,    788,    834,
+    953,    787,    953,    788,    953,    787,    768,    953,    788,
+    768,    953,    787,    769,    953,    788,    769,    953,    787,
+    834,    953,    788,    834,    921,    787,    921,    788,    921,
+    787,    768,    921,    788,    768,    921,    787,    769,    921,
+    788,    769,    921,    787,    834,    921,    788,    834,    959,
+    787,    959,    788,    959,    787,    768,    959,    788,    768,
+    959,    787,    769,    959,    788,    769,    927,    787,    927,
+    788,    927,    787,    768,    927,    788,    768,    927,    787,
+    769,    927,    788,    769,    965,    787,    965,    788,    965,
+    787,    768,    965,    788,    768,    965,    787,    769,    965,
+    788,    769,    965,    787,    834,    965,    788,    834,    933,
+    788,    933,    788,    768,    933,    788,    769,    933,    788,
+    834,    969,    787,    969,    788,    969,    787,    768,    969,
+    788,    768,    969,    787,    769,    969,    788,    769,    969,
+    787,    834,    969,    788,    834,    937,    787,    937,    788,
+    937,    787,    768,    937,    788,    768,    937,    787,    769,
+    937,    788,    769,    937,    787,    834,    937,    788,    834,
+    945,    768,    945,    769,    949,    768,    949,    769,    951,
+    768,    951,    769,    953,    768,    953,    769,    959,    768,
+    959,    769,    965,    768,    965,    769,    969,    768,    969,
+    769,    945,    787,    837,    945,    788,    837,    945,    787,
+    768,    837,    945,    788,    768,    837,    945,    787,    769,
+    837,    945,    788,    769,    837,    945,    787,    834,    837,
+    945,    788,    834,    837,    913,    787,    837,    913,    788,
+    837,    913,    787,    768,    837,    913,    788,    768,    837,
+    913,    787,    769,    837,    913,    788,    769,    837,    913,
+    787,    834,    837,    913,    788,    834,    837,    951,    787,
+    837,    951,    788,    837,    951,    787,    768,    837,    951,
+    788,    768,    837,    951,    787,    769,    837,    951,    788,
+    769,    837,    951,    787,    834,    837,    951,    788,    834,
+    837,    919,    787,    837,    919,    788,    837,    919,    787,
+    768,    837,    919,    788,    768,    837,    919,    787,    769,
+    837,    919,    788,    769,    837,    919,    787,    834,    837,
+    919,    788,    834,    837,    969,    787,    837,    969,    788,
+    837,    969,    787,    768,    837,    969,    788,    768,    837,
+    969,    787,    769,    837,    969,    788,    769,    837,    969,
+    787,    834,    837,    969,    788,    834,    837,    937,    787,
+    837,    937,    788,    837,    937,    787,    768,    837,    937,
+    788,    768,    837,    937,    787,    769,    837,    937,    788,
+    769,    837,    937,    787,    834,    837,    937,    788,    834,
+    837,    945,    774,    945,    772,    945,    768,    837,    945,
+    837,    945,    769,    837,    945,    834,    945,    834,    837,
+    913,    774,    913,    772,    913,    768,    913,    769,    913,
+    837,    32,     787,    953,    32,     787,    32,     834,    168,
+    834,    951,    768,    837,    951,    837,    951,    769,    837,
+    951,    834,    951,    834,    837,    917,    768,    917,    769,
+    919,    768,    919,    769,    919,    837,    8127,   768,    8127,
+    769,    8127,   834,    953,    774,    953,    772,    953,    776,
+    768,    953,    776,    769,    953,    834,    953,    776,    834,
+    921,    774,    921,    772,    921,    768,    921,    769,    8190,
+    768,    8190,   769,    8190,   834,    965,    774,    965,    772,
+    965,    776,    768,    965,    776,    769,    961,    787,    961,
+    788,    965,    834,    965,    776,    834,    933,    774,    933,
+    772,    933,    768,    933,    769,    929,    788,    168,    768,
+    168,    769,    96,     969,    768,    837,    969,    837,    969,
+    769,    837,    969,    834,    969,    834,    837,    927,    768,
+    927,    769,    937,    768,    937,    769,    937,    837,    180,
+    32,     788,    8194,   8195,   32,     32,     32,     32,     32,
+    32,     32,     32,     32,     8208,   32,     819,    46,     46,
+    46,     46,     46,     46,     32,     8242,   8242,   8242,   8242,
+    8242,   8245,   8245,   8245,   8245,   8245,   33,     33,     32,
+    773,    63,     63,     63,     33,     33,     63,     8242,   8242,
+    8242,   8242,   32,     48,     105,    52,     53,     54,     55,
+    56,     57,     43,     8722,   61,     40,     41,     110,    48,
+    49,     50,     51,     52,     53,     54,     55,     56,     57,
+    43,     8722,   61,     40,     41,     97,     101,    111,    120,
+    601,    104,    107,    108,    109,    110,    112,    115,    116,
+    82,     115,    97,     47,     99,     97,     47,     115,    67,
+    176,    67,     99,     47,     111,    99,     47,     117,    400,
+    176,    70,     103,    72,     72,     72,     104,    295,    73,
+    73,     76,     108,    78,     78,     111,    80,     81,     82,
+    82,     82,     83,     77,     84,     69,     76,     84,     77,
+    90,     937,    90,     75,     65,     778,    66,     67,     101,
+    69,     70,     77,     111,    1488,   1489,   1490,   1491,   105,
+    70,     65,     88,     960,    947,    915,    928,    8721,   68,
+    100,    101,    105,    106,    49,     8260,   55,     49,     8260,
+    57,     49,     8260,   49,     48,     49,     8260,   51,     50,
+    8260,   51,     49,     8260,   53,     50,     8260,   53,     51,
+    8260,   53,     52,     8260,   53,     49,     8260,   54,     53,
+    8260,   54,     49,     8260,   56,     51,     8260,   56,     53,
+    8260,   56,     55,     8260,   56,     49,     8260,   73,     73,
+    73,     73,     73,     73,     73,     86,     86,     86,     73,
+    86,     73,     73,     86,     73,     73,     73,     73,     88,
+    88,     88,     73,     88,     73,     73,     76,     67,     68,
+    77,     105,    105,    105,    105,    105,    105,    105,    118,
+    118,    118,    105,    118,    105,    105,    118,    105,    105,
+    105,    105,    120,    120,    120,    105,    120,    105,    105,
+    108,    99,     100,    109,    48,     8260,   51,     8592,   824,
+    8594,   824,    8596,   824,    8656,   824,    8660,   824,    8658,
+    824,    8707,   824,    8712,   824,    8715,   824,    8739,   824,
+    8741,   824,    8747,   8747,   8747,   8747,   8747,   8750,   8750,
+    8750,   8750,   8750,   8764,   824,    8771,   824,    8773,   824,
+    8776,   824,    61,     824,    8801,   824,    8781,   824,    60,
+    824,    62,     824,    8804,   824,    8805,   824,    8818,   824,
+    8819,   824,    8822,   824,    8823,   824,    8826,   824,    8827,
+    824,    8834,   824,    8835,   824,    8838,   824,    8839,   824,
+    8866,   824,    8872,   824,    8873,   824,    8875,   824,    8828,
+    824,    8829,   824,    8849,   824,    8850,   824,    8882,   824,
+    8883,   824,    8884,   824,    8885,   824,    12296,  12297,  49,
+    50,     51,     52,     53,     54,     55,     56,     57,     49,
+    48,     49,     49,     49,     50,     49,     51,     49,     52,
+    49,     53,     49,     54,     49,     55,     49,     56,     49,
+    57,     50,     48,     40,     49,     41,     40,     50,     41,
+    40,     51,     41,     40,     52,     41,     40,     53,     41,
+    40,     54,     41,     40,     55,     41,     40,     56,     41,
+    40,     57,     41,     40,     49,     48,     41,     40,     49,
+    49,     41,     40,     49,     50,     41,     40,     49,     51,
+    41,     40,     49,     52,     41,     40,     49,     53,     41,
+    40,     49,     54,     41,     40,     49,     55,     41,     40,
+    49,     56,     41,     40,     49,     57,     41,     40,     50,
+    48,     41,     49,     46,     50,     46,     51,     46,     52,
+    46,     53,     46,     54,     46,     55,     46,     56,     46,
+    57,     46,     49,     48,     46,     49,     49,     46,     49,
+    50,     46,     49,     51,     46,     49,     52,     46,     49,
+    53,     46,     49,     54,     46,     49,     55,     46,     49,
+    56,     46,     49,     57,     46,     50,     48,     46,     40,
+    97,     41,     40,     98,     41,     40,     99,     41,     40,
+    100,    41,     40,     101,    41,     40,     102,    41,     40,
+    103,    41,     40,     104,    41,     40,     105,    41,     40,
+    106,    41,     40,     107,    41,     40,     108,    41,     40,
+    109,    41,     40,     110,    41,     40,     111,    41,     40,
+    112,    41,     40,     113,    41,     40,     114,    41,     40,
+    115,    41,     40,     116,    41,     40,     117,    41,     40,
+    118,    41,     40,     119,    41,     40,     120,    41,     40,
+    121,    41,     40,     122,    41,     65,     66,     67,     68,
+    69,     70,     71,     72,     73,     74,     75,     76,     77,
+    78,     79,     80,     81,     82,     83,     84,     85,     86,
+    87,     88,     89,     90,     97,     98,     99,     100,    101,
+    102,    103,    104,    105,    106,    107,    108,    109,    110,
+    111,    112,    113,    114,    115,    116,    117,    118,    119,
+    120,    121,    122,    48,     8747,   8747,   8747,   8747,   58,
+    58,     61,     61,     61,     61,     61,     61,     10973,  824,
+    106,    86,     11617,  27597,  40863,  19968,  20008,  20022,  20031,
+    20057,  20101,  20108,  20128,  20154,  20799,  20837,  20843,  20866,
+    20886,  20907,  20960,  20981,  20992,  21147,  21241,  21269,  21274,
+    21304,  21313,  21340,  21353,  21378,  21430,  21448,  21475,  22231,
+    22303,  22763,  22786,  22794,  22805,  22823,  22899,  23376,  23424,
+    23544,  23567,  23586,  23608,  23662,  23665,  24027,  24037,  24049,
+    24062,  24178,  24186,  24191,  24308,  24318,  24331,  24339,  24400,
+    24417,  24435,  24515,  25096,  25142,  25163,  25903,  25908,  25991,
+    26007,  26020,  26041,  26080,  26085,  26352,  26376,  26408,  27424,
+    27490,  27513,  27571,  27595,  27604,  27611,  27663,  27668,  27700,
+    28779,  29226,  29238,  29243,  29247,  29255,  29273,  29275,  29356,
+    29572,  29577,  29916,  29926,  29976,  29983,  29992,  30000,  30091,
+    30098,  30326,  30333,  30382,  30399,  30446,  30683,  30690,  30707,
+    31034,  31160,  31166,  31348,  31435,  31481,  31859,  31992,  32566,
+    32593,  32650,  32701,  32769,  32780,  32786,  32819,  32895,  32905,
+    33251,  33258,  33267,  33276,  33292,  33307,  33311,  33390,  33394,
+    33400,  34381,  34411,  34880,  34892,  34915,  35198,  35211,  35282,
+    35328,  35895,  35910,  35925,  35960,  35997,  36196,  36208,  36275,
+    36523,  36554,  36763,  36784,  36789,  37009,  37193,  37318,  37324,
+    37329,  38263,  38272,  38428,  38582,  38585,  38632,  38737,  38750,
+    38754,  38761,  38859,  38893,  38899,  38913,  39080,  39131,  39135,
+    39318,  39321,  39340,  39592,  39640,  39647,  39717,  39727,  39730,
+    39740,  39770,  40165,  40565,  40575,  40613,  40635,  40643,  40653,
+    40657,  40697,  40701,  40718,  40723,  40736,  40763,  40778,  40786,
+    40845,  40860,  40864,  32,     12306,  21313,  21316,  21317,  12363,
+    12441,  12365,  12441,  12367,  12441,  12369,  12441,  12371,  12441,
+    12373,  12441,  12375,  12441,  12377,  12441,  12379,  12441,  12381,
+    12441,  12383,  12441,  12385,  12441,  12388,  12441,  12390,  12441,
+    12392,  12441,  12399,  12441,  12399,  12442,  12402,  12441,  12402,
+    12442,  12405,  12441,  12405,  12442,  12408,  12441,  12408,  12442,
+    12411,  12441,  12411,  12442,  12358,  12441,  32,     12441,  32,
+    12442,  12445,  12441,  12424,  12426,  12459,  12441,  12461,  12441,
+    12463,  12441,  12465,  12441,  12467,  12441,  12469,  12441,  12471,
+    12441,  12473,  12441,  12475,  12441,  12477,  12441,  12479,  12441,
+    12481,  12441,  12484,  12441,  12486,  12441,  12488,  12441,  12495,
+    12441,  12495,  12442,  12498,  12441,  12498,  12442,  12501,  12441,
+    12501,  12442,  12504,  12441,  12504,  12442,  12507,  12441,  12507,
+    12442,  12454,  12441,  12527,  12441,  12528,  12441,  12529,  12441,
+    12530,  12441,  12541,  12441,  12467,  12488,  4352,   4353,   4522,
+    4354,   4524,   4525,   4355,   4356,   4357,   4528,   4529,   4530,
+    4531,   4532,   4533,   4378,   4358,   4359,   4360,   4385,   4361,
+    4362,   4363,   4364,   4365,   4366,   4367,   4368,   4369,   4370,
+    4449,   4450,   4451,   4452,   4453,   4454,   4455,   4456,   4457,
+    4458,   4459,   4460,   4461,   4462,   4463,   4464,   4465,   4466,
+    4467,   4468,   4469,   4448,   4372,   4373,   4551,   4552,   4556,
+    4558,   4563,   4567,   4569,   4380,   4573,   4575,   4381,   4382,
+    4384,   4386,   4387,   4391,   4393,   4395,   4396,   4397,   4398,
+    4399,   4402,   4406,   4416,   4423,   4428,   4593,   4594,   4439,
+    4440,   4441,   4484,   4485,   4488,   4497,   4498,   4500,   4510,
+    4513,   19968,  20108,  19977,  22235,  19978,  20013,  19979,  30002,
+    20057,  19993,  19969,  22825,  22320,  20154,  40,     4352,   41,
+    40,     4354,   41,     40,     4355,   41,     40,     4357,   41,
+    40,     4358,   41,     40,     4359,   41,     40,     4361,   41,
+    40,     4363,   41,     40,     4364,   41,     40,     4366,   41,
+    40,     4367,   41,     40,     4368,   41,     40,     4369,   41,
+    40,     4370,   41,     40,     4352,   4449,   41,     40,     4354,
+    4449,   41,     40,     4355,   4449,   41,     40,     4357,   4449,
+    41,     40,     4358,   4449,   41,     40,     4359,   4449,   41,
+    40,     4361,   4449,   41,     40,     4363,   4449,   41,     40,
+    4364,   4449,   41,     40,     4366,   4449,   41,     40,     4367,
+    4449,   41,     40,     4368,   4449,   41,     40,     4369,   4449,
+    41,     40,     4370,   4449,   41,     40,     4364,   4462,   41,
+    40,     4363,   4457,   4364,   4453,   4523,   41,     40,     4363,
+    4457,   4370,   4462,   41,     40,     19968,  41,     40,     20108,
+    41,     40,     19977,  41,     40,     22235,  41,     40,     20116,
+    41,     40,     20845,  41,     40,     19971,  41,     40,     20843,
+    41,     40,     20061,  41,     40,     21313,  41,     40,     26376,
+    41,     40,     28779,  41,     40,     27700,  41,     40,     26408,
+    41,     40,     37329,  41,     40,     22303,  41,     40,     26085,
+    41,     40,     26666,  41,     40,     26377,  41,     40,     31038,
+    41,     40,     21517,  41,     40,     29305,  41,     40,     36001,
+    41,     40,     31069,  41,     40,     21172,  41,     40,     20195,
+    41,     40,     21628,  41,     40,     23398,  41,     40,     30435,
+    41,     40,     20225,  41,     40,     36039,  41,     40,     21332,
+    41,     40,     31085,  41,     40,     20241,  41,     40,     33258,
+    41,     40,     33267,  41,     21839,  24188,  25991,  31631,  80,
+    84,     69,     50,     49,     50,     50,     50,     51,     50,
+    52,     50,     53,     50,     54,     50,     55,     50,     56,
+    50,     57,     51,     48,     51,     49,     51,     50,     51,
+    51,     51,     52,     51,     53,     4352,   4354,   4355,   4357,
+    4358,   4359,   4361,   4363,   4364,   4366,   4367,   4368,   4369,
+    4370,   4352,   4449,   4354,   4449,   4355,   4449,   4357,   4449,
+    4358,   4449,   4359,   4449,   4361,   4449,   4363,   4449,   4364,
+    4449,   4366,   4449,   4367,   4449,   4368,   4449,   4369,   4449,
+    4370,   4449,   4366,   4449,   4535,   4352,   4457,   4364,   4462,
+    4363,   4468,   4363,   4462,   19968,  20108,  19977,  22235,  20116,
+    20845,  19971,  20843,  20061,  21313,  26376,  28779,  27700,  26408,
+    37329,  22303,  26085,  26666,  26377,  31038,  21517,  29305,  36001,
+    31069,  21172,  31192,  30007,  22899,  36969,  20778,  21360,  27880,
+    38917,  20241,  20889,  27491,  19978,  20013,  19979,  24038,  21491,
+    21307,  23447,  23398,  30435,  20225,  36039,  21332,  22812,  51,
+    54,     51,     55,     51,     56,     51,     57,     52,     48,
+    52,     49,     52,     50,     52,     51,     52,     52,     52,
+    53,     52,     54,     52,     55,     52,     56,     52,     57,
+    53,     48,     49,     26376,  50,     26376,  51,     26376,  52,
+    26376,  53,     26376,  54,     26376,  55,     26376,  56,     26376,
+    57,     26376,  49,     48,     26376,  49,     49,     26376,  49,
+    50,     26376,  72,     103,    101,    114,    103,    101,    86,
+    76,     84,     68,     12450,  12452,  12454,  12456,  12458,  12459,
+    12461,  12463,  12465,  12467,  12469,  12471,  12473,  12475,  12477,
+    12479,  12481,  12484,  12486,  12488,  12490,  12491,  12492,  12493,
+    12494,  12495,  12498,  12501,  12504,  12507,  12510,  12511,  12512,
+    12513,  12514,  12516,  12518,  12520,  12521,  12522,  12523,  12524,
+    12525,  12527,  12528,  12529,  12530,  20196,  21644,  12450,  12495,
+    12442,  12540,  12488,  12450,  12523,  12501,  12449,  12450,  12531,
+    12504,  12442,  12450,  12450,  12540,  12523,  12452,  12491,  12531,
+    12463,  12441,  12452,  12531,  12481,  12454,  12457,  12531,  12456,
+    12473,  12463,  12540,  12488,  12441,  12456,  12540,  12459,  12540,
+    12458,  12531,  12473,  12458,  12540,  12512,  12459,  12452,  12522,
+    12459,  12521,  12483,  12488,  12459,  12525,  12522,  12540,  12459,
+    12441,  12525,  12531,  12459,  12441,  12531,  12510,  12461,  12441,
+    12459,  12441,  12461,  12441,  12491,  12540,  12461,  12517,  12522,
+    12540,  12461,  12441,  12523,  12479,  12441,  12540,  12461,  12525,
+    12461,  12525,  12463,  12441,  12521,  12512,  12461,  12525,  12513,
+    12540,  12488,  12523,  12461,  12525,  12527,  12483,  12488,  12463,
+    12441,  12521,  12512,  12463,  12441,  12521,  12512,  12488,  12531,
+    12463,  12523,  12475,  12441,  12452,  12525,  12463,  12525,  12540,
+    12493,  12465,  12540,  12473,  12467,  12523,  12490,  12467,  12540,
+    12507,  12442,  12469,  12452,  12463,  12523,  12469,  12531,  12481,
+    12540,  12512,  12471,  12522,  12531,  12463,  12441,  12475,  12531,
+    12481,  12475,  12531,  12488,  12479,  12441,  12540,  12473,  12486,
+    12441,  12471,  12488,  12441,  12523,  12488,  12531,  12490,  12494,
+    12494,  12483,  12488,  12495,  12452,  12484,  12495,  12442,  12540,
+    12475,  12531,  12488,  12495,  12442,  12540,  12484,  12495,  12441,
+    12540,  12524,  12523,  12498,  12442,  12450,  12473,  12488,  12523,
+    12498,  12442,  12463,  12523,  12498,  12442,  12467,  12498,  12441,
+    12523,  12501,  12449,  12521,  12483,  12488,  12441,  12501,  12451,
+    12540,  12488,  12501,  12441,  12483,  12471,  12455,  12523,  12501,
+    12521,  12531,  12504,  12463,  12479,  12540,  12523,  12504,  12442,
+    12477,  12504,  12442,  12491,  12498,  12504,  12523,  12484,  12504,
+    12442,  12531,  12473,  12504,  12442,  12540,  12471,  12441,  12504,
+    12441,  12540,  12479,  12507,  12442,  12452,  12531,  12488,  12507,
+    12441,  12523,  12488,  12507,  12531,  12507,  12442,  12531,  12488,
+    12441,  12507,  12540,  12523,  12507,  12540,  12531,  12510,  12452,
+    12463,  12525,  12510,  12452,  12523,  12510,  12483,  12495,  12510,
+    12523,  12463,  12510,  12531,  12471,  12519,  12531,  12511,  12463,
+    12525,  12531,  12511,  12522,  12511,  12522,  12495,  12441,  12540,
+    12523,  12513,  12459,  12441,  12513,  12459,  12441,  12488,  12531,
+    12513,  12540,  12488,  12523,  12516,  12540,  12488,  12441,  12516,
+    12540,  12523,  12518,  12450,  12531,  12522,  12483,  12488,  12523,
+    12522,  12521,  12523,  12498,  12442,  12540,  12523,  12540,  12501,
+    12441,  12523,  12524,  12512,  12524,  12531,  12488,  12465,  12441,
+    12531,  12527,  12483,  12488,  48,     28857,  49,     28857,  50,
+    28857,  51,     28857,  52,     28857,  53,     28857,  54,     28857,
+    55,     28857,  56,     28857,  57,     28857,  49,     48,     28857,
+    49,     49,     28857,  49,     50,     28857,  49,     51,     28857,
+    49,     52,     28857,  49,     53,     28857,  49,     54,     28857,
+    49,     55,     28857,  49,     56,     28857,  49,     57,     28857,
+    50,     48,     28857,  50,     49,     28857,  50,     50,     28857,
+    50,     51,     28857,  50,     52,     28857,  104,    80,     97,
+    100,    97,     65,     85,     98,     97,     114,    111,    86,
+    112,    99,     100,    109,    100,    109,    50,     100,    109,
+    51,     73,     85,     24179,  25104,  26157,  21644,  22823,  27491,
+    26126,  27835,  26666,  24335,  20250,  31038,  112,    65,     110,
+    65,     956,    65,     109,    65,     107,    65,     75,     66,
+    77,     66,     71,     66,     99,     97,     108,    107,    99,
+    97,     108,    112,    70,     110,    70,     956,    70,     956,
+    103,    109,    103,    107,    103,    72,     122,    107,    72,
+    122,    77,     72,     122,    71,     72,     122,    84,     72,
+    122,    956,    108,    109,    108,    100,    108,    107,    108,
+    102,    109,    110,    109,    956,    109,    109,    109,    99,
+    109,    107,    109,    109,    109,    50,     99,     109,    50,
+    109,    50,     107,    109,    50,     109,    109,    51,     99,
+    109,    51,     109,    51,     107,    109,    51,     109,    8725,
+    115,    109,    8725,   115,    50,     80,     97,     107,    80,
+    97,     77,     80,     97,     71,     80,     97,     114,    97,
+    100,    114,    97,     100,    8725,   115,    114,    97,     100,
+    8725,   115,    50,     112,    115,    110,    115,    956,    115,
+    109,    115,    112,    86,     110,    86,     956,    86,     109,
+    86,     107,    86,     77,     86,     112,    87,     110,    87,
+    956,    87,     109,    87,     107,    87,     77,     87,     107,
+    937,    77,     937,    97,     46,     109,    46,     66,     113,
+    99,     99,     99,     100,    67,     8725,   107,    103,    67,
+    111,    46,     100,    66,     71,     121,    104,    97,     72,
+    80,     105,    110,    75,     75,     75,     77,     107,    116,
+    108,    109,    108,    110,    108,    111,    103,    108,    120,
+    109,    98,     109,    105,    108,    109,    111,    108,    80,
+    72,     112,    46,     109,    46,     80,     80,     77,     80,
+    82,     115,    114,    83,     118,    87,     98,     86,     8725,
+    109,    65,     8725,   109,    49,     26085,  50,     26085,  51,
+    26085,  52,     26085,  53,     26085,  54,     26085,  55,     26085,
+    56,     26085,  57,     26085,  49,     48,     26085,  49,     49,
+    26085,  49,     50,     26085,  49,     51,     26085,  49,     52,
+    26085,  49,     53,     26085,  49,     54,     26085,  49,     55,
+    26085,  49,     56,     26085,  49,     57,     26085,  50,     48,
+    26085,  50,     49,     26085,  50,     50,     26085,  50,     51,
+    26085,  50,     52,     26085,  50,     53,     26085,  50,     54,
+    26085,  50,     55,     26085,  50,     56,     26085,  50,     57,
+    26085,  51,     48,     26085,  51,     49,     26085,  103,    97,
+    108,    1098,   1100,   42863,  67,     70,     81,     294,    339,
+    42791,  43831,  619,    43858,  653,    35912,  26356,  36554,  36040,
+    28369,  20018,  21477,  40860,  40860,  22865,  37329,  21895,  22856,
+    25078,  30313,  32645,  34367,  34746,  35064,  37007,  27138,  27931,
+    28889,  29662,  33853,  37226,  39409,  20098,  21365,  27396,  29211,
+    34349,  40478,  23888,  28651,  34253,  35172,  25289,  33240,  34847,
+    24266,  26391,  28010,  29436,  37070,  20358,  20919,  21214,  25796,
+    27347,  29200,  30439,  32769,  34310,  34396,  36335,  38706,  39791,
+    40442,  30860,  31103,  32160,  33737,  37636,  40575,  35542,  22751,
+    24324,  31840,  32894,  29282,  30922,  36034,  38647,  22744,  23650,
+    27155,  28122,  28431,  32047,  32311,  38475,  21202,  32907,  20956,
+    20940,  31260,  32190,  33777,  38517,  35712,  25295,  27138,  35582,
+    20025,  23527,  24594,  29575,  30064,  21271,  30971,  20415,  24489,
+    19981,  27852,  25976,  32034,  21443,  22622,  30465,  33865,  35498,
+    27578,  36784,  27784,  25342,  33509,  25504,  30053,  20142,  20841,
+    20937,  26753,  31975,  33391,  35538,  37327,  21237,  21570,  22899,
+    24300,  26053,  28670,  31018,  38317,  39530,  40599,  40654,  21147,
+    26310,  27511,  36706,  24180,  24976,  25088,  25754,  28451,  29001,
+    29833,  31178,  32244,  32879,  36646,  34030,  36899,  37706,  21015,
+    21155,  21693,  28872,  35010,  35498,  24265,  24565,  25467,  27566,
+    31806,  29557,  20196,  22265,  23527,  23994,  24604,  29618,  29801,
+    32666,  32838,  37428,  38646,  38728,  38936,  20363,  31150,  37300,
+    38584,  24801,  20102,  20698,  23534,  23615,  26009,  27138,  29134,
+    30274,  34044,  36988,  40845,  26248,  38446,  21129,  26491,  26611,
+    27969,  28316,  29705,  30041,  30827,  32016,  39006,  20845,  25134,
+    38520,  20523,  23833,  28138,  36650,  24459,  24900,  26647,  29575,
+    38534,  21033,  21519,  23653,  26131,  26446,  26792,  27877,  29702,
+    30178,  32633,  35023,  35041,  37324,  38626,  21311,  28346,  21533,
+    29136,  29848,  34298,  38563,  40023,  40607,  26519,  28107,  33256,
+    31435,  31520,  31890,  29376,  28825,  35672,  20160,  33590,  21050,
+    20999,  24230,  25299,  31958,  23429,  27934,  26292,  36667,  34892,
+    38477,  35211,  24275,  20800,  21952,  22618,  26228,  20958,  29482,
+    30410,  31036,  31070,  31077,  31119,  38742,  31934,  32701,  34322,
+    35576,  36920,  37117,  39151,  39164,  39208,  40372,  37086,  38583,
+    20398,  20711,  20813,  21193,  21220,  21329,  21917,  22022,  22120,
+    22592,  22696,  23652,  23662,  24724,  24936,  24974,  25074,  25935,
+    26082,  26257,  26757,  28023,  28186,  28450,  29038,  29227,  29730,
+    30865,  31038,  31049,  31048,  31056,  31062,  31069,  31117,  31118,
+    31296,  31361,  31680,  32244,  32265,  32321,  32626,  32773,  33261,
+    33401,  33401,  33879,  35088,  35222,  35585,  35641,  36051,  36104,
+    36790,  36920,  38627,  38911,  38971,  24693,  148206, 33304,  20006,
+    20917,  20840,  20352,  20805,  20864,  21191,  21242,  21917,  21845,
+    21913,  21986,  22618,  22707,  22852,  22868,  23138,  23336,  24274,
+    24281,  24425,  24493,  24792,  24910,  24840,  24974,  24928,  25074,
+    25140,  25540,  25628,  25682,  25942,  26228,  26391,  26395,  26454,
+    27513,  27578,  27969,  28379,  28363,  28450,  28702,  29038,  30631,
+    29237,  29359,  29482,  29809,  29958,  30011,  30237,  30239,  30410,
+    30427,  30452,  30538,  30528,  30924,  31409,  31680,  31867,  32091,
+    32244,  32574,  32773,  33618,  33775,  34681,  35137,  35206,  35222,
+    35519,  35576,  35531,  35585,  35582,  35565,  35641,  35722,  36104,
+    36664,  36978,  37273,  37494,  38524,  38627,  38742,  38875,  38911,
+    38923,  38971,  39698,  40860,  141386, 141380, 144341, 15261,  16408,
+    16441,  152137, 154832, 163539, 40771,  40846,  102,    102,    102,
+    105,    102,    108,    102,    102,    105,    102,    102,    108,
+    115,    116,    115,    116,    1396,   1398,   1396,   1381,   1396,
+    1387,   1406,   1398,   1396,   1389,   1497,   1460,   1522,   1463,
+    1506,   1488,   1491,   1492,   1499,   1500,   1501,   1512,   1514,
+    43,     1513,   1473,   1513,   1474,   1513,   1468,   1473,   1513,
+    1468,   1474,   1488,   1463,   1488,   1464,   1488,   1468,   1489,
+    1468,   1490,   1468,   1491,   1468,   1492,   1468,   1493,   1468,
+    1494,   1468,   1496,   1468,   1497,   1468,   1498,   1468,   1499,
+    1468,   1500,   1468,   1502,   1468,   1504,   1468,   1505,   1468,
+    1507,   1468,   1508,   1468,   1510,   1468,   1511,   1468,   1512,
+    1468,   1513,   1468,   1514,   1468,   1493,   1465,   1489,   1471,
+    1499,   1471,   1508,   1471,   1488,   1500,   1649,   1649,   1659,
+    1659,   1659,   1659,   1662,   1662,   1662,   1662,   1664,   1664,
+    1664,   1664,   1658,   1658,   1658,   1658,   1663,   1663,   1663,
+    1663,   1657,   1657,   1657,   1657,   1700,   1700,   1700,   1700,
+    1702,   1702,   1702,   1702,   1668,   1668,   1668,   1668,   1667,
+    1667,   1667,   1667,   1670,   1670,   1670,   1670,   1671,   1671,
+    1671,   1671,   1677,   1677,   1676,   1676,   1678,   1678,   1672,
+    1672,   1688,   1688,   1681,   1681,   1705,   1705,   1705,   1705,
+    1711,   1711,   1711,   1711,   1715,   1715,   1715,   1715,   1713,
+    1713,   1713,   1713,   1722,   1722,   1723,   1723,   1723,   1723,
+    1749,   1620,   1749,   1620,   1729,   1729,   1729,   1729,   1726,
+    1726,   1726,   1726,   1746,   1746,   1746,   1620,   1746,   1620,
+    1709,   1709,   1709,   1709,   1735,   1735,   1734,   1734,   1736,
+    1736,   1735,   1652,   1739,   1739,   1733,   1733,   1737,   1737,
+    1744,   1744,   1744,   1744,   1609,   1609,   1610,   1620,   1575,
+    1610,   1620,   1575,   1610,   1620,   1749,   1610,   1620,   1749,
+    1610,   1620,   1608,   1610,   1620,   1608,   1610,   1620,   1735,
+    1610,   1620,   1735,   1610,   1620,   1734,   1610,   1620,   1734,
+    1610,   1620,   1736,   1610,   1620,   1736,   1610,   1620,   1744,
+    1610,   1620,   1744,   1610,   1620,   1744,   1610,   1620,   1609,
+    1610,   1620,   1609,   1610,   1620,   1609,   1740,   1740,   1740,
+    1740,   1610,   1620,   1580,   1610,   1620,   1581,   1610,   1620,
+    1605,   1610,   1620,   1609,   1610,   1620,   1610,   1576,   1580,
+    1576,   1581,   1576,   1582,   1576,   1605,   1576,   1609,   1576,
+    1610,   1578,   1580,   1578,   1581,   1578,   1582,   1578,   1605,
+    1578,   1609,   1578,   1610,   1579,   1580,   1579,   1605,   1579,
+    1609,   1579,   1610,   1580,   1581,   1580,   1605,   1581,   1580,
+    1581,   1605,   1582,   1580,   1582,   1581,   1582,   1605,   1587,
+    1580,   1587,   1581,   1587,   1582,   1587,   1605,   1589,   1581,
+    1589,   1605,   1590,   1580,   1590,   1581,   1590,   1582,   1590,
+    1605,   1591,   1581,   1591,   1605,   1592,   1605,   1593,   1580,
+    1593,   1605,   1594,   1580,   1594,   1605,   1601,   1580,   1601,
+    1581,   1601,   1582,   1601,   1605,   1601,   1609,   1601,   1610,
+    1602,   1581,   1602,   1605,   1602,   1609,   1602,   1610,   1603,
+    1575,   1603,   1580,   1603,   1581,   1603,   1582,   1603,   1604,
+    1603,   1605,   1603,   1609,   1603,   1610,   1604,   1580,   1604,
+    1581,   1604,   1582,   1604,   1605,   1604,   1609,   1604,   1610,
+    1605,   1580,   1605,   1581,   1605,   1582,   1605,   1605,   1605,
+    1609,   1605,   1610,   1606,   1580,   1606,   1581,   1606,   1582,
+    1606,   1605,   1606,   1609,   1606,   1610,   1607,   1580,   1607,
+    1605,   1607,   1609,   1607,   1610,   1610,   1580,   1610,   1581,
+    1610,   1582,   1610,   1605,   1610,   1609,   1610,   1610,   1584,
+    1648,   1585,   1648,   1609,   1648,   32,     1612,   1617,   32,
+    1613,   1617,   32,     1614,   1617,   32,     1615,   1617,   32,
+    1616,   1617,   32,     1617,   1648,   1610,   1620,   1585,   1610,
+    1620,   1586,   1610,   1620,   1605,   1610,   1620,   1606,   1610,
+    1620,   1609,   1610,   1620,   1610,   1576,   1585,   1576,   1586,
+    1576,   1605,   1576,   1606,   1576,   1609,   1576,   1610,   1578,
+    1585,   1578,   1586,   1578,   1605,   1578,   1606,   1578,   1609,
+    1578,   1610,   1579,   1585,   1579,   1586,   1579,   1605,   1579,
+    1606,   1579,   1609,   1579,   1610,   1601,   1609,   1601,   1610,
+    1602,   1609,   1602,   1610,   1603,   1575,   1603,   1604,   1603,
+    1605,   1603,   1609,   1603,   1610,   1604,   1605,   1604,   1609,
+    1604,   1610,   1605,   1575,   1605,   1605,   1606,   1585,   1606,
+    1586,   1606,   1605,   1606,   1606,   1606,   1609,   1606,   1610,
+    1609,   1648,   1610,   1585,   1610,   1586,   1610,   1605,   1610,
+    1606,   1610,   1609,   1610,   1610,   1610,   1620,   1580,   1610,
+    1620,   1581,   1610,   1620,   1582,   1610,   1620,   1605,   1610,
+    1620,   1607,   1576,   1580,   1576,   1581,   1576,   1582,   1576,
+    1605,   1576,   1607,   1578,   1580,   1578,   1581,   1578,   1582,
+    1578,   1605,   1578,   1607,   1579,   1605,   1580,   1581,   1580,
+    1605,   1581,   1580,   1581,   1605,   1582,   1580,   1582,   1605,
+    1587,   1580,   1587,   1581,   1587,   1582,   1587,   1605,   1589,
+    1581,   1589,   1582,   1589,   1605,   1590,   1580,   1590,   1581,
+    1590,   1582,   1590,   1605,   1591,   1581,   1592,   1605,   1593,
+    1580,   1593,   1605,   1594,   1580,   1594,   1605,   1601,   1580,
+    1601,   1581,   1601,   1582,   1601,   1605,   1602,   1581,   1602,
+    1605,   1603,   1580,   1603,   1581,   1603,   1582,   1603,   1604,
+    1603,   1605,   1604,   1580,   1604,   1581,   1604,   1582,   1604,
+    1605,   1604,   1607,   1605,   1580,   1605,   1581,   1605,   1582,
+    1605,   1605,   1606,   1580,   1606,   1581,   1606,   1582,   1606,
+    1605,   1606,   1607,   1607,   1580,   1607,   1605,   1607,   1648,
+    1610,   1580,   1610,   1581,   1610,   1582,   1610,   1605,   1610,
+    1607,   1610,   1620,   1605,   1610,   1620,   1607,   1576,   1605,
+    1576,   1607,   1578,   1605,   1578,   1607,   1579,   1605,   1579,
+    1607,   1587,   1605,   1587,   1607,   1588,   1605,   1588,   1607,
+    1603,   1604,   1603,   1605,   1604,   1605,   1606,   1605,   1606,
+    1607,   1610,   1605,   1610,   1607,   1600,   1614,   1617,   1600,
+    1615,   1617,   1600,   1616,   1617,   1591,   1609,   1591,   1610,
+    1593,   1609,   1593,   1610,   1594,   1609,   1594,   1610,   1587,
+    1609,   1587,   1610,   1588,   1609,   1588,   1610,   1581,   1609,
+    1581,   1610,   1580,   1609,   1580,   1610,   1582,   1609,   1582,
+    1610,   1589,   1609,   1589,   1610,   1590,   1609,   1590,   1610,
+    1588,   1580,   1588,   1581,   1588,   1582,   1588,   1605,   1588,
+    1585,   1587,   1585,   1589,   1585,   1590,   1585,   1591,   1609,
+    1591,   1610,   1593,   1609,   1593,   1610,   1594,   1609,   1594,
+    1610,   1587,   1609,   1587,   1610,   1588,   1609,   1588,   1610,
+    1581,   1609,   1581,   1610,   1580,   1609,   1580,   1610,   1582,
+    1609,   1582,   1610,   1589,   1609,   1589,   1610,   1590,   1609,
+    1590,   1610,   1588,   1580,   1588,   1581,   1588,   1582,   1588,
+    1605,   1588,   1585,   1587,   1585,   1589,   1585,   1590,   1585,
+    1588,   1580,   1588,   1581,   1588,   1582,   1588,   1605,   1587,
+    1607,   1588,   1607,   1591,   1605,   1587,   1580,   1587,   1581,
+    1587,   1582,   1588,   1580,   1588,   1581,   1588,   1582,   1591,
+    1605,   1592,   1605,   1575,   1611,   1575,   1611,   1578,   1580,
+    1605,   1578,   1581,   1580,   1578,   1581,   1580,   1578,   1581,
+    1605,   1578,   1582,   1605,   1578,   1605,   1580,   1578,   1605,
+    1581,   1578,   1605,   1582,   1580,   1605,   1581,   1580,   1605,
+    1581,   1581,   1605,   1610,   1581,   1605,   1609,   1587,   1581,
+    1580,   1587,   1580,   1581,   1587,   1580,   1609,   1587,   1605,
+    1581,   1587,   1605,   1581,   1587,   1605,   1580,   1587,   1605,
+    1605,   1587,   1605,   1605,   1589,   1581,   1581,   1589,   1581,
+    1581,   1589,   1605,   1605,   1588,   1581,   1605,   1588,   1581,
+    1605,   1588,   1580,   1610,   1588,   1605,   1582,   1588,   1605,
+    1582,   1588,   1605,   1605,   1588,   1605,   1605,   1590,   1581,
+    1609,   1590,   1582,   1605,   1590,   1582,   1605,   1591,   1605,
+    1581,   1591,   1605,   1581,   1591,   1605,   1605,   1591,   1605,
+    1610,   1593,   1580,   1605,   1593,   1605,   1605,   1593,   1605,
+    1605,   1593,   1605,   1609,   1594,   1605,   1605,   1594,   1605,
+    1610,   1594,   1605,   1609,   1601,   1582,   1605,   1601,   1582,
+    1605,   1602,   1605,   1581,   1602,   1605,   1605,   1604,   1581,
+    1605,   1604,   1581,   1610,   1604,   1581,   1609,   1604,   1580,
+    1580,   1604,   1580,   1580,   1604,   1582,   1605,   1604,   1582,
+    1605,   1604,   1605,   1581,   1604,   1605,   1581,   1605,   1581,
+    1580,   1605,   1581,   1605,   1605,   1581,   1610,   1605,   1580,
+    1581,   1605,   1580,   1605,   1605,   1582,   1580,   1605,   1582,
+    1605,   1605,   1580,   1582,   1607,   1605,   1580,   1607,   1605,
+    1605,   1606,   1581,   1605,   1606,   1581,   1609,   1606,   1580,
+    1605,   1606,   1580,   1605,   1606,   1580,   1609,   1606,   1605,
+    1610,   1606,   1605,   1609,   1610,   1605,   1605,   1610,   1605,
+    1605,   1576,   1582,   1610,   1578,   1580,   1610,   1578,   1580,
+    1609,   1578,   1582,   1610,   1578,   1582,   1609,   1578,   1605,
+    1610,   1578,   1605,   1609,   1580,   1605,   1610,   1580,   1581,
+    1609,   1580,   1605,   1609,   1587,   1582,   1609,   1589,   1581,
+    1610,   1588,   1581,   1610,   1590,   1581,   1610,   1604,   1580,
+    1610,   1604,   1605,   1610,   1610,   1581,   1610,   1610,   1580,
+    1610,   1610,   1605,   1610,   1605,   1605,   1610,   1602,   1605,
+    1610,   1606,   1581,   1610,   1602,   1605,   1581,   1604,   1581,
+    1605,   1593,   1605,   1610,   1603,   1605,   1610,   1606,   1580,
+    1581,   1605,   1582,   1610,   1604,   1580,   1605,   1603,   1605,
+    1605,   1604,   1580,   1605,   1606,   1580,   1581,   1580,   1581,
+    1610,   1581,   1580,   1610,   1605,   1580,   1610,   1601,   1605,
+    1610,   1576,   1581,   1610,   1603,   1605,   1605,   1593,   1580,
+    1605,   1589,   1605,   1605,   1587,   1582,   1610,   1606,   1580,
+    1610,   1589,   1604,   1746,   1602,   1604,   1746,   1575,   1604,
+    1604,   1607,   1575,   1603,   1576,   1585,   1605,   1581,   1605,
+    1583,   1589,   1604,   1593,   1605,   1585,   1587,   1608,   1604,
+    1593,   1604,   1610,   1607,   1608,   1587,   1604,   1605,   1589,
+    1604,   1609,   1589,   1604,   1609,   32,     1575,   1604,   1604,
+    1607,   32,     1593,   1604,   1610,   1607,   32,     1608,   1587,
+    1604,   1605,   1580,   1604,   32,     1580,   1604,   1575,   1604,
+    1607,   1585,   1740,   1575,   1604,   44,     12289,  12290,  58,
+    59,     33,     63,     12310,  12311,  46,     46,     46,     46,
+    46,     8212,   8211,   95,     95,     40,     41,     123,    125,
+    12308,  12309,  12304,  12305,  12298,  12299,  12296,  12297,  12300,
+    12301,  12302,  12303,  91,     93,     32,     773,    32,     773,
+    32,     773,    32,     773,    95,     95,     95,     44,     12289,
+    46,     59,     58,     63,     33,     8212,   40,     41,     123,
+    125,    12308,  12309,  35,     38,     42,     43,     45,     60,
+    62,     61,     92,     36,     37,     64,     32,     1611,   1600,
+    1611,   32,     1612,   32,     1613,   32,     1614,   1600,   1614,
+    32,     1615,   1600,   1615,   32,     1616,   1600,   1616,   32,
+    1617,   1600,   1617,   32,     1618,   1600,   1618,   1569,   1575,
+    1619,   1575,   1619,   1575,   1620,   1575,   1620,   1608,   1620,
+    1608,   1620,   1575,   1621,   1575,   1621,   1610,   1620,   1610,
+    1620,   1610,   1620,   1610,   1620,   1575,   1575,   1576,   1576,
+    1576,   1576,   1577,   1577,   1578,   1578,   1578,   1578,   1579,
+    1579,   1579,   1579,   1580,   1580,   1580,   1580,   1581,   1581,
+    1581,   1581,   1582,   1582,   1582,   1582,   1583,   1583,   1584,
+    1584,   1585,   1585,   1586,   1586,   1587,   1587,   1587,   1587,
+    1588,   1588,   1588,   1588,   1589,   1589,   1589,   1589,   1590,
+    1590,   1590,   1590,   1591,   1591,   1591,   1591,   1592,   1592,
+    1592,   1592,   1593,   1593,   1593,   1593,   1594,   1594,   1594,
+    1594,   1601,   1601,   1601,   1601,   1602,   1602,   1602,   1602,
+    1603,   1603,   1603,   1603,   1604,   1604,   1604,   1604,   1605,
+    1605,   1605,   1605,   1606,   1606,   1606,   1606,   1607,   1607,
+    1607,   1607,   1608,   1608,   1609,   1609,   1610,   1610,   1610,
+    1610,   1604,   1575,   1619,   1604,   1575,   1619,   1604,   1575,
+    1620,   1604,   1575,   1620,   1604,   1575,   1621,   1604,   1575,
+    1621,   1604,   1575,   1604,   1575,   33,     34,     35,     36,
+    37,     38,     39,     40,     41,     42,     43,     44,     45,
+    46,     47,     48,     49,     50,     51,     52,     53,     54,
+    55,     56,     57,     58,     59,     60,     61,     62,     63,
+    64,     65,     66,     67,     68,     69,     70,     71,     72,
+    73,     74,     75,     76,     77,     78,     79,     80,     81,
+    82,     83,     84,     85,     86,     87,     88,     89,     90,
+    91,     92,     93,     94,     95,     96,     97,     98,     99,
+    100,    101,    102,    103,    104,    105,    106,    107,    108,
+    109,    110,    111,    112,    113,    114,    115,    116,    117,
+    118,    119,    120,    121,    122,    123,    124,    125,    126,
+    10629,  10630,  12290,  12300,  12301,  12289,  12539,  12530,  12449,
+    12451,  12453,  12455,  12457,  12515,  12517,  12519,  12483,  12540,
+    12450,  12452,  12454,  12456,  12458,  12459,  12461,  12463,  12465,
+    12467,  12469,  12471,  12473,  12475,  12477,  12479,  12481,  12484,
+    12486,  12488,  12490,  12491,  12492,  12493,  12494,  12495,  12498,
+    12501,  12504,  12507,  12510,  12511,  12512,  12513,  12514,  12516,
+    12518,  12520,  12521,  12522,  12523,  12524,  12525,  12527,  12531,
+    12441,  12442,  4448,   4352,   4353,   4522,   4354,   4524,   4525,
+    4355,   4356,   4357,   4528,   4529,   4530,   4531,   4532,   4533,
+    4378,   4358,   4359,   4360,   4385,   4361,   4362,   4363,   4364,
+    4365,   4366,   4367,   4368,   4369,   4370,   4449,   4450,   4451,
+    4452,   4453,   4454,   4455,   4456,   4457,   4458,   4459,   4460,
+    4461,   4462,   4463,   4464,   4465,   4466,   4467,   4468,   4469,
+    162,    163,    172,    32,     772,    166,    165,    8361,   9474,
+    8592,   8593,   8594,   8595,   9632,   9675,   720,    721,    230,
+    665,    595,    675,    43878,  677,    676,    598,    599,    7569,
+    600,    606,    681,    612,    610,    608,    667,    295,    668,
+    615,    644,    682,    683,    620,    122628, 42894,  622,    122629,
+    654,    122630, 248,    630,    631,    113,    634,    122632, 637,
+    638,    640,    680,    678,    43879,  679,    648,    11377,  655,
+    673,    674,    664,    448,    449,    450,    122634, 122654, 69785,
+    69818,  69787,  69818,  69797,  69818,  69937,  69927,  69938,  69927,
+    70471,  70462,  70471,  70487,  70841,  70842,  70841,  70832,  70841,
+    70845,  71096,  71087,  71097,  71087,  71989,  71984,  119127, 119141,
+    119128, 119141, 119128, 119141, 119150, 119128, 119141, 119151, 119128,
+    119141, 119152, 119128, 119141, 119153, 119128, 119141, 119154, 119225,
+    119141, 119226, 119141, 119225, 119141, 119150, 119226, 119141, 119150,
+    119225, 119141, 119151, 119226, 119141, 119151, 65,     66,     67,
+    68,     69,     70,     71,     72,     73,     74,     75,     76,
+    77,     78,     79,     80,     81,     82,     83,     84,     85,
+    86,     87,     88,     89,     90,     97,     98,     99,     100,
+    101,    102,    103,    104,    105,    106,    107,    108,    109,
+    110,    111,    112,    113,    114,    115,    116,    117,    118,
+    119,    120,    121,    122,    65,     66,     67,     68,     69,
+    70,     71,     72,     73,     74,     75,     76,     77,     78,
+    79,     80,     81,     82,     83,     84,     85,     86,     87,
+    88,     89,     90,     97,     98,     99,     100,    101,    102,
+    103,    105,    106,    107,    108,    109,    110,    111,    112,
+    113,    114,    115,    116,    117,    118,    119,    120,    121,
+    122,    65,     66,     67,     68,     69,     70,     71,     72,
+    73,     74,     75,     76,     77,     78,     79,     80,     81,
+    82,     83,     84,     85,     86,     87,     88,     89,     90,
+    97,     98,     99,     100,    101,    102,    103,    104,    105,
+    106,    107,    108,    109,    110,    111,    112,    113,    114,
+    115,    116,    117,    118,    119,    120,    121,    122,    65,
+    67,     68,     71,     74,     75,     78,     79,     80,     81,
+    83,     84,     85,     86,     87,     88,     89,     90,     97,
+    98,     99,     100,    102,    104,    105,    106,    107,    108,
+    109,    110,    112,    113,    114,    115,    116,    117,    118,
+    119,    120,    121,    122,    65,     66,     67,     68,     69,
+    70,     71,     72,     73,     74,     75,     76,     77,     78,
+    79,     80,     81,     82,     83,     84,     85,     86,     87,
+    88,     89,     90,     97,     98,     99,     100,    101,    102,
+    103,    104,    105,    106,    107,    108,    109,    110,    111,
+    112,    113,    114,    115,    116,    117,    118,    119,    120,
+    121,    122,    65,     66,     68,     69,     70,     71,     74,
+    75,     76,     77,     78,     79,     80,     81,     83,     84,
+    85,     86,     87,     88,     89,     97,     98,     99,     100,
+    101,    102,    103,    104,    105,    106,    107,    108,    109,
+    110,    111,    112,    113,    114,    115,    116,    117,    118,
+    119,    120,    121,    122,    65,     66,     68,     69,     70,
+    71,     73,     74,     75,     76,     77,     79,     83,     84,
+    85,     86,     87,     88,     89,     97,     98,     99,     100,
+    101,    102,    103,    104,    105,    106,    107,    108,    109,
+    110,    111,    112,    113,    114,    115,    116,    117,    118,
+    119,    120,    121,    122,    65,     66,     67,     68,     69,
+    70,     71,     72,     73,     74,     75,     76,     77,     78,
+    79,     80,     81,     82,     83,     84,     85,     86,     87,
+    88,     89,     90,     97,     98,     99,     100,    101,    102,
+    103,    104,    105,    106,    107,    108,    109,    110,    111,
+    112,    113,    114,    115,    116,    117,    118,    119,    120,
+    121,    122,    65,     66,     67,     68,     69,     70,     71,
+    72,     73,     74,     75,     76,     77,     78,     79,     80,
+    81,     82,     83,     84,     85,     86,     87,     88,     89,
+    90,     97,     98,     99,     100,    101,    102,    103,    104,
+    105,    106,    107,    108,    109,    110,    111,    112,    113,
+    114,    115,    116,    117,    118,    119,    120,    121,    122,
+    65,     66,     67,     68,     69,     70,     71,     72,     73,
+    74,     75,     76,     77,     78,     79,     80,     81,     82,
+    83,     84,     85,     86,     87,     88,     89,     90,     97,
+    98,     99,     100,    101,    102,    103,    104,    105,    106,
+    107,    108,    109,    110,    111,    112,    113,    114,    115,
+    116,    117,    118,    119,    120,    121,    122,    65,     66,
+    67,     68,     69,     70,     71,     72,     73,     74,     75,
+    76,     77,     78,     79,     80,     81,     82,     83,     84,
+    85,     86,     87,     88,     89,     90,     97,     98,     99,
+    100,    101,    102,    103,    104,    105,    106,    107,    108,
+    109,    110,    111,    112,    113,    114,    115,    116,    117,
+    118,    119,    120,    121,    122,    65,     66,     67,     68,
+    69,     70,     71,     72,     73,     74,     75,     76,     77,
+    78,     79,     80,     81,     82,     83,     84,     85,     86,
+    87,     88,     89,     90,     97,     98,     99,     100,    101,
+    102,    103,    104,    105,    106,    107,    108,    109,    110,
+    111,    112,    113,    114,    115,    116,    117,    118,    119,
+    120,    121,    122,    65,     66,     67,     68,     69,     70,
+    71,     72,     73,     74,     75,     76,     77,     78,     79,
+    80,     81,     82,     83,     84,     85,     86,     87,     88,
+    89,     90,     97,     98,     99,     100,    101,    102,    103,
+    104,    105,    106,    107,    108,    109,    110,    111,    112,
+    113,    114,    115,    116,    117,    118,    119,    120,    121,
+    122,    305,    567,    913,    914,    915,    916,    917,    918,
+    919,    920,    921,    922,    923,    924,    925,    926,    927,
+    928,    929,    920,    931,    932,    933,    934,    935,    936,
+    937,    8711,   945,    946,    947,    948,    949,    950,    951,
+    952,    953,    954,    955,    956,    957,    958,    959,    960,
+    961,    962,    963,    964,    965,    966,    967,    968,    969,
+    8706,   949,    952,    954,    966,    961,    960,    913,    914,
+    915,    916,    917,    918,    919,    920,    921,    922,    923,
+    924,    925,    926,    927,    928,    929,    920,    931,    932,
+    933,    934,    935,    936,    937,    8711,   945,    946,    947,
+    948,    949,    950,    951,    952,    953,    954,    955,    956,
+    957,    958,    959,    960,    961,    962,    963,    964,    965,
+    966,    967,    968,    969,    8706,   949,    952,    954,    966,
+    961,    960,    913,    914,    915,    916,    917,    918,    919,
+    920,    921,    922,    923,    924,    925,    926,    927,    928,
+    929,    920,    931,    932,    933,    934,    935,    936,    937,
+    8711,   945,    946,    947,    948,    949,    950,    951,    952,
+    953,    954,    955,    956,    957,    958,    959,    960,    961,
+    962,    963,    964,    965,    966,    967,    968,    969,    8706,
+    949,    952,    954,    966,    961,    960,    913,    914,    915,
+    916,    917,    918,    919,    920,    921,    922,    923,    924,
+    925,    926,    927,    928,    929,    920,    931,    932,    933,
+    934,    935,    936,    937,    8711,   945,    946,    947,    948,
+    949,    950,    951,    952,    953,    954,    955,    956,    957,
+    958,    959,    960,    961,    962,    963,    964,    965,    966,
+    967,    968,    969,    8706,   949,    952,    954,    966,    961,
+    960,    913,    914,    915,    916,    917,    918,    919,    920,
+    921,    922,    923,    924,    925,    926,    927,    928,    929,
+    920,    931,    932,    933,    934,    935,    936,    937,    8711,
+    945,    946,    947,    948,    949,    950,    951,    952,    953,
+    954,    955,    956,    957,    958,    959,    960,    961,    962,
+    963,    964,    965,    966,    967,    968,    969,    8706,   949,
+    952,    954,    966,    961,    960,    988,    989,    48,     49,
+    50,     51,     52,     53,     54,     55,     56,     57,     48,
+    49,     50,     51,     52,     53,     54,     55,     56,     57,
+    48,     49,     50,     51,     52,     53,     54,     55,     56,
+    57,     48,     49,     50,     51,     52,     53,     54,     55,
+    56,     57,     48,     49,     50,     51,     52,     53,     54,
+    55,     56,     57,     1072,   1073,   1074,   1075,   1076,   1077,
+    1078,   1079,   1080,   1082,   1083,   1084,   1086,   1087,   1088,
+    1089,   1090,   1091,   1092,   1093,   1094,   1095,   1096,   1099,
+    1101,   1102,   42633,  1241,   1110,   1112,   1257,   1199,   1231,
+    1072,   1073,   1074,   1075,   1076,   1077,   1078,   1079,   1080,
+    1082,   1083,   1086,   1087,   1089,   1091,   1092,   1093,   1094,
+    1095,   1096,   1098,   1099,   1169,   1110,   1109,   1119,   1195,
+    42577,  1201,   1575,   1576,   1580,   1583,   1608,   1586,   1581,
+    1591,   1610,   1603,   1604,   1605,   1606,   1587,   1593,   1601,
+    1589,   1602,   1585,   1588,   1578,   1579,   1582,   1584,   1590,
+    1592,   1594,   1646,   1722,   1697,   1647,   1576,   1580,   1607,
+    1581,   1610,   1603,   1604,   1605,   1606,   1587,   1593,   1601,
+    1589,   1602,   1588,   1578,   1579,   1582,   1590,   1594,   1580,
+    1581,   1610,   1604,   1606,   1587,   1593,   1589,   1602,   1588,
+    1582,   1590,   1594,   1722,   1647,   1576,   1580,   1607,   1581,
+    1591,   1610,   1603,   1605,   1606,   1587,   1593,   1601,   1589,
+    1602,   1588,   1578,   1579,   1582,   1590,   1592,   1594,   1646,
+    1697,   1575,   1576,   1580,   1583,   1607,   1608,   1586,   1581,
+    1591,   1610,   1604,   1605,   1606,   1587,   1593,   1601,   1589,
+    1602,   1585,   1588,   1578,   1579,   1582,   1584,   1590,   1592,
+    1594,   1576,   1580,   1583,   1608,   1586,   1581,   1591,   1610,
+    1604,   1605,   1606,   1587,   1593,   1601,   1589,   1602,   1585,
+    1588,   1578,   1579,   1582,   1584,   1590,   1592,   1594,   48,
+    46,     48,     44,     49,     44,     50,     44,     51,     44,
+    52,     44,     53,     44,     54,     44,     55,     44,     56,
+    44,     57,     44,     40,     65,     41,     40,     66,     41,
+    40,     67,     41,     40,     68,     41,     40,     69,     41,
+    40,     70,     41,     40,     71,     41,     40,     72,     41,
+    40,     73,     41,     40,     74,     41,     40,     75,     41,
+    40,     76,     41,     40,     77,     41,     40,     78,     41,
+    40,     79,     41,     40,     80,     41,     40,     81,     41,
+    40,     82,     41,     40,     83,     41,     40,     84,     41,
+    40,     85,     41,     40,     86,     41,     40,     87,     41,
+    40,     88,     41,     40,     89,     41,     40,     90,     41,
+    12308,  83,     12309,  67,     82,     67,     68,     87,     90,
+    65,     66,     67,     68,     69,     70,     71,     72,     73,
+    74,     75,     76,     77,     78,     79,     80,     81,     82,
+    83,     84,     85,     86,     87,     88,     89,     90,     72,
+    86,     77,     86,     83,     68,     83,     83,     80,     80,
+    86,     87,     67,     77,     67,     77,     68,     77,     82,
+    68,     74,     12411,  12363,  12467,  12467,  12469,  25163,  23383,
+    21452,  12486,  12441,  20108,  22810,  35299,  22825,  20132,  26144,
+    28961,  26009,  21069,  24460,  20877,  26032,  21021,  32066,  29983,
+    36009,  22768,  21561,  28436,  25237,  25429,  19968,  19977,  36938,
+    24038,  20013,  21491,  25351,  36208,  25171,  31105,  31354,  21512,
+    28288,  26377,  26376,  30003,  21106,  21942,  37197,  12308,  26412,
+    12309,  12308,  19977,  12309,  12308,  20108,  12309,  12308,  23433,
+    12309,  12308,  28857,  12309,  12308,  25171,  12309,  12308,  30423,
+    12309,  12308,  21213,  12309,  12308,  25943,  12309,  24471,  21487,
+    48,     49,     50,     51,     52,     53,     54,     55,     56,
+    57,     20029,  20024,  20033,  131362, 20320,  20398,  20411,  20482,
+    20602,  20633,  20711,  20687,  13470,  132666, 20813,  20820,  20836,
+    20855,  132380, 13497,  20839,  20877,  132427, 20887,  20900,  20172,
+    20908,  20917,  168415, 20981,  20995,  13535,  21051,  21062,  21106,
+    21111,  13589,  21191,  21193,  21220,  21242,  21253,  21254,  21271,
+    21321,  21329,  21338,  21363,  21373,  21375,  21375,  21375,  133676,
+    28784,  21450,  21471,  133987, 21483,  21489,  21510,  21662,  21560,
+    21576,  21608,  21666,  21750,  21776,  21843,  21859,  21892,  21892,
+    21913,  21931,  21939,  21954,  22294,  22022,  22295,  22097,  22132,
+    20999,  22766,  22478,  22516,  22541,  22411,  22578,  22577,  22700,
+    136420, 22770,  22775,  22790,  22810,  22818,  22882,  136872, 136938,
+    23020,  23067,  23079,  23000,  23142,  14062,  14076,  23304,  23358,
+    23358,  137672, 23491,  23512,  23527,  23539,  138008, 23551,  23558,
+    24403,  23586,  14209,  23648,  23662,  23744,  23693,  138724, 23875,
+    138726, 23918,  23915,  23932,  24033,  24034,  14383,  24061,  24104,
+    24125,  24169,  14434,  139651, 14460,  24240,  24243,  24246,  24266,
+    172946, 24318,  140081, 140081, 33281,  24354,  24354,  14535,  144056,
+    156122, 24418,  24427,  14563,  24474,  24525,  24535,  24569,  24705,
+    14650,  14620,  24724,  141012, 24775,  24904,  24908,  24910,  24908,
+    24954,  24974,  25010,  24996,  25007,  25054,  25074,  25078,  25104,
+    25115,  25181,  25265,  25300,  25424,  142092, 25405,  25340,  25448,
+    25475,  25572,  142321, 25634,  25541,  25513,  14894,  25705,  25726,
+    25757,  25719,  14956,  25935,  25964,  143370, 26083,  26360,  26185,
+    15129,  26257,  15112,  15076,  20882,  20885,  26368,  26268,  32941,
+    17369,  26391,  26395,  26401,  26462,  26451,  144323, 15177,  26618,
+    26501,  26706,  26757,  144493, 26766,  26655,  26900,  15261,  26946,
+    27043,  27114,  27304,  145059, 27355,  15384,  27425,  145575, 27476,
+    15438,  27506,  27551,  27578,  27579,  146061, 138507, 146170, 27726,
+    146620, 27839,  27853,  27751,  27926,  27966,  28023,  27969,  28009,
+    28024,  28037,  146718, 27956,  28207,  28270,  15667,  28363,  28359,
+    147153, 28153,  28526,  147294, 147342, 28614,  28729,  28702,  28699,
+    15766,  28746,  28797,  28791,  28845,  132389, 28997,  148067, 29084,
+    148395, 29224,  29237,  29264,  149000, 29312,  29333,  149301, 149524,
+    29562,  29579,  16044,  29605,  16056,  16056,  29767,  29788,  29809,
+    29829,  29898,  16155,  29988,  150582, 30014,  150674, 30064,  139679,
+    30224,  151457, 151480, 151620, 16380,  16392,  30452,  151795, 151794,
+    151833, 151859, 30494,  30495,  30495,  30538,  16441,  30603,  16454,
+    16534,  152605, 30798,  30860,  30924,  16611,  153126, 31062,  153242,
+    153285, 31119,  31211,  16687,  31296,  31306,  31311,  153980, 154279,
+    154279, 31470,  16898,  154539, 31686,  31689,  16935,  154752, 31954,
+    17056,  31976,  31971,  32000,  155526, 32099,  17153,  32199,  32258,
+    32325,  17204,  156200, 156231, 17241,  156377, 32634,  156478, 32661,
+    32762,  32773,  156890, 156963, 32864,  157096, 32880,  144223, 17365,
+    32946,  33027,  17419,  33086,  23221,  157607, 157621, 144275, 144284,
+    33281,  33284,  36766,  17515,  33425,  33419,  33437,  21171,  33457,
+    33459,  33469,  33510,  158524, 33509,  33565,  33635,  33709,  33571,
+    33725,  33767,  33879,  33619,  33738,  33740,  33756,  158774, 159083,
+    158933, 17707,  34033,  34035,  34070,  160714, 34148,  159532, 17757,
+    17761,  159665, 159954, 17771,  34384,  34396,  34407,  34409,  34473,
+    34440,  34574,  34530,  34681,  34600,  34667,  34694,  17879,  34785,
+    34817,  17913,  34912,  34915,  161383, 35031,  35038,  17973,  35066,
+    13499,  161966, 162150, 18110,  18119,  35488,  35565,  35722,  35925,
+    162984, 36011,  36033,  36123,  36215,  163631, 133124, 36299,  36284,
+    36336,  133342, 36564,  36664,  165330, 165357, 37012,  37105,  37137,
+    165678, 37147,  37432,  37591,  37592,  37500,  37881,  37909,  166906,
+    38283,  18837,  38327,  167287, 18918,  38595,  23986,  38691,  168261,
+    168474, 19054,  19062,  38880,  168970, 19122,  169110, 38923,  38923,
+    38953,  169398, 39138,  19251,  39209,  39335,  39362,  39422,  19406,
+    170800, 39698,  40000,  40189,  19662,  19693,  40295,  172238, 19704,
+    172293, 172558, 172689, 40635,  19798,  40697,  40702,  40709,  40719,
+    40726,  40763,  173568};
+
+const uint8_t canonical_combining_class_index[4352] = {
+    0,  0,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 0,  0,
+    15, 0,  0,  0,  16, 17, 18, 19, 20, 21, 22, 0,  0,  23, 0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  24, 25, 0,  0,  26, 0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  27, 0,  28, 29, 30,
+    31, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  32, 0,  0,  33, 0,  0,  34, 35, 36, 0,  0,  0,  0,  0,  0,
+    37, 0,  0,  38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,  52,
+    53, 0,  54, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  55, 56, 0,  0,  0,  57, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  58, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  59, 60, 0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  61, 56, 62, 0,  63, 0,  0,  0,  64, 65, 0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    0};
+const uint8_t canonical_combining_class_block[67][256] = {
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 232, 220, 220, 220, 220, 232, 216, 220, 220,
+     220, 220, 220, 202, 202, 220, 220, 220, 220, 202, 202, 220, 220, 220, 220,
+     220, 220, 220, 220, 220, 220, 220, 1,   1,   1,   1,   1,   220, 220, 220,
+     220, 230, 230, 230, 230, 230, 230, 230, 230, 240, 230, 220, 220, 220, 230,
+     230, 230, 220, 220, 0,   230, 230, 230, 220, 220, 220, 220, 230, 232, 220,
+     220, 230, 233, 234, 234, 233, 234, 234, 233, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 230, 0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230,
+     230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   220, 230, 230, 230, 230,
+     220, 230, 230, 230, 222, 220, 230, 230, 230, 230, 230, 230, 220, 220, 220,
+     220, 220, 220, 230, 230, 220, 230, 230, 222, 228, 230, 10,  11,  12,  13,
+     14,  15,  16,  17,  18,  19,  19,  20,  21,  22,  0,   23,  0,   24,  25,
+     0,   230, 220, 0,   18,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   230, 230, 230, 230, 230, 230, 230, 230, 30,  31,  32,  0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     27,  28,  29,  30,  31,  32,  33,  34,  230, 230, 220, 220, 230, 230, 230,
+     230, 230, 220, 230, 230, 220, 0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   35,  0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   230, 230, 230, 230, 230, 230, 230, 0,   0,   230, 230,
+     230, 230, 220, 230, 0,   0,   230, 230, 0,   220, 230, 230, 220, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   36,  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   230, 220, 230, 230, 220, 230, 230, 220, 220, 220, 230, 220,
+     220, 230, 220, 230, 230, 230, 220, 230, 220, 230, 220, 230, 220, 230, 230,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230, 230, 230, 230,
+     230, 230, 220, 230, 0,   0,   0,   0,   0,   0,   0,   0,   0,   220, 0,
+     0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   230, 230, 230, 230, 0,   230, 230, 230,
+     230, 230, 230, 230, 230, 230, 0,   230, 230, 230, 0,   230, 230, 230, 230,
+     230, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   220,
+     220, 220, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   230, 220, 220, 220, 230, 230, 230, 230, 0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   230, 230, 230, 230, 230, 220, 220, 220,
+     220, 220, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 0,   220, 230, 230, 220, 230, 230, 220, 230, 230, 230, 220, 220, 220,
+     27,  28,  29,  230, 230, 230, 220, 230, 230, 220, 220, 230, 230, 230, 230,
+     230},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   7,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0,   0, 0, 230, 220, 230, 230, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0,   0,   0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 84, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 7, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   103, 103, 9,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     107, 107, 107, 107, 0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   118, 118, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   122, 122, 122, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0},
+    {0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   220, 220, 0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 220, 0,   220, 0,   216, 0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 129, 130, 0,   132, 0,   0, 0,
+     0,   0, 130, 130, 130, 130, 0, 0, 130, 0,   230, 230, 9,   0, 230,
+     230, 0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   220, 0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0,   0, 0,   0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0, 0,
+     0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 7, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 9, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 228, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 222, 230, 220, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   230, 220, 0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   9,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230, 230,
+     230, 230, 230, 230, 230, 0,   0,   220, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230, 230, 230,
+     230, 220, 220, 220, 220, 220, 220, 230, 230, 220, 0,   220, 220, 230, 230,
+     220, 220, 230, 230, 230, 230, 230, 220, 230, 230, 230, 230, 0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   7,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 220, 230, 230, 230, 230, 230,
+     230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   9,
+     9,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,
+     0,   0,   7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   9,   9,   0,   0,   0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0},
+    {0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   7,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230,
+     230, 0, 1, 220, 220, 220, 220, 220, 230, 230, 220, 220, 220, 220, 230,
+     0,   1, 1, 1,   1,   1,   1,   1,   0,   0,   0,   0,   220, 0,   0,
+     0,   0, 0, 0,   230, 0,   0,   0,   230, 230, 0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230, 220,
+     230, 230, 230, 230, 230, 230, 230, 220, 230, 230, 234, 214, 220, 202, 230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 232, 228, 228, 220, 218, 230, 233, 220, 230,
+     220},
+    {0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,
+     230, 230, 1, 1, 230, 230, 230, 230, 1,   1,   1, 230, 230, 0,   0,   0,
+     0,   230, 0, 0, 0,   1,   1,   230, 220, 230, 1, 1,   220, 220, 220, 220,
+     230, 0,   0, 0, 0,   0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   9,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230},
+    {0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 218, 228, 232, 222, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230,
+     0,   0,   0, 0, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230, 230,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     230, 230, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0},
+    {0,   0,   0,   0,   0,   0,   9,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   9,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   9,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230,
+     230, 230, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220,
+     220, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 230, 0, 230, 230, 220, 0, 0, 230, 230, 0, 0, 0, 0, 0,
+     230, 230, 0, 230, 0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 0,
+     0,   0,   0, 0,   0, 0,   0, 0,   0,   0,   0, 0, 0,   0,   0, 0, 0, 0, 9,
+     0,   0,   0, 0,   0, 0,   0, 0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   230, 230, 230, 230, 230, 230, 230, 220, 220, 220, 220, 220, 220,
+     220, 230, 230, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 220, 0, 230, 0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   230, 1, 220, 0,
+     0, 0, 0, 9, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 220, 0, 0, 0,   0, 0,   0,   0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0, 0, 0,   0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 230, 230, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,  0, 0, 0, 0,   0,   0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220},
+    {0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 220, 220,
+     230, 230, 230, 220, 230, 220, 220, 220, 220, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   230, 220, 230, 220, 0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0,   0,   0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 9, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   7,   7,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   9, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 230, 230, 230, 230, 230, 230, 230, 0, 0, 0,
+     230, 230, 230, 230, 230, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0,
+     0,   0,   0,   0,   0,   0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 7,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
+     7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 7, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 9, 9, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 230, 230, 230, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 216, 216, 1,   1,   1,   0,   0,   0,   226, 216, 216,
+     216, 216, 216, 0, 0, 0,   0,   0,   0,   0,   0,   220, 220, 220, 220, 220,
+     220, 220, 220, 0, 0, 230, 230, 230, 230, 230, 220, 220, 0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   230, 230, 230, 230, 0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0, 0, 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0},
+    {0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {230, 230, 230, 230, 230, 230, 230, 0,   230, 230, 230, 230, 230, 230, 230,
+     230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 0,   0,   230, 230, 230,
+     230, 230, 230, 230, 0,   230, 230, 0,   230, 230, 230, 230, 230, 0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   230, 0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+     0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   230, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 0,   0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 232, 232, 220, 230, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220, 220, 220, 220, 220, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0,   0},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 230, 230, 7, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0,   0,   0,   0, 0}};
+
+const uint8_t composition_index[4352] = {
+    0, 1, 2, 3, 4,  5,  6, 5, 5,  7,  5, 8,  9,  10, 5, 5, 11, 5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  12, 5, 5, 13, 14, 5, 15, 16, 5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 17, 5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 18, 19, 5, 20, 21, 22, 5, 5, 5,  23, 5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5,  5, 5, 5, 5,
+    5, 5, 5, 5, 5,  5,  5, 5, 5,  5,  5, 5,  5,  5,  5, 5, 5,  5};
+const uint16_t composition_block[67][257] = {
+    {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,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+     1,   3,   5,   7,   7,   7,   39,  45,  55,  67,  101, 103, 117, 131, 161,
+     163, 173, 185, 191, 209, 241, 245, 245, 261, 275, 289, 327, 331, 343, 347,
+     365, 377, 377, 377, 377, 377, 377, 377, 409, 415, 425, 437, 471, 473, 487,
+     503, 531, 535, 545, 557, 563, 581, 613, 617, 617, 633, 647, 663, 701, 705,
+     719, 723, 743, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+     755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+     755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755,
+     755, 755, 755, 755, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761,
+     761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761, 761,
+     769, 769, 771, 773, 777, 779, 779, 779, 787, 787, 787, 787, 787, 789, 789,
+     789, 789, 789, 797, 803, 805, 805, 807, 807, 807, 807, 815, 815, 815, 815,
+     815, 815, 823, 823, 825, 827, 831, 833, 833, 833, 841, 841, 841, 841, 841,
+     843, 843, 843, 843, 843, 851, 857, 859, 859, 861, 861, 861, 861, 869, 869,
+     869, 869},
+    {869, 869, 869, 877, 885, 885, 885, 885, 885, 885, 885, 885, 885, 885, 885,
+     885, 885, 885, 885, 889, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893,
+     893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893,
+     893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893,
+     893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893, 893,
+     893, 893, 897, 901, 901, 901, 901, 901, 901, 901, 901, 901, 901, 901, 901,
+     901, 903, 905, 905, 905, 905, 905, 907, 909, 909, 909, 909, 909, 909, 909,
+     911, 913, 915, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917, 917,
+     917, 917, 917, 917, 917, 917, 917, 917, 919, 919, 919, 919, 919, 919, 919,
+     919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919,
+     919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 919, 929, 939, 939, 939,
+     939, 939, 939, 939, 939, 939, 939, 939, 939, 939, 939, 949, 959, 959, 959,
+     959, 959, 959, 959, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961,
+     961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961,
+     961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 961,
+     961, 961, 961, 961, 961, 961, 961, 961, 961, 961, 963, 965, 965, 965, 965,
+     965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965,
+     965, 965},
+    {965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965,
+     965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965, 965,
+     965, 965, 965, 965, 965, 965, 965, 965, 965, 967, 969, 971, 973, 973, 973,
+     973, 973, 975, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977,
+     977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979, 979,
+     979, 979},
+    {979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,  979,
+     979,  979,  993,  993,  993,  993,  1001, 1001, 1011, 1011, 1025, 1025,
+     1025, 1025, 1025, 1025, 1033, 1033, 1035, 1035, 1035, 1035, 1047, 1047,
+     1047, 1047, 1057, 1057, 1057, 1059, 1059, 1061, 1061, 1061, 1077, 1077,
+     1077, 1077, 1085, 1085, 1097, 1097, 1113, 1113, 1113, 1113, 1113, 1113,
+     1121, 1121, 1125, 1125, 1125, 1125, 1141, 1141, 1141, 1141, 1153, 1159,
+     1165, 1165, 1165, 1167, 1167, 1167, 1167, 1171, 1171, 1171, 1171, 1171,
+     1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171,
+     1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171,
+     1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171, 1171,
+     1171, 1171, 1171, 1171, 1171},
+    {1171, 1171, 1171, 1171, 1171, 1171, 1171, 1173, 1173, 1173, 1173, 1173,
+     1173, 1173, 1173, 1173, 1173, 1177, 1177, 1177, 1179, 1179, 1185, 1189,
+     1191, 1199, 1199, 1201, 1201, 1201, 1201, 1203, 1203, 1203, 1203, 1203,
+     1211, 1211, 1211, 1211, 1213, 1213, 1213, 1213, 1215, 1215, 1217, 1217,
+     1217, 1221, 1221, 1221, 1223, 1223, 1229, 1233, 1235, 1243, 1243, 1245,
+     1245, 1245, 1245, 1247, 1247, 1247, 1247, 1247, 1255, 1255, 1255, 1255,
+     1257, 1257, 1257, 1257, 1259, 1259, 1261, 1261, 1261, 1261, 1261, 1261,
+     1261, 1261, 1261, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263,
+     1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263,
+     1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1263, 1265, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267, 1267,
+     1267, 1269, 1271, 1271, 1271, 1271, 1271, 1271, 1271, 1271, 1271, 1271,
+     1271, 1271, 1271, 1271, 1271, 1273, 1275, 1275, 1275, 1275, 1275, 1275,
+     1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
+     1275, 1275, 1275, 1275, 1275},
+    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+    {1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
+     1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
+     1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
+     1275, 1275, 1275, 1275, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281,
+     1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281,
+     1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281,
+     1281, 1283, 1283, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285, 1285,
+     1285, 1285, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287, 1287,
+     1287, 1287, 1287, 1287, 1287, 1287, 1287, 1289, 1289, 1289, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291},
+    {1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291,
+     1291, 1291, 1291, 1291, 1291, 1293, 1293, 1293, 1293, 1293, 1293, 1293,
+     1293, 1295, 1295, 1295, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297,
+     1297, 1297, 1297, 1297, 1297, 1297, 1297, 1297, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301},
+    {1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301, 1301,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307, 1307,
+     1307, 1307, 1307, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309,
+     1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309,
+     1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309,
+     1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309,
+     1309, 1309, 1309, 1309, 1309, 1309, 1309, 1313, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315},
+    {1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315,
+     1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1315, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317, 1317,
+     1319, 1319, 1319, 1319, 1319, 1319, 1319, 1325, 1325, 1325, 1325, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327},
+    {1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327,
+     1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1331,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333, 1333,
+     1333, 1333, 1339, 1339, 1339, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1341, 1341, 1341},
+    {1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341, 1341,
+     1341, 1341, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343, 1343,
+     1343, 1343, 1343, 1343, 1343},
+    {1343, 1343, 1343, 1343, 1343, 1343, 1345, 1345, 1347, 1347, 1349, 1349,
+     1351, 1351, 1353, 1353, 1353, 1353, 1355, 1355, 1355, 1355, 1355, 1355,
+     1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355,
+     1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355,
+     1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1355, 1357,
+     1357, 1359, 1359, 1361, 1363, 1363, 1363, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365},
+    {1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365, 1365,
+     1365, 1365, 1365, 1365, 1365, 1365, 1365, 1367, 1369, 1369, 1369, 1369,
+     1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369,
+     1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369, 1369,
+     1369, 1369, 1369, 1369, 1369, 1369, 1369, 1371, 1373, 1373, 1373, 1373,
+     1373, 1373, 1373, 1375, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377,
+     1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377,
+     1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377,
+     1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377,
+     1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377, 1377,
+     1377, 1377, 1377, 1377, 1377, 1381, 1385, 1385, 1385, 1385, 1385, 1385,
+     1385, 1385, 1385, 1385, 1385, 1385, 1385, 1385, 1385, 1385, 1385, 1385,
+     1385, 1385, 1385, 1385, 1385, 1387, 1389, 1389, 1389, 1389, 1389, 1389,
+     1389, 1389, 1389, 1389, 1389, 1389, 1389, 1389, 1389, 1389, 1389, 1389,
+     1389, 1391, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393,
+     1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393,
+     1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393,
+     1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393, 1393,
+     1393, 1393, 1393, 1393, 1393},
+    {1393, 1401, 1409, 1411, 1413, 1415, 1417, 1419, 1421, 1429, 1437, 1439,
+     1441, 1443, 1445, 1447, 1449, 1453, 1457, 1457, 1457, 1457, 1457, 1457,
+     1457, 1461, 1465, 1465, 1465, 1465, 1465, 1465, 1465, 1473, 1481, 1483,
+     1485, 1487, 1489, 1491, 1493, 1501, 1509, 1511, 1513, 1515, 1517, 1519,
+     1521, 1527, 1533, 1533, 1533, 1533, 1533, 1533, 1533, 1539, 1545, 1545,
+     1545, 1545, 1545, 1545, 1545, 1549, 1553, 1553, 1553, 1553, 1553, 1553,
+     1553, 1557, 1561, 1561, 1561, 1561, 1561, 1561, 1561, 1567, 1573, 1573,
+     1573, 1573, 1573, 1573, 1573, 1573, 1579, 1579, 1579, 1579, 1579, 1579,
+     1579, 1587, 1595, 1597, 1599, 1601, 1603, 1605, 1607, 1615, 1623, 1625,
+     1627, 1629, 1631, 1633, 1635, 1637, 1637, 1637, 1637, 1639, 1639, 1639,
+     1639, 1639, 1639, 1639, 1639, 1641, 1641, 1641, 1641, 1641, 1641, 1641,
+     1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641,
+     1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641,
+     1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641,
+     1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641, 1641,
+     1641, 1641, 1641, 1643, 1643, 1643, 1643, 1643, 1643, 1643, 1643, 1643,
+     1649, 1649, 1649, 1649, 1649, 1649, 1649, 1651, 1651, 1651, 1651, 1651,
+     1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651,
+     1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651,
+     1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651, 1651,
+     1651, 1651, 1651, 1651, 1651, 1651, 1651, 1653, 1653, 1653, 1653, 1653,
+     1653, 1653, 1653, 1659, 1659},
+    {1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659, 1659,
+     1659, 1661, 1661, 1663, 1663, 1665, 1665, 1665, 1665, 1665, 1665, 1665,
+     1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665,
+     1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665,
+     1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665,
+     1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665, 1665,
+     1665, 1665, 1665, 1665, 1665, 1667, 1667, 1669, 1669, 1671, 1671, 1671,
+     1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671,
+     1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671,
+     1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671, 1671,
+     1671, 1671, 1671, 1671, 1671},
+    {1671, 1671, 1671, 1671, 1673, 1673, 1673, 1673, 1673, 1675, 1675, 1675,
+     1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677,
+     1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677, 1677,
+     1679, 1679, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681,
+     1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681, 1681,
+     1681, 1683, 1683, 1683, 1683, 1683, 1683, 1683, 1685, 1685, 1687, 1687,
+     1687, 1689, 1689, 1689, 1689, 1689, 1691, 1691, 1691, 1691, 1691, 1691,
+     1691, 1691, 1691, 1691, 1691, 1691, 1691, 1691, 1691, 1691, 1691, 1691,
+     1691, 1691, 1693, 1693, 1693, 1695, 1697, 1697, 1697, 1697, 1697, 1697,
+     1697, 1697, 1697, 1697, 1697, 1697, 1697, 1699, 1701, 1701, 1701, 1703,
+     1705, 1705, 1705, 1707, 1709, 1711, 1713, 1713, 1713, 1713, 1713, 1715,
+     1717, 1717, 1717, 1719, 1721, 1721, 1721, 1721, 1721, 1721, 1721, 1721,
+     1721, 1721, 1723, 1725, 1725, 1725, 1725, 1725, 1725, 1725, 1725, 1725,
+     1725, 1725, 1725, 1725, 1725, 1725, 1725, 1727, 1727, 1727, 1727, 1727,
+     1727, 1729, 1731, 1731, 1733, 1733, 1733, 1733, 1733, 1733, 1733, 1735,
+     1737, 1739, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741},
+    {1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741,
+     1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1741, 1743,
+     1743, 1743, 1743, 1743, 1745, 1745, 1747, 1747, 1749, 1749, 1751, 1751,
+     1753, 1753, 1755, 1755, 1757, 1757, 1759, 1759, 1761, 1761, 1763, 1763,
+     1765, 1765, 1767, 1767, 1767, 1769, 1769, 1771, 1771, 1773, 1773, 1773,
+     1773, 1773, 1773, 1773, 1777, 1777, 1777, 1781, 1781, 1781, 1785, 1785,
+     1785, 1789, 1789, 1789, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793,
+     1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793,
+     1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793, 1793,
+     1793, 1793, 1795, 1795, 1795, 1795, 1795, 1795, 1795, 1795, 1795, 1797,
+     1797, 1797, 1797, 1797, 1799, 1799, 1801, 1801, 1803, 1803, 1805, 1805,
+     1807, 1807, 1809, 1809, 1811, 1811, 1813, 1813, 1815, 1815, 1817, 1817,
+     1819, 1819, 1821, 1821, 1821, 1823, 1823, 1825, 1825, 1827, 1827, 1827,
+     1827, 1827, 1827, 1827, 1831, 1831, 1831, 1835, 1835, 1835, 1839, 1839,
+     1839, 1843, 1843, 1843, 1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847,
+     1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847, 1847,
+     1849, 1851, 1853, 1855, 1855, 1855, 1855, 1855, 1855, 1855, 1855, 1855,
+     1855, 1855, 1857, 1857, 1857},
+    {1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857,
+     1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1857, 1859, 1859,
+     1861, 1861, 1861, 1861, 1861, 1861, 1861, 1861, 1861, 1861, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863},
+    {1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863, 1863,
+     1863, 1863, 1865, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867},
+    {1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867, 1867,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871},
+    {1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871, 1871,
+     1871, 1871, 1871, 1871, 1871, 1871, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877},
+    {1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877,
+     1877, 1877, 1877, 1877, 1877, 1879, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881},
+    {1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881, 1881,
+     1881, 1881, 1881, 1881, 1881, 1881, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883, 1883,
+     1883, 1883, 1883, 1883, 1883}};
+const char32_t composition_data[1883] = {
+    0,     824,   8814,  824,   8800,  824,   8815,  768,   192,   769,   193,
+    770,   194,   771,   195,   772,   256,   774,   258,   775,   550,   776,
+    196,   777,   7842,  778,   197,   780,   461,   783,   512,   785,   514,
+    803,   7840,  805,   7680,  808,   260,   775,   7682,  803,   7684,  817,
+    7686,  769,   262,   770,   264,   775,   266,   780,   268,   807,   199,
+    775,   7690,  780,   270,   803,   7692,  807,   7696,  813,   7698,  817,
+    7694,  768,   200,   769,   201,   770,   202,   771,   7868,  772,   274,
+    774,   276,   775,   278,   776,   203,   777,   7866,  780,   282,   783,
+    516,   785,   518,   803,   7864,  807,   552,   808,   280,   813,   7704,
+    816,   7706,  775,   7710,  769,   500,   770,   284,   772,   7712,  774,
+    286,   775,   288,   780,   486,   807,   290,   770,   292,   775,   7714,
+    776,   7718,  780,   542,   803,   7716,  807,   7720,  814,   7722,  768,
+    204,   769,   205,   770,   206,   771,   296,   772,   298,   774,   300,
+    775,   304,   776,   207,   777,   7880,  780,   463,   783,   520,   785,
+    522,   803,   7882,  808,   302,   816,   7724,  770,   308,   769,   7728,
+    780,   488,   803,   7730,  807,   310,   817,   7732,  769,   313,   780,
+    317,   803,   7734,  807,   315,   813,   7740,  817,   7738,  769,   7742,
+    775,   7744,  803,   7746,  768,   504,   769,   323,   771,   209,   775,
+    7748,  780,   327,   803,   7750,  807,   325,   813,   7754,  817,   7752,
+    768,   210,   769,   211,   770,   212,   771,   213,   772,   332,   774,
+    334,   775,   558,   776,   214,   777,   7886,  779,   336,   780,   465,
+    783,   524,   785,   526,   795,   416,   803,   7884,  808,   490,   769,
+    7764,  775,   7766,  769,   340,   775,   7768,  780,   344,   783,   528,
+    785,   530,   803,   7770,  807,   342,   817,   7774,  769,   346,   770,
+    348,   775,   7776,  780,   352,   803,   7778,  806,   536,   807,   350,
+    775,   7786,  780,   356,   803,   7788,  806,   538,   807,   354,   813,
+    7792,  817,   7790,  768,   217,   769,   218,   770,   219,   771,   360,
+    772,   362,   774,   364,   776,   220,   777,   7910,  778,   366,   779,
+    368,   780,   467,   783,   532,   785,   534,   795,   431,   803,   7908,
+    804,   7794,  808,   370,   813,   7798,  816,   7796,  771,   7804,  803,
+    7806,  768,   7808,  769,   7810,  770,   372,   775,   7814,  776,   7812,
+    803,   7816,  775,   7818,  776,   7820,  768,   7922,  769,   221,   770,
+    374,   771,   7928,  772,   562,   775,   7822,  776,   376,   777,   7926,
+    803,   7924,  769,   377,   770,   7824,  775,   379,   780,   381,   803,
+    7826,  817,   7828,  768,   224,   769,   225,   770,   226,   771,   227,
+    772,   257,   774,   259,   775,   551,   776,   228,   777,   7843,  778,
+    229,   780,   462,   783,   513,   785,   515,   803,   7841,  805,   7681,
+    808,   261,   775,   7683,  803,   7685,  817,   7687,  769,   263,   770,
+    265,   775,   267,   780,   269,   807,   231,   775,   7691,  780,   271,
+    803,   7693,  807,   7697,  813,   7699,  817,   7695,  768,   232,   769,
+    233,   770,   234,   771,   7869,  772,   275,   774,   277,   775,   279,
+    776,   235,   777,   7867,  780,   283,   783,   517,   785,   519,   803,
+    7865,  807,   553,   808,   281,   813,   7705,  816,   7707,  775,   7711,
+    769,   501,   770,   285,   772,   7713,  774,   287,   775,   289,   780,
+    487,   807,   291,   770,   293,   775,   7715,  776,   7719,  780,   543,
+    803,   7717,  807,   7721,  814,   7723,  817,   7830,  768,   236,   769,
+    237,   770,   238,   771,   297,   772,   299,   774,   301,   776,   239,
+    777,   7881,  780,   464,   783,   521,   785,   523,   803,   7883,  808,
+    303,   816,   7725,  770,   309,   780,   496,   769,   7729,  780,   489,
+    803,   7731,  807,   311,   817,   7733,  769,   314,   780,   318,   803,
+    7735,  807,   316,   813,   7741,  817,   7739,  769,   7743,  775,   7745,
+    803,   7747,  768,   505,   769,   324,   771,   241,   775,   7749,  780,
+    328,   803,   7751,  807,   326,   813,   7755,  817,   7753,  768,   242,
+    769,   243,   770,   244,   771,   245,   772,   333,   774,   335,   775,
+    559,   776,   246,   777,   7887,  779,   337,   780,   466,   783,   525,
+    785,   527,   795,   417,   803,   7885,  808,   491,   769,   7765,  775,
+    7767,  769,   341,   775,   7769,  780,   345,   783,   529,   785,   531,
+    803,   7771,  807,   343,   817,   7775,  769,   347,   770,   349,   775,
+    7777,  780,   353,   803,   7779,  806,   537,   807,   351,   775,   7787,
+    776,   7831,  780,   357,   803,   7789,  806,   539,   807,   355,   813,
+    7793,  817,   7791,  768,   249,   769,   250,   770,   251,   771,   361,
+    772,   363,   774,   365,   776,   252,   777,   7911,  778,   367,   779,
+    369,   780,   468,   783,   533,   785,   535,   795,   432,   803,   7909,
+    804,   7795,  808,   371,   813,   7799,  816,   7797,  771,   7805,  803,
+    7807,  768,   7809,  769,   7811,  770,   373,   775,   7815,  776,   7813,
+    778,   7832,  803,   7817,  775,   7819,  776,   7821,  768,   7923,  769,
+    253,   770,   375,   771,   7929,  772,   563,   775,   7823,  776,   255,
+    777,   7927,  778,   7833,  803,   7925,  769,   378,   770,   7825,  775,
+    380,   780,   382,   803,   7827,  817,   7829,  768,   8173,  769,   901,
+    834,   8129,  768,   7846,  769,   7844,  771,   7850,  777,   7848,  772,
+    478,   769,   506,   769,   508,   772,   482,   769,   7688,  768,   7872,
+    769,   7870,  771,   7876,  777,   7874,  769,   7726,  768,   7890,  769,
+    7888,  771,   7894,  777,   7892,  769,   7756,  772,   556,   776,   7758,
+    772,   554,   769,   510,   768,   475,   769,   471,   772,   469,   780,
+    473,   768,   7847,  769,   7845,  771,   7851,  777,   7849,  772,   479,
+    769,   507,   769,   509,   772,   483,   769,   7689,  768,   7873,  769,
+    7871,  771,   7877,  777,   7875,  769,   7727,  768,   7891,  769,   7889,
+    771,   7895,  777,   7893,  769,   7757,  772,   557,   776,   7759,  772,
+    555,   769,   511,   768,   476,   769,   472,   772,   470,   780,   474,
+    768,   7856,  769,   7854,  771,   7860,  777,   7858,  768,   7857,  769,
+    7855,  771,   7861,  777,   7859,  768,   7700,  769,   7702,  768,   7701,
+    769,   7703,  768,   7760,  769,   7762,  768,   7761,  769,   7763,  775,
+    7780,  775,   7781,  775,   7782,  775,   7783,  769,   7800,  769,   7801,
+    776,   7802,  776,   7803,  775,   7835,  768,   7900,  769,   7898,  771,
+    7904,  777,   7902,  803,   7906,  768,   7901,  769,   7899,  771,   7905,
+    777,   7903,  803,   7907,  768,   7914,  769,   7912,  771,   7918,  777,
+    7916,  803,   7920,  768,   7915,  769,   7913,  771,   7919,  777,   7917,
+    803,   7921,  780,   494,   772,   492,   772,   493,   772,   480,   772,
+    481,   774,   7708,  774,   7709,  772,   560,   772,   561,   780,   495,
+    768,   8122,  769,   902,   772,   8121,  774,   8120,  787,   7944,  788,
+    7945,  837,   8124,  768,   8136,  769,   904,   787,   7960,  788,   7961,
+    768,   8138,  769,   905,   787,   7976,  788,   7977,  837,   8140,  768,
+    8154,  769,   906,   772,   8153,  774,   8152,  776,   938,   787,   7992,
+    788,   7993,  768,   8184,  769,   908,   787,   8008,  788,   8009,  788,
+    8172,  768,   8170,  769,   910,   772,   8169,  774,   8168,  776,   939,
+    788,   8025,  768,   8186,  769,   911,   787,   8040,  788,   8041,  837,
+    8188,  837,   8116,  837,   8132,  768,   8048,  769,   940,   772,   8113,
+    774,   8112,  787,   7936,  788,   7937,  834,   8118,  837,   8115,  768,
+    8050,  769,   941,   787,   7952,  788,   7953,  768,   8052,  769,   942,
+    787,   7968,  788,   7969,  834,   8134,  837,   8131,  768,   8054,  769,
+    943,   772,   8145,  774,   8144,  776,   970,   787,   7984,  788,   7985,
+    834,   8150,  768,   8056,  769,   972,   787,   8000,  788,   8001,  787,
+    8164,  788,   8165,  768,   8058,  769,   973,   772,   8161,  774,   8160,
+    776,   971,   787,   8016,  788,   8017,  834,   8166,  768,   8060,  769,
+    974,   787,   8032,  788,   8033,  834,   8182,  837,   8179,  768,   8146,
+    769,   912,   834,   8151,  768,   8162,  769,   944,   834,   8167,  837,
+    8180,  769,   979,   776,   980,   776,   1031,  774,   1232,  776,   1234,
+    769,   1027,  768,   1024,  774,   1238,  776,   1025,  774,   1217,  776,
+    1244,  776,   1246,  768,   1037,  772,   1250,  774,   1049,  776,   1252,
+    769,   1036,  776,   1254,  772,   1262,  774,   1038,  776,   1264,  779,
+    1266,  776,   1268,  776,   1272,  776,   1260,  774,   1233,  776,   1235,
+    769,   1107,  768,   1104,  774,   1239,  776,   1105,  774,   1218,  776,
+    1245,  776,   1247,  768,   1117,  772,   1251,  774,   1081,  776,   1253,
+    769,   1116,  776,   1255,  772,   1263,  774,   1118,  776,   1265,  779,
+    1267,  776,   1269,  776,   1273,  776,   1261,  776,   1111,  783,   1142,
+    783,   1143,  776,   1242,  776,   1243,  776,   1258,  776,   1259,  1619,
+    1570,  1620,  1571,  1621,  1573,  1620,  1572,  1620,  1574,  1620,  1730,
+    1620,  1747,  1620,  1728,  2364,  2345,  2364,  2353,  2364,  2356,  2494,
+    2507,  2519,  2508,  2878,  2891,  2902,  2888,  2903,  2892,  3031,  2964,
+    3006,  3018,  3031,  3020,  3006,  3019,  3158,  3144,  3285,  3264,  3266,
+    3274,  3285,  3271,  3286,  3272,  3285,  3275,  3390,  3402,  3415,  3404,
+    3390,  3403,  3530,  3546,  3535,  3548,  3551,  3550,  3530,  3549,  4142,
+    4134,  6965,  6918,  6965,  6920,  6965,  6922,  6965,  6924,  6965,  6926,
+    6965,  6930,  6965,  6971,  6965,  6973,  6965,  6976,  6965,  6977,  6965,
+    6979,  772,   7736,  772,   7737,  772,   7772,  772,   7773,  775,   7784,
+    775,   7785,  770,   7852,  774,   7862,  770,   7853,  774,   7863,  770,
+    7878,  770,   7879,  770,   7896,  770,   7897,  768,   7938,  769,   7940,
+    834,   7942,  837,   8064,  768,   7939,  769,   7941,  834,   7943,  837,
+    8065,  837,   8066,  837,   8067,  837,   8068,  837,   8069,  837,   8070,
+    837,   8071,  768,   7946,  769,   7948,  834,   7950,  837,   8072,  768,
+    7947,  769,   7949,  834,   7951,  837,   8073,  837,   8074,  837,   8075,
+    837,   8076,  837,   8077,  837,   8078,  837,   8079,  768,   7954,  769,
+    7956,  768,   7955,  769,   7957,  768,   7962,  769,   7964,  768,   7963,
+    769,   7965,  768,   7970,  769,   7972,  834,   7974,  837,   8080,  768,
+    7971,  769,   7973,  834,   7975,  837,   8081,  837,   8082,  837,   8083,
+    837,   8084,  837,   8085,  837,   8086,  837,   8087,  768,   7978,  769,
+    7980,  834,   7982,  837,   8088,  768,   7979,  769,   7981,  834,   7983,
+    837,   8089,  837,   8090,  837,   8091,  837,   8092,  837,   8093,  837,
+    8094,  837,   8095,  768,   7986,  769,   7988,  834,   7990,  768,   7987,
+    769,   7989,  834,   7991,  768,   7994,  769,   7996,  834,   7998,  768,
+    7995,  769,   7997,  834,   7999,  768,   8002,  769,   8004,  768,   8003,
+    769,   8005,  768,   8010,  769,   8012,  768,   8011,  769,   8013,  768,
+    8018,  769,   8020,  834,   8022,  768,   8019,  769,   8021,  834,   8023,
+    768,   8027,  769,   8029,  834,   8031,  768,   8034,  769,   8036,  834,
+    8038,  837,   8096,  768,   8035,  769,   8037,  834,   8039,  837,   8097,
+    837,   8098,  837,   8099,  837,   8100,  837,   8101,  837,   8102,  837,
+    8103,  768,   8042,  769,   8044,  834,   8046,  837,   8104,  768,   8043,
+    769,   8045,  834,   8047,  837,   8105,  837,   8106,  837,   8107,  837,
+    8108,  837,   8109,  837,   8110,  837,   8111,  837,   8114,  837,   8130,
+    837,   8178,  837,   8119,  768,   8141,  769,   8142,  834,   8143,  837,
+    8135,  837,   8183,  768,   8157,  769,   8158,  834,   8159,  824,   8602,
+    824,   8603,  824,   8622,  824,   8653,  824,   8655,  824,   8654,  824,
+    8708,  824,   8713,  824,   8716,  824,   8740,  824,   8742,  824,   8769,
+    824,   8772,  824,   8775,  824,   8777,  824,   8813,  824,   8802,  824,
+    8816,  824,   8817,  824,   8820,  824,   8821,  824,   8824,  824,   8825,
+    824,   8832,  824,   8833,  824,   8928,  824,   8929,  824,   8836,  824,
+    8837,  824,   8840,  824,   8841,  824,   8930,  824,   8931,  824,   8876,
+    824,   8877,  824,   8878,  824,   8879,  824,   8938,  824,   8939,  824,
+    8940,  824,   8941,  12441, 12436, 12441, 12364, 12441, 12366, 12441, 12368,
+    12441, 12370, 12441, 12372, 12441, 12374, 12441, 12376, 12441, 12378, 12441,
+    12380, 12441, 12382, 12441, 12384, 12441, 12386, 12441, 12389, 12441, 12391,
+    12441, 12393, 12441, 12400, 12442, 12401, 12441, 12403, 12442, 12404, 12441,
+    12406, 12442, 12407, 12441, 12409, 12442, 12410, 12441, 12412, 12442, 12413,
+    12441, 12446, 12441, 12532, 12441, 12460, 12441, 12462, 12441, 12464, 12441,
+    12466, 12441, 12468, 12441, 12470, 12441, 12472, 12441, 12474, 12441, 12476,
+    12441, 12478, 12441, 12480, 12441, 12482, 12441, 12485, 12441, 12487, 12441,
+    12489, 12441, 12496, 12442, 12497, 12441, 12499, 12442, 12500, 12441, 12502,
+    12442, 12503, 12441, 12505, 12442, 12506, 12441, 12508, 12442, 12509, 12441,
+    12535, 12441, 12536, 12441, 12537, 12441, 12538, 12441, 12542, 69818, 69786,
+    69818, 69788, 69818, 69803, 69927, 69934, 69927, 69935, 70462, 70475, 70487,
+    70476, 70832, 70844, 70842, 70843, 70845, 70846, 71087, 71098, 71087, 71099,
+    71984, 71992};
+
+}  // namespace ada::idna
+#endif  // ADA_IDNA_NORMALIZATION_TABLES_H
+/* end file src/normalization_tables.cpp */
+
+namespace ada::idna {
+
+// See
+// https://github.com/uni-algo/uni-algo/blob/c612968c5ed3ace39bde4c894c24286c5f2c7fe2/include/uni_algo/impl/impl_norm.h#L467
+constexpr char32_t hangul_sbase = 0xAC00;
+constexpr char32_t hangul_tbase = 0x11A7;
+constexpr char32_t hangul_vbase = 0x1161;
+constexpr char32_t hangul_lbase = 0x1100;
+constexpr char32_t hangul_lcount = 19;
+constexpr char32_t hangul_vcount = 21;
+constexpr char32_t hangul_tcount = 28;
+constexpr char32_t hangul_ncount = hangul_vcount * hangul_tcount;
+constexpr char32_t hangul_scount =
+    hangul_lcount * hangul_vcount * hangul_tcount;
+
+std::pair<bool, size_t> compute_decomposition_length(
+    const std::u32string_view input) noexcept {
+  bool decomposition_needed{false};
+  size_t additional_elements{0};
+  for (char32_t current_character : input) {
+    size_t decomposition_length{0};
+
+    if (current_character >= hangul_sbase &&
+        current_character < hangul_sbase + hangul_scount) {
+      decomposition_length = 2;
+      if ((current_character - hangul_sbase) % hangul_tcount) {
+        decomposition_length = 3;
+      }
+    } else if (current_character < 0x110000) {
+      const uint8_t di = decomposition_index[current_character >> 8];
+      const uint16_t* const decomposition =
+          decomposition_block[di] + (current_character % 256);
+      decomposition_length = (decomposition[1] >> 2) - (decomposition[0] >> 2);
+      if ((decomposition_length > 0) && (decomposition[0] & 1)) {
+        decomposition_length = 0;
+      }
+    }
+    if (decomposition_length != 0) {
+      decomposition_needed = true;
+      additional_elements += decomposition_length - 1;
+    }
+  }
+  return {decomposition_needed, additional_elements};
+}
+
+void decompose(std::u32string& input, size_t additional_elements) {
+  input.resize(input.size() + additional_elements);
+  for (size_t descending_idx = input.size(),
+              input_count = descending_idx - additional_elements;
+       input_count--;) {
+    if (input[input_count] >= hangul_sbase &&
+        input[input_count] < hangul_sbase + hangul_scount) {
+      // Hangul decomposition.
+      char32_t s_index = input[input_count] - hangul_sbase;
+      if (s_index % hangul_tcount != 0) {
+        input[--descending_idx] = hangul_tbase + s_index % hangul_tcount;
+      }
+      input[--descending_idx] =
+          hangul_vbase + (s_index % hangul_ncount) / hangul_tcount;
+      input[--descending_idx] = hangul_lbase + s_index / hangul_ncount;
+    } else if (input[input_count] < 0x110000) {
+      // Check decomposition_data.
+      const uint16_t* decomposition =
+          decomposition_block[decomposition_index[input[input_count] >> 8]] +
+          (input[input_count] % 256);
+      uint16_t decomposition_length =
+          (decomposition[1] >> 2) - (decomposition[0] >> 2);
+      if (decomposition_length > 0 && (decomposition[0] & 1)) {
+        decomposition_length = 0;
+      }
+      if (decomposition_length > 0) {
+        // Non-recursive decomposition.
+        while (decomposition_length-- > 0) {
+          input[--descending_idx] = decomposition_data[(decomposition[0] >> 2) +
+                                                       decomposition_length];
+        }
+      } else {
+        // No decomposition.
+        input[--descending_idx] = input[input_count];
+      }
+    } else {
+      // Non-Unicode character.
+      input[--descending_idx] = input[input_count];
+    }
+  }
+}
+
+uint8_t get_ccc(char32_t c) noexcept {
+  return c < 0x110000 ? canonical_combining_class_block
+                            [canonical_combining_class_index[c >> 8]][c % 256]
+                      : 0;
+}
+
+void sort_marks(std::u32string& input) {
+  for (size_t idx = 1; idx < input.size(); idx++) {
+    uint8_t ccc = get_ccc(input[idx]);
+    if (ccc == 0) {
+      continue;
+    }  // Skip non-combining characters.
+    auto current_character = input[idx];
+    size_t back_idx = idx;
+    while (back_idx != 0 && get_ccc(input[back_idx - 1]) > ccc) {
+      input[back_idx] = input[back_idx - 1];
+      back_idx--;
+    }
+    input[back_idx] = current_character;
+  }
+}
+
+void decompose_nfc(std::u32string& input) {
+  /**
+   * Decompose the domain_name string to Unicode Normalization Form C.
+   * @see https://www.unicode.org/reports/tr46/#ProcessingStepDecompose
+   */
+  auto [decomposition_needed, additional_elements] =
+      compute_decomposition_length(input);
+  if (decomposition_needed) {
+    decompose(input, additional_elements);
+  }
+  sort_marks(input);
+}
+
+void compose(std::u32string& input) {
+  /**
+   * Compose the domain_name string to Unicode Normalization Form C.
+   * @see https://www.unicode.org/reports/tr46/#ProcessingStepCompose
+   */
+  size_t input_count{0};
+  size_t composition_count{0};
+  for (; input_count < input.size(); input_count++, composition_count++) {
+    input[composition_count] = input[input_count];
+    if (input[input_count] >= hangul_lbase &&
+        input[input_count] < hangul_lbase + hangul_lcount) {
+      if (input_count + 1 < input.size() &&
+          input[input_count + 1] >= hangul_vbase &&
+          input[input_count + 1] < hangul_vbase + hangul_vcount) {
+        input[composition_count] =
+            hangul_sbase +
+            ((input[input_count] - hangul_lbase) * hangul_vcount +
+             input[input_count + 1] - hangul_vbase) *
+                hangul_tcount;
+        input_count++;
+        if (input_count + 1 < input.size() &&
+            input[input_count + 1] > hangul_tbase &&
+            input[input_count + 1] < hangul_tbase + hangul_tcount) {
+          input[composition_count] += input[++input_count] - hangul_tbase;
+        }
+      }
+    } else if (input[input_count] >= hangul_sbase &&
+               input[input_count] < hangul_sbase + hangul_scount) {
+      if ((input[input_count] - hangul_sbase) % hangul_tcount &&
+          input_count + 1 < input.size() &&
+          input[input_count + 1] > hangul_tbase &&
+          input[input_count + 1] < hangul_tbase + hangul_tcount) {
+        input[composition_count] += input[++input_count] - hangul_tbase;
+      }
+    } else if (input[input_count] < 0x110000) {
+      const uint16_t* composition =
+          &composition_block[composition_index[input[input_count] >> 8]]
+                            [input[input_count] % 256];
+      size_t initial_composition_count = composition_count;
+      for (int32_t previous_ccc = -1; input_count + 1 < input.size();
+           input_count++) {
+        uint8_t ccc = get_ccc(input[input_count + 1]);
+
+        if (composition[1] != composition[0] && previous_ccc < ccc) {
+          // Try finding a composition.
+          uint16_t left = composition[0];
+          uint16_t right = composition[1];
+          while (left + 2 < right) {
+            // mean without overflow
+            uint16_t middle = left + (((right - left) >> 1) & ~1);
+            if (composition_data[middle] <= input[input_count + 1]) {
+              left = middle;
+            }
+            if (composition_data[middle] >= input[input_count + 1]) {
+              right = middle;
+            }
+          }
+          if (composition_data[left] == input[input_count + 1]) {
+            input[initial_composition_count] = composition_data[left + 1];
+            composition =
+                &composition_block
+                    [composition_index[composition_data[left + 1] >> 8]]
+                    [composition_data[left + 1] % 256];
+            continue;
+          }
+        }
+
+        if (ccc == 0) {
+          break;
+        }  // Not a combining character.
+        previous_ccc = ccc;
+        input[++composition_count] = input[input_count + 1];
+      }
+    }
+  }
+
+  if (composition_count < input_count) {
+    input.resize(composition_count);
+  }
+}
+
+void normalize(std::u32string& input) {
+  /**
+   * Normalize the domain_name string to Unicode Normalization Form C.
+   * @see https://www.unicode.org/reports/tr46/#ProcessingStepNormalize
+   */
+  decompose_nfc(input);
+  compose(input);
+}
+
+}  // namespace ada::idna
+/* end file src/normalization.cpp */
+/* begin file src/punycode.cpp */
+
+#include <cstdint>
+
+namespace ada::idna {
+
+constexpr int32_t base = 36;
+constexpr int32_t tmin = 1;
+constexpr int32_t tmax = 26;
+constexpr int32_t skew = 38;
+constexpr int32_t damp = 700;
+constexpr int32_t initial_bias = 72;
+constexpr uint32_t initial_n = 128;
+
+static constexpr int32_t char_to_digit_value(char value) {
+  if (value >= 'a' && value <= 'z') return value - 'a';
+  if (value >= '0' && value <= '9') return value - '0' + 26;
+  return -1;
+}
+
+static constexpr char digit_to_char(int32_t digit) {
+  return digit < 26 ? char(digit + 97) : char(digit + 22);
+}
+
+static constexpr int32_t adapt(int32_t d, int32_t n, bool firsttime) {
+  if (firsttime) {
+    d = d / damp;
+  } else {
+    d = d / 2;
+  }
+  d += d / n;
+  int32_t k = 0;
+  while (d > ((base - tmin) * tmax) / 2) {
+    d /= base - tmin;
+    k += base;
+  }
+  return k + (((base - tmin + 1) * d) / (d + skew));
+}
+
+bool punycode_to_utf32(std::string_view input, std::u32string &out) {
+  int32_t written_out{0};
+  out.reserve(out.size() + input.size());
+  uint32_t n = initial_n;
+  int32_t i = 0;
+  int32_t bias = initial_bias;
+  // grab ascii content
+  size_t end_of_ascii = input.find_last_of('-');
+  if (end_of_ascii != std::string_view::npos) {
+    for (uint8_t c : input.substr(0, end_of_ascii)) {
+      if (c >= 0x80) {
+        return false;
+      }
+      out.push_back(c);
+      written_out++;
+    }
+    input.remove_prefix(end_of_ascii + 1);
+  }
+  while (!input.empty()) {
+    int32_t oldi = i;
+    int32_t w = 1;
+    for (int32_t k = base;; k += base) {
+      if (input.empty()) {
+        return false;
+      }
+      uint8_t code_point = input.front();
+      input.remove_prefix(1);
+      int32_t digit = char_to_digit_value(code_point);
+      if (digit < 0) {
+        return false;
+      }
+      if (digit > (0x7fffffff - i) / w) {
+        return false;
+      }
+      i = i + digit * w;
+      int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
+      if (digit < t) {
+        break;
+      }
+      if (w > 0x7fffffff / (base - t)) {
+        return false;
+      }
+      w = w * (base - t);
+    }
+    bias = adapt(i - oldi, written_out + 1, oldi == 0);
+    if (i / (written_out + 1) > int32_t(0x7fffffff - n)) {
+      return false;
+    }
+    n = n + i / (written_out + 1);
+    i = i % (written_out + 1);
+    if (n < 0x80) {
+      return false;
+    }
+    out.insert(out.begin() + i, n);
+    written_out++;
+    ++i;
+  }
+
+  return true;
+}
+
+bool verify_punycode(std::string_view input) {
+  size_t written_out{0};
+  uint32_t n = initial_n;
+  int32_t i = 0;
+  int32_t bias = initial_bias;
+  // grab ascii content
+  size_t end_of_ascii = input.find_last_of('-');
+  if (end_of_ascii != std::string_view::npos) {
+    for (uint8_t c : input.substr(0, end_of_ascii)) {
+      if (c >= 0x80) {
+        return false;
+      }
+      written_out++;
+    }
+    input.remove_prefix(end_of_ascii + 1);
+  }
+  while (!input.empty()) {
+    int32_t oldi = i;
+    int32_t w = 1;
+    for (int32_t k = base;; k += base) {
+      if (input.empty()) {
+        return false;
+      }
+      uint8_t code_point = input.front();
+      input.remove_prefix(1);
+      int32_t digit = char_to_digit_value(code_point);
+      if (digit < 0) {
+        return false;
+      }
+      if (digit > (0x7fffffff - i) / w) {
+        return false;
+      }
+      i = i + digit * w;
+      int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
+      if (digit < t) {
+        break;
+      }
+      if (w > 0x7fffffff / (base - t)) {
+        return false;
+      }
+      w = w * (base - t);
+    }
+    bias = adapt(i - oldi, int32_t(written_out + 1), oldi == 0);
+    if (i / (written_out + 1) > 0x7fffffff - n) {
+      return false;
+    }
+    n = n + i / int32_t(written_out + 1);
+    i = i % int32_t(written_out + 1);
+    if (n < 0x80) {
+      return false;
+    }
+    written_out++;
+    ++i;
+  }
+
+  return true;
+}
+
+bool utf32_to_punycode(std::u32string_view input, std::string &out) {
+  out.reserve(input.size() + out.size());
+  uint32_t n = initial_n;
+  int32_t d = 0;
+  int32_t bias = initial_bias;
+  size_t h = 0;
+  // first push the ascii content
+  for (uint32_t c : input) {
+    if (c < 0x80) {
+      ++h;
+      out.push_back(char(c));
+    }
+    if (c > 0x10ffff || (c >= 0xd880 && c < 0xe000)) {
+      return false;
+    }
+  }
+  size_t b = h;
+  if (b > 0) {
+    out.push_back('-');
+  }
+  while (h < input.size()) {
+    uint32_t m = 0x10FFFF;
+    for (auto code_point : input) {
+      if (code_point >= n && code_point < m) m = code_point;
+    }
+
+    if ((m - n) > (0x7fffffff - d) / (h + 1)) {
+      return false;
+    }
+    d = d + int32_t((m - n) * (h + 1));
+    n = m;
+    for (auto c : input) {
+      if (c < n) {
+        if (d == 0x7fffffff) {
+          return false;
+        }
+        ++d;
+      }
+      if (c == n) {
+        int32_t q = d;
+        for (int32_t k = base;; k += base) {
+          int32_t t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
+
+          if (q < t) {
+            break;
+          }
+          out.push_back(digit_to_char(t + ((q - t) % (base - t))));
+          q = (q - t) / (base - t);
+        }
+        out.push_back(digit_to_char(q));
+        bias = adapt(d, int32_t(h + 1), h == b);
+        d = 0;
+        ++h;
+      }
+    }
+    ++d;
+    ++n;
+  }
+  return true;
+}
+
+}  // namespace ada::idna
+/* end file src/punycode.cpp */
+/* begin file src/validity.cpp */
+#include <algorithm>
+#include <string_view>
+
+namespace ada::idna {
+
+enum direction : uint8_t {
+  NONE,
+  BN,
+  CS,
+  ES,
+  ON,
+  EN,
+  L,
+  R,
+  NSM,
+  AL,
+  AN,
+  ET,
+  WS,
+  RLO,
+  LRO,
+  PDF,
+  RLE,
+  RLI,
+  FSI,
+  PDI,
+  LRI,
+  B,
+  S,
+  LRE
+};
+
+struct directions {
+  uint32_t start_code;
+  uint32_t final_code;
+  direction direct;
+};
+
+static directions dir_table[] = {
+    {0x0, 0x8, direction::BN},          {0x9, 0x9, direction::S},
+    {0xa, 0xa, direction::B},           {0xb, 0xb, direction::S},
+    {0xc, 0xc, direction::WS},          {0xd, 0xd, direction::B},
+    {0xe, 0x1b, direction::BN},         {0x1c, 0x1e, direction::B},
+    {0x1f, 0x1f, direction::S},         {0x20, 0x20, direction::WS},
+    {0x21, 0x22, direction::ON},        {0x23, 0x25, direction::ET},
+    {0x26, 0x2a, direction::ON},        {0x2b, 0x2b, direction::ES},
+    {0x2c, 0x2c, direction::CS},        {0x2d, 0x2d, direction::ES},
+    {0x2e, 0x2f, direction::CS},        {0x30, 0x39, direction::EN},
+    {0x3a, 0x3a, direction::CS},        {0x3b, 0x40, direction::ON},
+    {0x41, 0x5a, direction::L},         {0x5b, 0x60, direction::ON},
+    {0x61, 0x7a, direction::L},         {0x7b, 0x7e, direction::ON},
+    {0x7f, 0x84, direction::BN},        {0x85, 0x85, direction::B},
+    {0x86, 0x9f, direction::BN},        {0xa0, 0xa0, direction::CS},
+    {0xa1, 0xa1, direction::ON},        {0xa2, 0xa5, direction::ET},
+    {0xa6, 0xa9, direction::ON},        {0xaa, 0xaa, direction::L},
+    {0xab, 0xac, direction::ON},        {0xad, 0xad, direction::BN},
+    {0xae, 0xaf, direction::ON},        {0xb0, 0xb1, direction::ET},
+    {0xb2, 0xb3, direction::EN},        {0xb4, 0xb4, direction::ON},
+    {0xb5, 0xb5, direction::L},         {0xb6, 0xb8, direction::ON},
+    {0xb9, 0xb9, direction::EN},        {0xba, 0xba, direction::L},
+    {0xbb, 0xbf, direction::ON},        {0xc0, 0xd6, direction::L},
+    {0xd7, 0xd7, direction::ON},        {0xd8, 0xf6, direction::L},
+    {0xf7, 0xf7, direction::ON},        {0xf8, 0x2b8, direction::L},
+    {0x2b9, 0x2ba, direction::ON},      {0x2bb, 0x2c1, direction::L},
+    {0x2c2, 0x2cf, direction::ON},      {0x2d0, 0x2d1, direction::L},
+    {0x2d2, 0x2df, direction::ON},      {0x2e0, 0x2e4, direction::L},
+    {0x2e5, 0x2ed, direction::ON},      {0x2ee, 0x2ee, direction::L},
+    {0x2ef, 0x2ff, direction::ON},      {0x300, 0x36f, direction::NSM},
+    {0x370, 0x373, direction::L},       {0x374, 0x375, direction::ON},
+    {0x376, 0x377, direction::L},       {0x37a, 0x37d, direction::L},
+    {0x37e, 0x37e, direction::ON},      {0x37f, 0x37f, direction::L},
+    {0x384, 0x385, direction::ON},      {0x386, 0x386, direction::L},
+    {0x387, 0x387, direction::ON},      {0x388, 0x38a, direction::L},
+    {0x38c, 0x38c, direction::L},       {0x38e, 0x3a1, direction::L},
+    {0x3a3, 0x3f5, direction::L},       {0x3f6, 0x3f6, direction::ON},
+    {0x3f7, 0x482, direction::L},       {0x483, 0x489, direction::NSM},
+    {0x48a, 0x52f, direction::L},       {0x531, 0x556, direction::L},
+    {0x559, 0x589, direction::L},       {0x58a, 0x58a, direction::ON},
+    {0x58d, 0x58e, direction::ON},      {0x58f, 0x58f, direction::ET},
+    {0x591, 0x5bd, direction::NSM},     {0x5be, 0x5be, direction::R},
+    {0x5bf, 0x5bf, direction::NSM},     {0x5c0, 0x5c0, direction::R},
+    {0x5c1, 0x5c2, direction::NSM},     {0x5c3, 0x5c3, direction::R},
+    {0x5c4, 0x5c5, direction::NSM},     {0x5c6, 0x5c6, direction::R},
+    {0x5c7, 0x5c7, direction::NSM},     {0x5d0, 0x5ea, direction::R},
+    {0x5ef, 0x5f4, direction::R},       {0x600, 0x605, direction::AN},
+    {0x606, 0x607, direction::ON},      {0x608, 0x608, direction::AL},
+    {0x609, 0x60a, direction::ET},      {0x60b, 0x60b, direction::AL},
+    {0x60c, 0x60c, direction::CS},      {0x60d, 0x60d, direction::AL},
+    {0x60e, 0x60f, direction::ON},      {0x610, 0x61a, direction::NSM},
+    {0x61b, 0x61c, direction::AL},      {0x61e, 0x64a, direction::AL},
+    {0x64b, 0x65f, direction::NSM},     {0x660, 0x669, direction::AN},
+    {0x66a, 0x66a, direction::ET},      {0x66b, 0x66c, direction::AN},
+    {0x66d, 0x66f, direction::AL},      {0x670, 0x670, direction::NSM},
+    {0x671, 0x6d5, direction::AL},      {0x6d6, 0x6dc, direction::NSM},
+    {0x6dd, 0x6dd, direction::AN},      {0x6de, 0x6de, direction::ON},
+    {0x6df, 0x6e4, direction::NSM},     {0x6e5, 0x6e6, direction::AL},
+    {0x6e7, 0x6e8, direction::NSM},     {0x6e9, 0x6e9, direction::ON},
+    {0x6ea, 0x6ed, direction::NSM},     {0x6ee, 0x6ef, direction::AL},
+    {0x6f0, 0x6f9, direction::EN},      {0x6fa, 0x70d, direction::AL},
+    {0x70f, 0x710, direction::AL},      {0x711, 0x711, direction::NSM},
+    {0x712, 0x72f, direction::AL},      {0x730, 0x74a, direction::NSM},
+    {0x74d, 0x7a5, direction::AL},      {0x7a6, 0x7b0, direction::NSM},
+    {0x7b1, 0x7b1, direction::AL},      {0x7c0, 0x7ea, direction::R},
+    {0x7eb, 0x7f3, direction::NSM},     {0x7f4, 0x7f5, direction::R},
+    {0x7f6, 0x7f9, direction::ON},      {0x7fa, 0x7fa, direction::R},
+    {0x7fd, 0x7fd, direction::NSM},     {0x7fe, 0x815, direction::R},
+    {0x816, 0x819, direction::NSM},     {0x81a, 0x81a, direction::R},
+    {0x81b, 0x823, direction::NSM},     {0x824, 0x824, direction::R},
+    {0x825, 0x827, direction::NSM},     {0x828, 0x828, direction::R},
+    {0x829, 0x82d, direction::NSM},     {0x830, 0x83e, direction::R},
+    {0x840, 0x858, direction::R},       {0x859, 0x85b, direction::NSM},
+    {0x85e, 0x85e, direction::R},       {0x860, 0x86a, direction::AL},
+    {0x8a0, 0x8b4, direction::AL},      {0x8b6, 0x8c7, direction::AL},
+    {0x8d3, 0x8e1, direction::NSM},     {0x8e2, 0x8e2, direction::AN},
+    {0x8e3, 0x902, direction::NSM},     {0x903, 0x939, direction::L},
+    {0x93a, 0x93a, direction::NSM},     {0x93b, 0x93b, direction::L},
+    {0x93c, 0x93c, direction::NSM},     {0x93d, 0x940, direction::L},
+    {0x941, 0x948, direction::NSM},     {0x949, 0x94c, direction::L},
+    {0x94d, 0x94d, direction::NSM},     {0x94e, 0x950, direction::L},
+    {0x951, 0x957, direction::NSM},     {0x958, 0x961, direction::L},
+    {0x962, 0x963, direction::NSM},     {0x964, 0x980, direction::L},
+    {0x981, 0x981, direction::NSM},     {0x982, 0x983, direction::L},
+    {0x985, 0x98c, direction::L},       {0x98f, 0x990, direction::L},
+    {0x993, 0x9a8, direction::L},       {0x9aa, 0x9b0, direction::L},
+    {0x9b2, 0x9b2, direction::L},       {0x9b6, 0x9b9, direction::L},
+    {0x9bc, 0x9bc, direction::NSM},     {0x9bd, 0x9c0, direction::L},
+    {0x9c1, 0x9c4, direction::NSM},     {0x9c7, 0x9c8, direction::L},
+    {0x9cb, 0x9cc, direction::L},       {0x9cd, 0x9cd, direction::NSM},
+    {0x9ce, 0x9ce, direction::L},       {0x9d7, 0x9d7, direction::L},
+    {0x9dc, 0x9dd, direction::L},       {0x9df, 0x9e1, direction::L},
+    {0x9e2, 0x9e3, direction::NSM},     {0x9e6, 0x9f1, direction::L},
+    {0x9f2, 0x9f3, direction::ET},      {0x9f4, 0x9fa, direction::L},
+    {0x9fb, 0x9fb, direction::ET},      {0x9fc, 0x9fd, direction::L},
+    {0x9fe, 0x9fe, direction::NSM},     {0xa01, 0xa02, direction::NSM},
+    {0xa03, 0xa03, direction::L},       {0xa05, 0xa0a, direction::L},
+    {0xa0f, 0xa10, direction::L},       {0xa13, 0xa28, direction::L},
+    {0xa2a, 0xa30, direction::L},       {0xa32, 0xa33, direction::L},
+    {0xa35, 0xa36, direction::L},       {0xa38, 0xa39, direction::L},
+    {0xa3c, 0xa3c, direction::NSM},     {0xa3e, 0xa40, direction::L},
+    {0xa41, 0xa42, direction::NSM},     {0xa47, 0xa48, direction::NSM},
+    {0xa4b, 0xa4d, direction::NSM},     {0xa51, 0xa51, direction::NSM},
+    {0xa59, 0xa5c, direction::L},       {0xa5e, 0xa5e, direction::L},
+    {0xa66, 0xa6f, direction::L},       {0xa70, 0xa71, direction::NSM},
+    {0xa72, 0xa74, direction::L},       {0xa75, 0xa75, direction::NSM},
+    {0xa76, 0xa76, direction::L},       {0xa81, 0xa82, direction::NSM},
+    {0xa83, 0xa83, direction::L},       {0xa85, 0xa8d, direction::L},
+    {0xa8f, 0xa91, direction::L},       {0xa93, 0xaa8, direction::L},
+    {0xaaa, 0xab0, direction::L},       {0xab2, 0xab3, direction::L},
+    {0xab5, 0xab9, direction::L},       {0xabc, 0xabc, direction::NSM},
+    {0xabd, 0xac0, direction::L},       {0xac1, 0xac5, direction::NSM},
+    {0xac7, 0xac8, direction::NSM},     {0xac9, 0xac9, direction::L},
+    {0xacb, 0xacc, direction::L},       {0xacd, 0xacd, direction::NSM},
+    {0xad0, 0xad0, direction::L},       {0xae0, 0xae1, direction::L},
+    {0xae2, 0xae3, direction::NSM},     {0xae6, 0xaf0, direction::L},
+    {0xaf1, 0xaf1, direction::ET},      {0xaf9, 0xaf9, direction::L},
+    {0xafa, 0xaff, direction::NSM},     {0xb01, 0xb01, direction::NSM},
+    {0xb02, 0xb03, direction::L},       {0xb05, 0xb0c, direction::L},
+    {0xb0f, 0xb10, direction::L},       {0xb13, 0xb28, direction::L},
+    {0xb2a, 0xb30, direction::L},       {0xb32, 0xb33, direction::L},
+    {0xb35, 0xb39, direction::L},       {0xb3c, 0xb3c, direction::NSM},
+    {0xb3d, 0xb3e, direction::L},       {0xb3f, 0xb3f, direction::NSM},
+    {0xb40, 0xb40, direction::L},       {0xb41, 0xb44, direction::NSM},
+    {0xb47, 0xb48, direction::L},       {0xb4b, 0xb4c, direction::L},
+    {0xb4d, 0xb4d, direction::NSM},     {0xb55, 0xb56, direction::NSM},
+    {0xb57, 0xb57, direction::L},       {0xb5c, 0xb5d, direction::L},
+    {0xb5f, 0xb61, direction::L},       {0xb62, 0xb63, direction::NSM},
+    {0xb66, 0xb77, direction::L},       {0xb82, 0xb82, direction::NSM},
+    {0xb83, 0xb83, direction::L},       {0xb85, 0xb8a, direction::L},
+    {0xb8e, 0xb90, direction::L},       {0xb92, 0xb95, direction::L},
+    {0xb99, 0xb9a, direction::L},       {0xb9c, 0xb9c, direction::L},
+    {0xb9e, 0xb9f, direction::L},       {0xba3, 0xba4, direction::L},
+    {0xba8, 0xbaa, direction::L},       {0xbae, 0xbb9, direction::L},
+    {0xbbe, 0xbbf, direction::L},       {0xbc0, 0xbc0, direction::NSM},
+    {0xbc1, 0xbc2, direction::L},       {0xbc6, 0xbc8, direction::L},
+    {0xbca, 0xbcc, direction::L},       {0xbcd, 0xbcd, direction::NSM},
+    {0xbd0, 0xbd0, direction::L},       {0xbd7, 0xbd7, direction::L},
+    {0xbe6, 0xbf2, direction::L},       {0xbf3, 0xbf8, direction::ON},
+    {0xbf9, 0xbf9, direction::ET},      {0xbfa, 0xbfa, direction::ON},
+    {0xc00, 0xc00, direction::NSM},     {0xc01, 0xc03, direction::L},
+    {0xc04, 0xc04, direction::NSM},     {0xc05, 0xc0c, direction::L},
+    {0xc0e, 0xc10, direction::L},       {0xc12, 0xc28, direction::L},
+    {0xc2a, 0xc39, direction::L},       {0xc3d, 0xc3d, direction::L},
+    {0xc3e, 0xc40, direction::NSM},     {0xc41, 0xc44, direction::L},
+    {0xc46, 0xc48, direction::NSM},     {0xc4a, 0xc4d, direction::NSM},
+    {0xc55, 0xc56, direction::NSM},     {0xc58, 0xc5a, direction::L},
+    {0xc60, 0xc61, direction::L},       {0xc62, 0xc63, direction::NSM},
+    {0xc66, 0xc6f, direction::L},       {0xc77, 0xc77, direction::L},
+    {0xc78, 0xc7e, direction::ON},      {0xc7f, 0xc80, direction::L},
+    {0xc81, 0xc81, direction::NSM},     {0xc82, 0xc8c, direction::L},
+    {0xc8e, 0xc90, direction::L},       {0xc92, 0xca8, direction::L},
+    {0xcaa, 0xcb3, direction::L},       {0xcb5, 0xcb9, direction::L},
+    {0xcbc, 0xcbc, direction::NSM},     {0xcbd, 0xcc4, direction::L},
+    {0xcc6, 0xcc8, direction::L},       {0xcca, 0xccb, direction::L},
+    {0xccc, 0xccd, direction::NSM},     {0xcd5, 0xcd6, direction::L},
+    {0xcde, 0xcde, direction::L},       {0xce0, 0xce1, direction::L},
+    {0xce2, 0xce3, direction::NSM},     {0xce6, 0xcef, direction::L},
+    {0xcf1, 0xcf2, direction::L},       {0xd00, 0xd01, direction::NSM},
+    {0xd02, 0xd0c, direction::L},       {0xd0e, 0xd10, direction::L},
+    {0xd12, 0xd3a, direction::L},       {0xd3b, 0xd3c, direction::NSM},
+    {0xd3d, 0xd40, direction::L},       {0xd41, 0xd44, direction::NSM},
+    {0xd46, 0xd48, direction::L},       {0xd4a, 0xd4c, direction::L},
+    {0xd4d, 0xd4d, direction::NSM},     {0xd4e, 0xd4f, direction::L},
+    {0xd54, 0xd61, direction::L},       {0xd62, 0xd63, direction::NSM},
+    {0xd66, 0xd7f, direction::L},       {0xd81, 0xd81, direction::NSM},
+    {0xd82, 0xd83, direction::L},       {0xd85, 0xd96, direction::L},
+    {0xd9a, 0xdb1, direction::L},       {0xdb3, 0xdbb, direction::L},
+    {0xdbd, 0xdbd, direction::L},       {0xdc0, 0xdc6, direction::L},
+    {0xdca, 0xdca, direction::NSM},     {0xdcf, 0xdd1, direction::L},
+    {0xdd2, 0xdd4, direction::NSM},     {0xdd6, 0xdd6, direction::NSM},
+    {0xdd8, 0xddf, direction::L},       {0xde6, 0xdef, direction::L},
+    {0xdf2, 0xdf4, direction::L},       {0xe01, 0xe30, direction::L},
+    {0xe31, 0xe31, direction::NSM},     {0xe32, 0xe33, direction::L},
+    {0xe34, 0xe3a, direction::NSM},     {0xe3f, 0xe3f, direction::ET},
+    {0xe40, 0xe46, direction::L},       {0xe47, 0xe4e, direction::NSM},
+    {0xe4f, 0xe5b, direction::L},       {0xe81, 0xe82, direction::L},
+    {0xe84, 0xe84, direction::L},       {0xe86, 0xe8a, direction::L},
+    {0xe8c, 0xea3, direction::L},       {0xea5, 0xea5, direction::L},
+    {0xea7, 0xeb0, direction::L},       {0xeb1, 0xeb1, direction::NSM},
+    {0xeb2, 0xeb3, direction::L},       {0xeb4, 0xebc, direction::NSM},
+    {0xebd, 0xebd, direction::L},       {0xec0, 0xec4, direction::L},
+    {0xec6, 0xec6, direction::L},       {0xec8, 0xecd, direction::NSM},
+    {0xed0, 0xed9, direction::L},       {0xedc, 0xedf, direction::L},
+    {0xf00, 0xf17, direction::L},       {0xf18, 0xf19, direction::NSM},
+    {0xf1a, 0xf34, direction::L},       {0xf35, 0xf35, direction::NSM},
+    {0xf36, 0xf36, direction::L},       {0xf37, 0xf37, direction::NSM},
+    {0xf38, 0xf38, direction::L},       {0xf39, 0xf39, direction::NSM},
+    {0xf3a, 0xf3d, direction::ON},      {0xf3e, 0xf47, direction::L},
+    {0xf49, 0xf6c, direction::L},       {0xf71, 0xf7e, direction::NSM},
+    {0xf7f, 0xf7f, direction::L},       {0xf80, 0xf84, direction::NSM},
+    {0xf85, 0xf85, direction::L},       {0xf86, 0xf87, direction::NSM},
+    {0xf88, 0xf8c, direction::L},       {0xf8d, 0xf97, direction::NSM},
+    {0xf99, 0xfbc, direction::NSM},     {0xfbe, 0xfc5, direction::L},
+    {0xfc6, 0xfc6, direction::NSM},     {0xfc7, 0xfcc, direction::L},
+    {0xfce, 0xfda, direction::L},       {0x1000, 0x102c, direction::L},
+    {0x102d, 0x1030, direction::NSM},   {0x1031, 0x1031, direction::L},
+    {0x1032, 0x1037, direction::NSM},   {0x1038, 0x1038, direction::L},
+    {0x1039, 0x103a, direction::NSM},   {0x103b, 0x103c, direction::L},
+    {0x103d, 0x103e, direction::NSM},   {0x103f, 0x1057, direction::L},
+    {0x1058, 0x1059, direction::NSM},   {0x105a, 0x105d, direction::L},
+    {0x105e, 0x1060, direction::NSM},   {0x1061, 0x1070, direction::L},
+    {0x1071, 0x1074, direction::NSM},   {0x1075, 0x1081, direction::L},
+    {0x1082, 0x1082, direction::NSM},   {0x1083, 0x1084, direction::L},
+    {0x1085, 0x1086, direction::NSM},   {0x1087, 0x108c, direction::L},
+    {0x108d, 0x108d, direction::NSM},   {0x108e, 0x109c, direction::L},
+    {0x109d, 0x109d, direction::NSM},   {0x109e, 0x10c5, direction::L},
+    {0x10c7, 0x10c7, direction::L},     {0x10cd, 0x10cd, direction::L},
+    {0x10d0, 0x1248, direction::L},     {0x124a, 0x124d, direction::L},
+    {0x1250, 0x1256, direction::L},     {0x1258, 0x1258, direction::L},
+    {0x125a, 0x125d, direction::L},     {0x1260, 0x1288, direction::L},
+    {0x128a, 0x128d, direction::L},     {0x1290, 0x12b0, direction::L},
+    {0x12b2, 0x12b5, direction::L},     {0x12b8, 0x12be, direction::L},
+    {0x12c0, 0x12c0, direction::L},     {0x12c2, 0x12c5, direction::L},
+    {0x12c8, 0x12d6, direction::L},     {0x12d8, 0x1310, direction::L},
+    {0x1312, 0x1315, direction::L},     {0x1318, 0x135a, direction::L},
+    {0x135d, 0x135f, direction::NSM},   {0x1360, 0x137c, direction::L},
+    {0x1380, 0x138f, direction::L},     {0x1390, 0x1399, direction::ON},
+    {0x13a0, 0x13f5, direction::L},     {0x13f8, 0x13fd, direction::L},
+    {0x1400, 0x1400, direction::ON},    {0x1401, 0x167f, direction::L},
+    {0x1680, 0x1680, direction::WS},    {0x1681, 0x169a, direction::L},
+    {0x169b, 0x169c, direction::ON},    {0x16a0, 0x16f8, direction::L},
+    {0x1700, 0x170c, direction::L},     {0x170e, 0x1711, direction::L},
+    {0x1712, 0x1714, direction::NSM},   {0x1720, 0x1731, direction::L},
+    {0x1732, 0x1734, direction::NSM},   {0x1735, 0x1736, direction::L},
+    {0x1740, 0x1751, direction::L},     {0x1752, 0x1753, direction::NSM},
+    {0x1760, 0x176c, direction::L},     {0x176e, 0x1770, direction::L},
+    {0x1772, 0x1773, direction::NSM},   {0x1780, 0x17b3, direction::L},
+    {0x17b4, 0x17b5, direction::NSM},   {0x17b6, 0x17b6, direction::L},
+    {0x17b7, 0x17bd, direction::NSM},   {0x17be, 0x17c5, direction::L},
+    {0x17c6, 0x17c6, direction::NSM},   {0x17c7, 0x17c8, direction::L},
+    {0x17c9, 0x17d3, direction::NSM},   {0x17d4, 0x17da, direction::L},
+    {0x17db, 0x17db, direction::ET},    {0x17dc, 0x17dc, direction::L},
+    {0x17dd, 0x17dd, direction::NSM},   {0x17e0, 0x17e9, direction::L},
+    {0x17f0, 0x17f9, direction::ON},    {0x1800, 0x180a, direction::ON},
+    {0x180b, 0x180d, direction::NSM},   {0x180e, 0x180e, direction::BN},
+    {0x1810, 0x1819, direction::L},     {0x1820, 0x1878, direction::L},
+    {0x1880, 0x1884, direction::L},     {0x1885, 0x1886, direction::NSM},
+    {0x1887, 0x18a8, direction::L},     {0x18a9, 0x18a9, direction::NSM},
+    {0x18aa, 0x18aa, direction::L},     {0x18b0, 0x18f5, direction::L},
+    {0x1900, 0x191e, direction::L},     {0x1920, 0x1922, direction::NSM},
+    {0x1923, 0x1926, direction::L},     {0x1927, 0x1928, direction::NSM},
+    {0x1929, 0x192b, direction::L},     {0x1930, 0x1931, direction::L},
+    {0x1932, 0x1932, direction::NSM},   {0x1933, 0x1938, direction::L},
+    {0x1939, 0x193b, direction::NSM},   {0x1940, 0x1940, direction::ON},
+    {0x1944, 0x1945, direction::ON},    {0x1946, 0x196d, direction::L},
+    {0x1970, 0x1974, direction::L},     {0x1980, 0x19ab, direction::L},
+    {0x19b0, 0x19c9, direction::L},     {0x19d0, 0x19da, direction::L},
+    {0x19de, 0x19ff, direction::ON},    {0x1a00, 0x1a16, direction::L},
+    {0x1a17, 0x1a18, direction::NSM},   {0x1a19, 0x1a1a, direction::L},
+    {0x1a1b, 0x1a1b, direction::NSM},   {0x1a1e, 0x1a55, direction::L},
+    {0x1a56, 0x1a56, direction::NSM},   {0x1a57, 0x1a57, direction::L},
+    {0x1a58, 0x1a5e, direction::NSM},   {0x1a60, 0x1a60, direction::NSM},
+    {0x1a61, 0x1a61, direction::L},     {0x1a62, 0x1a62, direction::NSM},
+    {0x1a63, 0x1a64, direction::L},     {0x1a65, 0x1a6c, direction::NSM},
+    {0x1a6d, 0x1a72, direction::L},     {0x1a73, 0x1a7c, direction::NSM},
+    {0x1a7f, 0x1a7f, direction::NSM},   {0x1a80, 0x1a89, direction::L},
+    {0x1a90, 0x1a99, direction::L},     {0x1aa0, 0x1aad, direction::L},
+    {0x1ab0, 0x1ac0, direction::NSM},   {0x1b00, 0x1b03, direction::NSM},
+    {0x1b04, 0x1b33, direction::L},     {0x1b34, 0x1b34, direction::NSM},
+    {0x1b35, 0x1b35, direction::L},     {0x1b36, 0x1b3a, direction::NSM},
+    {0x1b3b, 0x1b3b, direction::L},     {0x1b3c, 0x1b3c, direction::NSM},
+    {0x1b3d, 0x1b41, direction::L},     {0x1b42, 0x1b42, direction::NSM},
+    {0x1b43, 0x1b4b, direction::L},     {0x1b50, 0x1b6a, direction::L},
+    {0x1b6b, 0x1b73, direction::NSM},   {0x1b74, 0x1b7c, direction::L},
+    {0x1b80, 0x1b81, direction::NSM},   {0x1b82, 0x1ba1, direction::L},
+    {0x1ba2, 0x1ba5, direction::NSM},   {0x1ba6, 0x1ba7, direction::L},
+    {0x1ba8, 0x1ba9, direction::NSM},   {0x1baa, 0x1baa, direction::L},
+    {0x1bab, 0x1bad, direction::NSM},   {0x1bae, 0x1be5, direction::L},
+    {0x1be6, 0x1be6, direction::NSM},   {0x1be7, 0x1be7, direction::L},
+    {0x1be8, 0x1be9, direction::NSM},   {0x1bea, 0x1bec, direction::L},
+    {0x1bed, 0x1bed, direction::NSM},   {0x1bee, 0x1bee, direction::L},
+    {0x1bef, 0x1bf1, direction::NSM},   {0x1bf2, 0x1bf3, direction::L},
+    {0x1bfc, 0x1c2b, direction::L},     {0x1c2c, 0x1c33, direction::NSM},
+    {0x1c34, 0x1c35, direction::L},     {0x1c36, 0x1c37, direction::NSM},
+    {0x1c3b, 0x1c49, direction::L},     {0x1c4d, 0x1c88, direction::L},
+    {0x1c90, 0x1cba, direction::L},     {0x1cbd, 0x1cc7, direction::L},
+    {0x1cd0, 0x1cd2, direction::NSM},   {0x1cd3, 0x1cd3, direction::L},
+    {0x1cd4, 0x1ce0, direction::NSM},   {0x1ce1, 0x1ce1, direction::L},
+    {0x1ce2, 0x1ce8, direction::NSM},   {0x1ce9, 0x1cec, direction::L},
+    {0x1ced, 0x1ced, direction::NSM},   {0x1cee, 0x1cf3, direction::L},
+    {0x1cf4, 0x1cf4, direction::NSM},   {0x1cf5, 0x1cf7, direction::L},
+    {0x1cf8, 0x1cf9, direction::NSM},   {0x1cfa, 0x1cfa, direction::L},
+    {0x1d00, 0x1dbf, direction::L},     {0x1dc0, 0x1df9, direction::NSM},
+    {0x1dfb, 0x1dff, direction::NSM},   {0x1e00, 0x1f15, direction::L},
+    {0x1f18, 0x1f1d, direction::L},     {0x1f20, 0x1f45, direction::L},
+    {0x1f48, 0x1f4d, direction::L},     {0x1f50, 0x1f57, direction::L},
+    {0x1f59, 0x1f59, direction::L},     {0x1f5b, 0x1f5b, direction::L},
+    {0x1f5d, 0x1f5d, direction::L},     {0x1f5f, 0x1f7d, direction::L},
+    {0x1f80, 0x1fb4, direction::L},     {0x1fb6, 0x1fbc, direction::L},
+    {0x1fbd, 0x1fbd, direction::ON},    {0x1fbe, 0x1fbe, direction::L},
+    {0x1fbf, 0x1fc1, direction::ON},    {0x1fc2, 0x1fc4, direction::L},
+    {0x1fc6, 0x1fcc, direction::L},     {0x1fcd, 0x1fcf, direction::ON},
+    {0x1fd0, 0x1fd3, direction::L},     {0x1fd6, 0x1fdb, direction::L},
+    {0x1fdd, 0x1fdf, direction::ON},    {0x1fe0, 0x1fec, direction::L},
+    {0x1fed, 0x1fef, direction::ON},    {0x1ff2, 0x1ff4, direction::L},
+    {0x1ff6, 0x1ffc, direction::L},     {0x1ffd, 0x1ffe, direction::ON},
+    {0x2000, 0x200a, direction::WS},    {0x200b, 0x200d, direction::BN},
+    {0x200e, 0x200e, direction::L},     {0x200f, 0x200f, direction::R},
+    {0x2010, 0x2027, direction::ON},    {0x2028, 0x2028, direction::WS},
+    {0x2029, 0x2029, direction::B},     {0x202a, 0x202a, direction::LRE},
+    {0x202b, 0x202b, direction::RLE},   {0x202c, 0x202c, direction::PDF},
+    {0x202d, 0x202d, direction::LRO},   {0x202e, 0x202e, direction::RLO},
+    {0x202f, 0x202f, direction::CS},    {0x2030, 0x2034, direction::ET},
+    {0x2035, 0x2043, direction::ON},    {0x2044, 0x2044, direction::CS},
+    {0x2045, 0x205e, direction::ON},    {0x205f, 0x205f, direction::WS},
+    {0x2060, 0x2064, direction::BN},    {0x2066, 0x2066, direction::LRI},
+    {0x2067, 0x2067, direction::RLI},   {0x2068, 0x2068, direction::FSI},
+    {0x2069, 0x2069, direction::PDI},   {0x206a, 0x206f, direction::BN},
+    {0x2070, 0x2070, direction::EN},    {0x2071, 0x2071, direction::L},
+    {0x2074, 0x2079, direction::EN},    {0x207a, 0x207b, direction::ES},
+    {0x207c, 0x207e, direction::ON},    {0x207f, 0x207f, direction::L},
+    {0x2080, 0x2089, direction::EN},    {0x208a, 0x208b, direction::ES},
+    {0x208c, 0x208e, direction::ON},    {0x2090, 0x209c, direction::L},
+    {0x20a0, 0x20bf, direction::ET},    {0x20d0, 0x20f0, direction::NSM},
+    {0x2100, 0x2101, direction::ON},    {0x2102, 0x2102, direction::L},
+    {0x2103, 0x2106, direction::ON},    {0x2107, 0x2107, direction::L},
+    {0x2108, 0x2109, direction::ON},    {0x210a, 0x2113, direction::L},
+    {0x2114, 0x2114, direction::ON},    {0x2115, 0x2115, direction::L},
+    {0x2116, 0x2118, direction::ON},    {0x2119, 0x211d, direction::L},
+    {0x211e, 0x2123, direction::ON},    {0x2124, 0x2124, direction::L},
+    {0x2125, 0x2125, direction::ON},    {0x2126, 0x2126, direction::L},
+    {0x2127, 0x2127, direction::ON},    {0x2128, 0x2128, direction::L},
+    {0x2129, 0x2129, direction::ON},    {0x212a, 0x212d, direction::L},
+    {0x212e, 0x212e, direction::ET},    {0x212f, 0x2139, direction::L},
+    {0x213a, 0x213b, direction::ON},    {0x213c, 0x213f, direction::L},
+    {0x2140, 0x2144, direction::ON},    {0x2145, 0x2149, direction::L},
+    {0x214a, 0x214d, direction::ON},    {0x214e, 0x214f, direction::L},
+    {0x2150, 0x215f, direction::ON},    {0x2160, 0x2188, direction::L},
+    {0x2189, 0x218b, direction::ON},    {0x2190, 0x2211, direction::ON},
+    {0x2212, 0x2212, direction::ES},    {0x2213, 0x2213, direction::ET},
+    {0x2214, 0x2335, direction::ON},    {0x2336, 0x237a, direction::L},
+    {0x237b, 0x2394, direction::ON},    {0x2395, 0x2395, direction::L},
+    {0x2396, 0x2426, direction::ON},    {0x2440, 0x244a, direction::ON},
+    {0x2460, 0x2487, direction::ON},    {0x2488, 0x249b, direction::EN},
+    {0x249c, 0x24e9, direction::L},     {0x24ea, 0x26ab, direction::ON},
+    {0x26ac, 0x26ac, direction::L},     {0x26ad, 0x27ff, direction::ON},
+    {0x2800, 0x28ff, direction::L},     {0x2900, 0x2b73, direction::ON},
+    {0x2b76, 0x2b95, direction::ON},    {0x2b97, 0x2bff, direction::ON},
+    {0x2c00, 0x2c2e, direction::L},     {0x2c30, 0x2c5e, direction::L},
+    {0x2c60, 0x2ce4, direction::L},     {0x2ce5, 0x2cea, direction::ON},
+    {0x2ceb, 0x2cee, direction::L},     {0x2cef, 0x2cf1, direction::NSM},
+    {0x2cf2, 0x2cf3, direction::L},     {0x2cf9, 0x2cff, direction::ON},
+    {0x2d00, 0x2d25, direction::L},     {0x2d27, 0x2d27, direction::L},
+    {0x2d2d, 0x2d2d, direction::L},     {0x2d30, 0x2d67, direction::L},
+    {0x2d6f, 0x2d70, direction::L},     {0x2d7f, 0x2d7f, direction::NSM},
+    {0x2d80, 0x2d96, direction::L},     {0x2da0, 0x2da6, direction::L},
+    {0x2da8, 0x2dae, direction::L},     {0x2db0, 0x2db6, direction::L},
+    {0x2db8, 0x2dbe, direction::L},     {0x2dc0, 0x2dc6, direction::L},
+    {0x2dc8, 0x2dce, direction::L},     {0x2dd0, 0x2dd6, direction::L},
+    {0x2dd8, 0x2dde, direction::L},     {0x2de0, 0x2dff, direction::NSM},
+    {0x2e00, 0x2e52, direction::ON},    {0x2e80, 0x2e99, direction::ON},
+    {0x2e9b, 0x2ef3, direction::ON},    {0x2f00, 0x2fd5, direction::ON},
+    {0x2ff0, 0x2ffb, direction::ON},    {0x3000, 0x3000, direction::WS},
+    {0x3001, 0x3004, direction::ON},    {0x3005, 0x3007, direction::L},
+    {0x3008, 0x3020, direction::ON},    {0x3021, 0x3029, direction::L},
+    {0x302a, 0x302d, direction::NSM},   {0x302e, 0x302f, direction::L},
+    {0x3030, 0x3030, direction::ON},    {0x3031, 0x3035, direction::L},
+    {0x3036, 0x3037, direction::ON},    {0x3038, 0x303c, direction::L},
+    {0x303d, 0x303f, direction::ON},    {0x3041, 0x3096, direction::L},
+    {0x3099, 0x309a, direction::NSM},   {0x309b, 0x309c, direction::ON},
+    {0x309d, 0x309f, direction::L},     {0x30a0, 0x30a0, direction::ON},
+    {0x30a1, 0x30fa, direction::L},     {0x30fb, 0x30fb, direction::ON},
+    {0x30fc, 0x30ff, direction::L},     {0x3105, 0x312f, direction::L},
+    {0x3131, 0x318e, direction::L},     {0x3190, 0x31bf, direction::L},
+    {0x31c0, 0x31e3, direction::ON},    {0x31f0, 0x321c, direction::L},
+    {0x321d, 0x321e, direction::ON},    {0x3220, 0x324f, direction::L},
+    {0x3250, 0x325f, direction::ON},    {0x3260, 0x327b, direction::L},
+    {0x327c, 0x327e, direction::ON},    {0x327f, 0x32b0, direction::L},
+    {0x32b1, 0x32bf, direction::ON},    {0x32c0, 0x32cb, direction::L},
+    {0x32cc, 0x32cf, direction::ON},    {0x32d0, 0x3376, direction::L},
+    {0x3377, 0x337a, direction::ON},    {0x337b, 0x33dd, direction::L},
+    {0x33de, 0x33df, direction::ON},    {0x33e0, 0x33fe, direction::L},
+    {0x33ff, 0x33ff, direction::ON},    {0x3400, 0x4dbf, direction::L},
+    {0x4dc0, 0x4dff, direction::ON},    {0x4e00, 0x9ffc, direction::L},
+    {0xa000, 0xa48c, direction::L},     {0xa490, 0xa4c6, direction::ON},
+    {0xa4d0, 0xa60c, direction::L},     {0xa60d, 0xa60f, direction::ON},
+    {0xa610, 0xa62b, direction::L},     {0xa640, 0xa66e, direction::L},
+    {0xa66f, 0xa672, direction::NSM},   {0xa673, 0xa673, direction::ON},
+    {0xa674, 0xa67d, direction::NSM},   {0xa67e, 0xa67f, direction::ON},
+    {0xa680, 0xa69d, direction::L},     {0xa69e, 0xa69f, direction::NSM},
+    {0xa6a0, 0xa6ef, direction::L},     {0xa6f0, 0xa6f1, direction::NSM},
+    {0xa6f2, 0xa6f7, direction::L},     {0xa700, 0xa721, direction::ON},
+    {0xa722, 0xa787, direction::L},     {0xa788, 0xa788, direction::ON},
+    {0xa789, 0xa7bf, direction::L},     {0xa7c2, 0xa7ca, direction::L},
+    {0xa7f5, 0xa801, direction::L},     {0xa802, 0xa802, direction::NSM},
+    {0xa803, 0xa805, direction::L},     {0xa806, 0xa806, direction::NSM},
+    {0xa807, 0xa80a, direction::L},     {0xa80b, 0xa80b, direction::NSM},
+    {0xa80c, 0xa824, direction::L},     {0xa825, 0xa826, direction::NSM},
+    {0xa827, 0xa827, direction::L},     {0xa828, 0xa82b, direction::ON},
+    {0xa82c, 0xa82c, direction::NSM},   {0xa830, 0xa837, direction::L},
+    {0xa838, 0xa839, direction::ET},    {0xa840, 0xa873, direction::L},
+    {0xa874, 0xa877, direction::ON},    {0xa880, 0xa8c3, direction::L},
+    {0xa8c4, 0xa8c5, direction::NSM},   {0xa8ce, 0xa8d9, direction::L},
+    {0xa8e0, 0xa8f1, direction::NSM},   {0xa8f2, 0xa8fe, direction::L},
+    {0xa8ff, 0xa8ff, direction::NSM},   {0xa900, 0xa925, direction::L},
+    {0xa926, 0xa92d, direction::NSM},   {0xa92e, 0xa946, direction::L},
+    {0xa947, 0xa951, direction::NSM},   {0xa952, 0xa953, direction::L},
+    {0xa95f, 0xa97c, direction::L},     {0xa980, 0xa982, direction::NSM},
+    {0xa983, 0xa9b2, direction::L},     {0xa9b3, 0xa9b3, direction::NSM},
+    {0xa9b4, 0xa9b5, direction::L},     {0xa9b6, 0xa9b9, direction::NSM},
+    {0xa9ba, 0xa9bb, direction::L},     {0xa9bc, 0xa9bd, direction::NSM},
+    {0xa9be, 0xa9cd, direction::L},     {0xa9cf, 0xa9d9, direction::L},
+    {0xa9de, 0xa9e4, direction::L},     {0xa9e5, 0xa9e5, direction::NSM},
+    {0xa9e6, 0xa9fe, direction::L},     {0xaa00, 0xaa28, direction::L},
+    {0xaa29, 0xaa2e, direction::NSM},   {0xaa2f, 0xaa30, direction::L},
+    {0xaa31, 0xaa32, direction::NSM},   {0xaa33, 0xaa34, direction::L},
+    {0xaa35, 0xaa36, direction::NSM},   {0xaa40, 0xaa42, direction::L},
+    {0xaa43, 0xaa43, direction::NSM},   {0xaa44, 0xaa4b, direction::L},
+    {0xaa4c, 0xaa4c, direction::NSM},   {0xaa4d, 0xaa4d, direction::L},
+    {0xaa50, 0xaa59, direction::L},     {0xaa5c, 0xaa7b, direction::L},
+    {0xaa7c, 0xaa7c, direction::NSM},   {0xaa7d, 0xaaaf, direction::L},
+    {0xaab0, 0xaab0, direction::NSM},   {0xaab1, 0xaab1, direction::L},
+    {0xaab2, 0xaab4, direction::NSM},   {0xaab5, 0xaab6, direction::L},
+    {0xaab7, 0xaab8, direction::NSM},   {0xaab9, 0xaabd, direction::L},
+    {0xaabe, 0xaabf, direction::NSM},   {0xaac0, 0xaac0, direction::L},
+    {0xaac1, 0xaac1, direction::NSM},   {0xaac2, 0xaac2, direction::L},
+    {0xaadb, 0xaaeb, direction::L},     {0xaaec, 0xaaed, direction::NSM},
+    {0xaaee, 0xaaf5, direction::L},     {0xaaf6, 0xaaf6, direction::NSM},
+    {0xab01, 0xab06, direction::L},     {0xab09, 0xab0e, direction::L},
+    {0xab11, 0xab16, direction::L},     {0xab20, 0xab26, direction::L},
+    {0xab28, 0xab2e, direction::L},     {0xab30, 0xab69, direction::L},
+    {0xab6a, 0xab6b, direction::ON},    {0xab70, 0xabe4, direction::L},
+    {0xabe5, 0xabe5, direction::NSM},   {0xabe6, 0xabe7, direction::L},
+    {0xabe8, 0xabe8, direction::NSM},   {0xabe9, 0xabec, direction::L},
+    {0xabed, 0xabed, direction::NSM},   {0xabf0, 0xabf9, direction::L},
+    {0xac00, 0xd7a3, direction::L},     {0xd7b0, 0xd7c6, direction::L},
+    {0xd7cb, 0xd7fb, direction::L},     {0xd800, 0xfa6d, direction::L},
+    {0xfa70, 0xfad9, direction::L},     {0xfb00, 0xfb06, direction::L},
+    {0xfb13, 0xfb17, direction::L},     {0xfb1d, 0xfb1d, direction::R},
+    {0xfb1e, 0xfb1e, direction::NSM},   {0xfb1f, 0xfb28, direction::R},
+    {0xfb29, 0xfb29, direction::ES},    {0xfb2a, 0xfb36, direction::R},
+    {0xfb38, 0xfb3c, direction::R},     {0xfb3e, 0xfb3e, direction::R},
+    {0xfb40, 0xfb41, direction::R},     {0xfb43, 0xfb44, direction::R},
+    {0xfb46, 0xfb4f, direction::R},     {0xfb50, 0xfbc1, direction::AL},
+    {0xfbd3, 0xfd3d, direction::AL},    {0xfd3e, 0xfd3f, direction::ON},
+    {0xfd50, 0xfd8f, direction::AL},    {0xfd92, 0xfdc7, direction::AL},
+    {0xfdf0, 0xfdfc, direction::AL},    {0xfdfd, 0xfdfd, direction::ON},
+    {0xfe00, 0xfe0f, direction::NSM},   {0xfe10, 0xfe19, direction::ON},
+    {0xfe20, 0xfe2f, direction::NSM},   {0xfe30, 0xfe4f, direction::ON},
+    {0xfe50, 0xfe50, direction::CS},    {0xfe51, 0xfe51, direction::ON},
+    {0xfe52, 0xfe52, direction::CS},    {0xfe54, 0xfe54, direction::ON},
+    {0xfe55, 0xfe55, direction::CS},    {0xfe56, 0xfe5e, direction::ON},
+    {0xfe5f, 0xfe5f, direction::ET},    {0xfe60, 0xfe61, direction::ON},
+    {0xfe62, 0xfe63, direction::ES},    {0xfe64, 0xfe66, direction::ON},
+    {0xfe68, 0xfe68, direction::ON},    {0xfe69, 0xfe6a, direction::ET},
+    {0xfe6b, 0xfe6b, direction::ON},    {0xfe70, 0xfe74, direction::AL},
+    {0xfe76, 0xfefc, direction::AL},    {0xfeff, 0xfeff, direction::BN},
+    {0xff01, 0xff02, direction::ON},    {0xff03, 0xff05, direction::ET},
+    {0xff06, 0xff0a, direction::ON},    {0xff0b, 0xff0b, direction::ES},
+    {0xff0c, 0xff0c, direction::CS},    {0xff0d, 0xff0d, direction::ES},
+    {0xff0e, 0xff0f, direction::CS},    {0xff10, 0xff19, direction::EN},
+    {0xff1a, 0xff1a, direction::CS},    {0xff1b, 0xff20, direction::ON},
+    {0xff21, 0xff3a, direction::L},     {0xff3b, 0xff40, direction::ON},
+    {0xff41, 0xff5a, direction::L},     {0xff5b, 0xff65, direction::ON},
+    {0xff66, 0xffbe, direction::L},     {0xffc2, 0xffc7, direction::L},
+    {0xffca, 0xffcf, direction::L},     {0xffd2, 0xffd7, direction::L},
+    {0xffda, 0xffdc, direction::L},     {0xffe0, 0xffe1, direction::ET},
+    {0xffe2, 0xffe4, direction::ON},    {0xffe5, 0xffe6, direction::ET},
+    {0xffe8, 0xffee, direction::ON},    {0xfff9, 0xfffd, direction::ON},
+    {0x10000, 0x1000b, direction::L},   {0x1000d, 0x10026, direction::L},
+    {0x10028, 0x1003a, direction::L},   {0x1003c, 0x1003d, direction::L},
+    {0x1003f, 0x1004d, direction::L},   {0x10050, 0x1005d, direction::L},
+    {0x10080, 0x100fa, direction::L},   {0x10100, 0x10100, direction::L},
+    {0x10101, 0x10101, direction::ON},  {0x10102, 0x10102, direction::L},
+    {0x10107, 0x10133, direction::L},   {0x10137, 0x1013f, direction::L},
+    {0x10140, 0x1018c, direction::ON},  {0x1018d, 0x1018e, direction::L},
+    {0x10190, 0x1019c, direction::ON},  {0x101a0, 0x101a0, direction::ON},
+    {0x101d0, 0x101fc, direction::L},   {0x101fd, 0x101fd, direction::NSM},
+    {0x10280, 0x1029c, direction::L},   {0x102a0, 0x102d0, direction::L},
+    {0x102e0, 0x102e0, direction::NSM}, {0x102e1, 0x102fb, direction::EN},
+    {0x10300, 0x10323, direction::L},   {0x1032d, 0x1034a, direction::L},
+    {0x10350, 0x10375, direction::L},   {0x10376, 0x1037a, direction::NSM},
+    {0x10380, 0x1039d, direction::L},   {0x1039f, 0x103c3, direction::L},
+    {0x103c8, 0x103d5, direction::L},   {0x10400, 0x1049d, direction::L},
+    {0x104a0, 0x104a9, direction::L},   {0x104b0, 0x104d3, direction::L},
+    {0x104d8, 0x104fb, direction::L},   {0x10500, 0x10527, direction::L},
+    {0x10530, 0x10563, direction::L},   {0x1056f, 0x1056f, direction::L},
+    {0x10600, 0x10736, direction::L},   {0x10740, 0x10755, direction::L},
+    {0x10760, 0x10767, direction::L},   {0x10800, 0x10805, direction::R},
+    {0x10808, 0x10808, direction::R},   {0x1080a, 0x10835, direction::R},
+    {0x10837, 0x10838, direction::R},   {0x1083c, 0x1083c, direction::R},
+    {0x1083f, 0x10855, direction::R},   {0x10857, 0x1089e, direction::R},
+    {0x108a7, 0x108af, direction::R},   {0x108e0, 0x108f2, direction::R},
+    {0x108f4, 0x108f5, direction::R},   {0x108fb, 0x1091b, direction::R},
+    {0x1091f, 0x1091f, direction::ON},  {0x10920, 0x10939, direction::R},
+    {0x1093f, 0x1093f, direction::R},   {0x10980, 0x109b7, direction::R},
+    {0x109bc, 0x109cf, direction::R},   {0x109d2, 0x10a00, direction::R},
+    {0x10a01, 0x10a03, direction::NSM}, {0x10a05, 0x10a06, direction::NSM},
+    {0x10a0c, 0x10a0f, direction::NSM}, {0x10a10, 0x10a13, direction::R},
+    {0x10a15, 0x10a17, direction::R},   {0x10a19, 0x10a35, direction::R},
+    {0x10a38, 0x10a3a, direction::NSM}, {0x10a3f, 0x10a3f, direction::NSM},
+    {0x10a40, 0x10a48, direction::R},   {0x10a50, 0x10a58, direction::R},
+    {0x10a60, 0x10a9f, direction::R},   {0x10ac0, 0x10ae4, direction::R},
+    {0x10ae5, 0x10ae6, direction::NSM}, {0x10aeb, 0x10af6, direction::R},
+    {0x10b00, 0x10b35, direction::R},   {0x10b39, 0x10b3f, direction::ON},
+    {0x10b40, 0x10b55, direction::R},   {0x10b58, 0x10b72, direction::R},
+    {0x10b78, 0x10b91, direction::R},   {0x10b99, 0x10b9c, direction::R},
+    {0x10ba9, 0x10baf, direction::R},   {0x10c00, 0x10c48, direction::R},
+    {0x10c80, 0x10cb2, direction::R},   {0x10cc0, 0x10cf2, direction::R},
+    {0x10cfa, 0x10cff, direction::R},   {0x10d00, 0x10d23, direction::AL},
+    {0x10d24, 0x10d27, direction::NSM}, {0x10d30, 0x10d39, direction::AN},
+    {0x10e60, 0x10e7e, direction::AN},  {0x10e80, 0x10ea9, direction::R},
+    {0x10eab, 0x10eac, direction::NSM}, {0x10ead, 0x10ead, direction::R},
+    {0x10eb0, 0x10eb1, direction::R},   {0x10f00, 0x10f27, direction::R},
+    {0x10f30, 0x10f45, direction::AL},  {0x10f46, 0x10f50, direction::NSM},
+    {0x10f51, 0x10f59, direction::AL},  {0x10fb0, 0x10fcb, direction::R},
+    {0x10fe0, 0x10ff6, direction::R},   {0x11000, 0x11000, direction::L},
+    {0x11001, 0x11001, direction::NSM}, {0x11002, 0x11037, direction::L},
+    {0x11038, 0x11046, direction::NSM}, {0x11047, 0x1104d, direction::L},
+    {0x11052, 0x11065, direction::ON},  {0x11066, 0x1106f, direction::L},
+    {0x1107f, 0x11081, direction::NSM}, {0x11082, 0x110b2, direction::L},
+    {0x110b3, 0x110b6, direction::NSM}, {0x110b7, 0x110b8, direction::L},
+    {0x110b9, 0x110ba, direction::NSM}, {0x110bb, 0x110c1, direction::L},
+    {0x110cd, 0x110cd, direction::L},   {0x110d0, 0x110e8, direction::L},
+    {0x110f0, 0x110f9, direction::L},   {0x11100, 0x11102, direction::NSM},
+    {0x11103, 0x11126, direction::L},   {0x11127, 0x1112b, direction::NSM},
+    {0x1112c, 0x1112c, direction::L},   {0x1112d, 0x11134, direction::NSM},
+    {0x11136, 0x11147, direction::L},   {0x11150, 0x11172, direction::L},
+    {0x11173, 0x11173, direction::NSM}, {0x11174, 0x11176, direction::L},
+    {0x11180, 0x11181, direction::NSM}, {0x11182, 0x111b5, direction::L},
+    {0x111b6, 0x111be, direction::NSM}, {0x111bf, 0x111c8, direction::L},
+    {0x111c9, 0x111cc, direction::NSM}, {0x111cd, 0x111ce, direction::L},
+    {0x111cf, 0x111cf, direction::NSM}, {0x111d0, 0x111df, direction::L},
+    {0x111e1, 0x111f4, direction::L},   {0x11200, 0x11211, direction::L},
+    {0x11213, 0x1122e, direction::L},   {0x1122f, 0x11231, direction::NSM},
+    {0x11232, 0x11233, direction::L},   {0x11234, 0x11234, direction::NSM},
+    {0x11235, 0x11235, direction::L},   {0x11236, 0x11237, direction::NSM},
+    {0x11238, 0x1123d, direction::L},   {0x1123e, 0x1123e, direction::NSM},
+    {0x11280, 0x11286, direction::L},   {0x11288, 0x11288, direction::L},
+    {0x1128a, 0x1128d, direction::L},   {0x1128f, 0x1129d, direction::L},
+    {0x1129f, 0x112a9, direction::L},   {0x112b0, 0x112de, direction::L},
+    {0x112df, 0x112df, direction::NSM}, {0x112e0, 0x112e2, direction::L},
+    {0x112e3, 0x112ea, direction::NSM}, {0x112f0, 0x112f9, direction::L},
+    {0x11300, 0x11301, direction::NSM}, {0x11302, 0x11303, direction::L},
+    {0x11305, 0x1130c, direction::L},   {0x1130f, 0x11310, direction::L},
+    {0x11313, 0x11328, direction::L},   {0x1132a, 0x11330, direction::L},
+    {0x11332, 0x11333, direction::L},   {0x11335, 0x11339, direction::L},
+    {0x1133b, 0x1133c, direction::NSM}, {0x1133d, 0x1133f, direction::L},
+    {0x11340, 0x11340, direction::NSM}, {0x11341, 0x11344, direction::L},
+    {0x11347, 0x11348, direction::L},   {0x1134b, 0x1134d, direction::L},
+    {0x11350, 0x11350, direction::L},   {0x11357, 0x11357, direction::L},
+    {0x1135d, 0x11363, direction::L},   {0x11366, 0x1136c, direction::NSM},
+    {0x11370, 0x11374, direction::NSM}, {0x11400, 0x11437, direction::L},
+    {0x11438, 0x1143f, direction::NSM}, {0x11440, 0x11441, direction::L},
+    {0x11442, 0x11444, direction::NSM}, {0x11445, 0x11445, direction::L},
+    {0x11446, 0x11446, direction::NSM}, {0x11447, 0x1145b, direction::L},
+    {0x1145d, 0x1145d, direction::L},   {0x1145e, 0x1145e, direction::NSM},
+    {0x1145f, 0x11461, direction::L},   {0x11480, 0x114b2, direction::L},
+    {0x114b3, 0x114b8, direction::NSM}, {0x114b9, 0x114b9, direction::L},
+    {0x114ba, 0x114ba, direction::NSM}, {0x114bb, 0x114be, direction::L},
+    {0x114bf, 0x114c0, direction::NSM}, {0x114c1, 0x114c1, direction::L},
+    {0x114c2, 0x114c3, direction::NSM}, {0x114c4, 0x114c7, direction::L},
+    {0x114d0, 0x114d9, direction::L},   {0x11580, 0x115b1, direction::L},
+    {0x115b2, 0x115b5, direction::NSM}, {0x115b8, 0x115bb, direction::L},
+    {0x115bc, 0x115bd, direction::NSM}, {0x115be, 0x115be, direction::L},
+    {0x115bf, 0x115c0, direction::NSM}, {0x115c1, 0x115db, direction::L},
+    {0x115dc, 0x115dd, direction::NSM}, {0x11600, 0x11632, direction::L},
+    {0x11633, 0x1163a, direction::NSM}, {0x1163b, 0x1163c, direction::L},
+    {0x1163d, 0x1163d, direction::NSM}, {0x1163e, 0x1163e, direction::L},
+    {0x1163f, 0x11640, direction::NSM}, {0x11641, 0x11644, direction::L},
+    {0x11650, 0x11659, direction::L},   {0x11660, 0x1166c, direction::ON},
+    {0x11680, 0x116aa, direction::L},   {0x116ab, 0x116ab, direction::NSM},
+    {0x116ac, 0x116ac, direction::L},   {0x116ad, 0x116ad, direction::NSM},
+    {0x116ae, 0x116af, direction::L},   {0x116b0, 0x116b5, direction::NSM},
+    {0x116b6, 0x116b6, direction::L},   {0x116b7, 0x116b7, direction::NSM},
+    {0x116b8, 0x116b8, direction::L},   {0x116c0, 0x116c9, direction::L},
+    {0x11700, 0x1171a, direction::L},   {0x1171d, 0x1171f, direction::NSM},
+    {0x11720, 0x11721, direction::L},   {0x11722, 0x11725, direction::NSM},
+    {0x11726, 0x11726, direction::L},   {0x11727, 0x1172b, direction::NSM},
+    {0x11730, 0x1173f, direction::L},   {0x11800, 0x1182e, direction::L},
+    {0x1182f, 0x11837, direction::NSM}, {0x11838, 0x11838, direction::L},
+    {0x11839, 0x1183a, direction::NSM}, {0x1183b, 0x1183b, direction::L},
+    {0x118a0, 0x118f2, direction::L},   {0x118ff, 0x11906, direction::L},
+    {0x11909, 0x11909, direction::L},   {0x1190c, 0x11913, direction::L},
+    {0x11915, 0x11916, direction::L},   {0x11918, 0x11935, direction::L},
+    {0x11937, 0x11938, direction::L},   {0x1193b, 0x1193c, direction::NSM},
+    {0x1193d, 0x1193d, direction::L},   {0x1193e, 0x1193e, direction::NSM},
+    {0x1193f, 0x11942, direction::L},   {0x11943, 0x11943, direction::NSM},
+    {0x11944, 0x11946, direction::L},   {0x11950, 0x11959, direction::L},
+    {0x119a0, 0x119a7, direction::L},   {0x119aa, 0x119d3, direction::L},
+    {0x119d4, 0x119d7, direction::NSM}, {0x119da, 0x119db, direction::NSM},
+    {0x119dc, 0x119df, direction::L},   {0x119e0, 0x119e0, direction::NSM},
+    {0x119e1, 0x119e4, direction::L},   {0x11a00, 0x11a00, direction::L},
+    {0x11a01, 0x11a06, direction::NSM}, {0x11a07, 0x11a08, direction::L},
+    {0x11a09, 0x11a0a, direction::NSM}, {0x11a0b, 0x11a32, direction::L},
+    {0x11a33, 0x11a38, direction::NSM}, {0x11a39, 0x11a3a, direction::L},
+    {0x11a3b, 0x11a3e, direction::NSM}, {0x11a3f, 0x11a46, direction::L},
+    {0x11a47, 0x11a47, direction::NSM}, {0x11a50, 0x11a50, direction::L},
+    {0x11a51, 0x11a56, direction::NSM}, {0x11a57, 0x11a58, direction::L},
+    {0x11a59, 0x11a5b, direction::NSM}, {0x11a5c, 0x11a89, direction::L},
+    {0x11a8a, 0x11a96, direction::NSM}, {0x11a97, 0x11a97, direction::L},
+    {0x11a98, 0x11a99, direction::NSM}, {0x11a9a, 0x11aa2, direction::L},
+    {0x11ac0, 0x11af8, direction::L},   {0x11c00, 0x11c08, direction::L},
+    {0x11c0a, 0x11c2f, direction::L},   {0x11c30, 0x11c36, direction::NSM},
+    {0x11c38, 0x11c3d, direction::NSM}, {0x11c3e, 0x11c45, direction::L},
+    {0x11c50, 0x11c6c, direction::L},   {0x11c70, 0x11c8f, direction::L},
+    {0x11c92, 0x11ca7, direction::NSM}, {0x11ca9, 0x11ca9, direction::L},
+    {0x11caa, 0x11cb0, direction::NSM}, {0x11cb1, 0x11cb1, direction::L},
+    {0x11cb2, 0x11cb3, direction::NSM}, {0x11cb4, 0x11cb4, direction::L},
+    {0x11cb5, 0x11cb6, direction::NSM}, {0x11d00, 0x11d06, direction::L},
+    {0x11d08, 0x11d09, direction::L},   {0x11d0b, 0x11d30, direction::L},
+    {0x11d31, 0x11d36, direction::NSM}, {0x11d3a, 0x11d3a, direction::NSM},
+    {0x11d3c, 0x11d3d, direction::NSM}, {0x11d3f, 0x11d45, direction::NSM},
+    {0x11d46, 0x11d46, direction::L},   {0x11d47, 0x11d47, direction::NSM},
+    {0x11d50, 0x11d59, direction::L},   {0x11d60, 0x11d65, direction::L},
+    {0x11d67, 0x11d68, direction::L},   {0x11d6a, 0x11d8e, direction::L},
+    {0x11d90, 0x11d91, direction::NSM}, {0x11d93, 0x11d94, direction::L},
+    {0x11d95, 0x11d95, direction::NSM}, {0x11d96, 0x11d96, direction::L},
+    {0x11d97, 0x11d97, direction::NSM}, {0x11d98, 0x11d98, direction::L},
+    {0x11da0, 0x11da9, direction::L},   {0x11ee0, 0x11ef2, direction::L},
+    {0x11ef3, 0x11ef4, direction::NSM}, {0x11ef5, 0x11ef8, direction::L},
+    {0x11fb0, 0x11fb0, direction::L},   {0x11fc0, 0x11fd4, direction::L},
+    {0x11fd5, 0x11fdc, direction::ON},  {0x11fdd, 0x11fe0, direction::ET},
+    {0x11fe1, 0x11ff1, direction::ON},  {0x11fff, 0x12399, direction::L},
+    {0x12400, 0x1246e, direction::L},   {0x12470, 0x12474, direction::L},
+    {0x12480, 0x12543, direction::L},   {0x13000, 0x1342e, direction::L},
+    {0x13430, 0x13438, direction::L},   {0x14400, 0x14646, direction::L},
+    {0x16800, 0x16a38, direction::L},   {0x16a40, 0x16a5e, direction::L},
+    {0x16a60, 0x16a69, direction::L},   {0x16a6e, 0x16a6f, direction::L},
+    {0x16ad0, 0x16aed, direction::L},   {0x16af0, 0x16af4, direction::NSM},
+    {0x16af5, 0x16af5, direction::L},   {0x16b00, 0x16b2f, direction::L},
+    {0x16b30, 0x16b36, direction::NSM}, {0x16b37, 0x16b45, direction::L},
+    {0x16b50, 0x16b59, direction::L},   {0x16b5b, 0x16b61, direction::L},
+    {0x16b63, 0x16b77, direction::L},   {0x16b7d, 0x16b8f, direction::L},
+    {0x16e40, 0x16e9a, direction::L},   {0x16f00, 0x16f4a, direction::L},
+    {0x16f4f, 0x16f4f, direction::NSM}, {0x16f50, 0x16f87, direction::L},
+    {0x16f8f, 0x16f92, direction::NSM}, {0x16f93, 0x16f9f, direction::L},
+    {0x16fe0, 0x16fe1, direction::L},   {0x16fe2, 0x16fe2, direction::ON},
+    {0x16fe3, 0x16fe3, direction::L},   {0x16fe4, 0x16fe4, direction::NSM},
+    {0x16ff0, 0x16ff1, direction::L},   {0x17000, 0x187f7, direction::L},
+    {0x18800, 0x18cd5, direction::L},   {0x18d00, 0x18d08, direction::L},
+    {0x1b000, 0x1b11e, direction::L},   {0x1b150, 0x1b152, direction::L},
+    {0x1b164, 0x1b167, direction::L},   {0x1b170, 0x1b2fb, direction::L},
+    {0x1bc00, 0x1bc6a, direction::L},   {0x1bc70, 0x1bc7c, direction::L},
+    {0x1bc80, 0x1bc88, direction::L},   {0x1bc90, 0x1bc99, direction::L},
+    {0x1bc9c, 0x1bc9c, direction::L},   {0x1bc9d, 0x1bc9e, direction::NSM},
+    {0x1bc9f, 0x1bc9f, direction::L},   {0x1bca0, 0x1bca3, direction::BN},
+    {0x1d000, 0x1d0f5, direction::L},   {0x1d100, 0x1d126, direction::L},
+    {0x1d129, 0x1d166, direction::L},   {0x1d167, 0x1d169, direction::NSM},
+    {0x1d16a, 0x1d172, direction::L},   {0x1d173, 0x1d17a, direction::BN},
+    {0x1d17b, 0x1d182, direction::NSM}, {0x1d183, 0x1d184, direction::L},
+    {0x1d185, 0x1d18b, direction::NSM}, {0x1d18c, 0x1d1a9, direction::L},
+    {0x1d1aa, 0x1d1ad, direction::NSM}, {0x1d1ae, 0x1d1e8, direction::L},
+    {0x1d200, 0x1d241, direction::ON},  {0x1d242, 0x1d244, direction::NSM},
+    {0x1d245, 0x1d245, direction::ON},  {0x1d2e0, 0x1d2f3, direction::L},
+    {0x1d300, 0x1d356, direction::ON},  {0x1d360, 0x1d378, direction::L},
+    {0x1d400, 0x1d454, direction::L},   {0x1d456, 0x1d49c, direction::L},
+    {0x1d49e, 0x1d49f, direction::L},   {0x1d4a2, 0x1d4a2, direction::L},
+    {0x1d4a5, 0x1d4a6, direction::L},   {0x1d4a9, 0x1d4ac, direction::L},
+    {0x1d4ae, 0x1d4b9, direction::L},   {0x1d4bb, 0x1d4bb, direction::L},
+    {0x1d4bd, 0x1d4c3, direction::L},   {0x1d4c5, 0x1d505, direction::L},
+    {0x1d507, 0x1d50a, direction::L},   {0x1d50d, 0x1d514, direction::L},
+    {0x1d516, 0x1d51c, direction::L},   {0x1d51e, 0x1d539, direction::L},
+    {0x1d53b, 0x1d53e, direction::L},   {0x1d540, 0x1d544, direction::L},
+    {0x1d546, 0x1d546, direction::L},   {0x1d54a, 0x1d550, direction::L},
+    {0x1d552, 0x1d6a5, direction::L},   {0x1d6a8, 0x1d6da, direction::L},
+    {0x1d6db, 0x1d6db, direction::ON},  {0x1d6dc, 0x1d714, direction::L},
+    {0x1d715, 0x1d715, direction::ON},  {0x1d716, 0x1d74e, direction::L},
+    {0x1d74f, 0x1d74f, direction::ON},  {0x1d750, 0x1d788, direction::L},
+    {0x1d789, 0x1d789, direction::ON},  {0x1d78a, 0x1d7c2, direction::L},
+    {0x1d7c3, 0x1d7c3, direction::ON},  {0x1d7c4, 0x1d7cb, direction::L},
+    {0x1d7ce, 0x1d7ff, direction::EN},  {0x1d800, 0x1d9ff, direction::L},
+    {0x1da00, 0x1da36, direction::NSM}, {0x1da37, 0x1da3a, direction::L},
+    {0x1da3b, 0x1da6c, direction::NSM}, {0x1da6d, 0x1da74, direction::L},
+    {0x1da75, 0x1da75, direction::NSM}, {0x1da76, 0x1da83, direction::L},
+    {0x1da84, 0x1da84, direction::NSM}, {0x1da85, 0x1da8b, direction::L},
+    {0x1da9b, 0x1da9f, direction::NSM}, {0x1daa1, 0x1daaf, direction::NSM},
+    {0x1e000, 0x1e006, direction::NSM}, {0x1e008, 0x1e018, direction::NSM},
+    {0x1e01b, 0x1e021, direction::NSM}, {0x1e023, 0x1e024, direction::NSM},
+    {0x1e026, 0x1e02a, direction::NSM}, {0x1e100, 0x1e12c, direction::L},
+    {0x1e130, 0x1e136, direction::NSM}, {0x1e137, 0x1e13d, direction::L},
+    {0x1e140, 0x1e149, direction::L},   {0x1e14e, 0x1e14f, direction::L},
+    {0x1e2c0, 0x1e2eb, direction::L},   {0x1e2ec, 0x1e2ef, direction::NSM},
+    {0x1e2f0, 0x1e2f9, direction::L},   {0x1e2ff, 0x1e2ff, direction::ET},
+    {0x1e800, 0x1e8c4, direction::R},   {0x1e8c7, 0x1e8cf, direction::R},
+    {0x1e8d0, 0x1e8d6, direction::NSM}, {0x1e900, 0x1e943, direction::R},
+    {0x1e944, 0x1e94a, direction::NSM}, {0x1e94b, 0x1e94b, direction::R},
+    {0x1e950, 0x1e959, direction::R},   {0x1e95e, 0x1e95f, direction::R},
+    {0x1ec71, 0x1ecb4, direction::AL},  {0x1ed01, 0x1ed3d, direction::AL},
+    {0x1ee00, 0x1ee03, direction::AL},  {0x1ee05, 0x1ee1f, direction::AL},
+    {0x1ee21, 0x1ee22, direction::AL},  {0x1ee24, 0x1ee24, direction::AL},
+    {0x1ee27, 0x1ee27, direction::AL},  {0x1ee29, 0x1ee32, direction::AL},
+    {0x1ee34, 0x1ee37, direction::AL},  {0x1ee39, 0x1ee39, direction::AL},
+    {0x1ee3b, 0x1ee3b, direction::AL},  {0x1ee42, 0x1ee42, direction::AL},
+    {0x1ee47, 0x1ee47, direction::AL},  {0x1ee49, 0x1ee49, direction::AL},
+    {0x1ee4b, 0x1ee4b, direction::AL},  {0x1ee4d, 0x1ee4f, direction::AL},
+    {0x1ee51, 0x1ee52, direction::AL},  {0x1ee54, 0x1ee54, direction::AL},
+    {0x1ee57, 0x1ee57, direction::AL},  {0x1ee59, 0x1ee59, direction::AL},
+    {0x1ee5b, 0x1ee5b, direction::AL},  {0x1ee5d, 0x1ee5d, direction::AL},
+    {0x1ee5f, 0x1ee5f, direction::AL},  {0x1ee61, 0x1ee62, direction::AL},
+    {0x1ee64, 0x1ee64, direction::AL},  {0x1ee67, 0x1ee6a, direction::AL},
+    {0x1ee6c, 0x1ee72, direction::AL},  {0x1ee74, 0x1ee77, direction::AL},
+    {0x1ee79, 0x1ee7c, direction::AL},  {0x1ee7e, 0x1ee7e, direction::AL},
+    {0x1ee80, 0x1ee89, direction::AL},  {0x1ee8b, 0x1ee9b, direction::AL},
+    {0x1eea1, 0x1eea3, direction::AL},  {0x1eea5, 0x1eea9, direction::AL},
+    {0x1eeab, 0x1eebb, direction::AL},  {0x1eef0, 0x1eef1, direction::ON},
+    {0x1f000, 0x1f02b, direction::ON},  {0x1f030, 0x1f093, direction::ON},
+    {0x1f0a0, 0x1f0ae, direction::ON},  {0x1f0b1, 0x1f0bf, direction::ON},
+    {0x1f0c1, 0x1f0cf, direction::ON},  {0x1f0d1, 0x1f0f5, direction::ON},
+    {0x1f100, 0x1f10a, direction::EN},  {0x1f10b, 0x1f10f, direction::ON},
+    {0x1f110, 0x1f12e, direction::L},   {0x1f12f, 0x1f12f, direction::ON},
+    {0x1f130, 0x1f169, direction::L},   {0x1f16a, 0x1f16f, direction::ON},
+    {0x1f170, 0x1f1ac, direction::L},   {0x1f1ad, 0x1f1ad, direction::ON},
+    {0x1f1e6, 0x1f202, direction::L},   {0x1f210, 0x1f23b, direction::L},
+    {0x1f240, 0x1f248, direction::L},   {0x1f250, 0x1f251, direction::L},
+    {0x1f260, 0x1f265, direction::ON},  {0x1f300, 0x1f6d7, direction::ON},
+    {0x1f6e0, 0x1f6ec, direction::ON},  {0x1f6f0, 0x1f6fc, direction::ON},
+    {0x1f700, 0x1f773, direction::ON},  {0x1f780, 0x1f7d8, direction::ON},
+    {0x1f7e0, 0x1f7eb, direction::ON},  {0x1f800, 0x1f80b, direction::ON},
+    {0x1f810, 0x1f847, direction::ON},  {0x1f850, 0x1f859, direction::ON},
+    {0x1f860, 0x1f887, direction::ON},  {0x1f890, 0x1f8ad, direction::ON},
+    {0x1f8b0, 0x1f8b1, direction::ON},  {0x1f900, 0x1f978, direction::ON},
+    {0x1f97a, 0x1f9cb, direction::ON},  {0x1f9cd, 0x1fa53, direction::ON},
+    {0x1fa60, 0x1fa6d, direction::ON},  {0x1fa70, 0x1fa74, direction::ON},
+    {0x1fa78, 0x1fa7a, direction::ON},  {0x1fa80, 0x1fa86, direction::ON},
+    {0x1fa90, 0x1faa8, direction::ON},  {0x1fab0, 0x1fab6, direction::ON},
+    {0x1fac0, 0x1fac2, direction::ON},  {0x1fad0, 0x1fad6, direction::ON},
+    {0x1fb00, 0x1fb92, direction::ON},  {0x1fb94, 0x1fbca, direction::ON},
+    {0x1fbf0, 0x1fbf9, direction::EN},  {0x20000, 0x2a6dd, direction::L},
+    {0x2a700, 0x2b734, direction::L},   {0x2b740, 0x2b81d, direction::L},
+    {0x2b820, 0x2cea1, direction::L},   {0x2ceb0, 0x2ebe0, direction::L},
+    {0x2f800, 0x2fa1d, direction::L},   {0x30000, 0x3134a, direction::L},
+    {0xe0001, 0xe0001, direction::BN},  {0xe0020, 0xe007f, direction::BN},
+    {0xe0100, 0xe01ef, direction::NSM}, {0xf0000, 0xffffd, direction::L},
+    {0x100000, 0x10fffd, direction::L}};
+
+// CheckJoiners and CheckBidi are true for URL specification.
+
+inline static direction find_direction(uint32_t code_point) noexcept {
+  auto it = std::lower_bound(
+      std::begin(dir_table), std::end(dir_table), code_point,
+      [](const directions& d, uint32_t c) { return d.final_code < c; });
+
+  // next check is almost surely in vain, but we use it for safety.
+  if (it == std::end(dir_table)) {
+    return direction::NONE;
+  }
+  // We have that d.final_code >= c.
+  if (code_point >= it->start_code) {
+    return it->direct;
+  }
+  return direction::NONE;
+}
+
+inline static size_t find_last_not_of_nsm(
+    const std::u32string_view label) noexcept {
+  for (int i = label.size() - 1; i >= 0; i--)
+    if (find_direction(label[i]) != direction::NSM) return i;
+
+  return std::u32string_view::npos;
+}
+
+// An RTL label is a label that contains at least one character of type R, AL,
+// or AN. https://www.rfc-editor.org/rfc/rfc5893#section-2
+inline static bool is_rtl_label(const std::u32string_view label) noexcept {
+  const size_t mask =
+      (1u << direction::R) | (1u << direction::AL) | (1u << direction::AN);
+
+  size_t directions = 0;
+  for (size_t i = 0; i < label.size(); i++) {
+    directions |= 1u << find_direction(label[i]);
+  }
+  return (directions & mask) != 0;
+}
+
+bool is_label_valid(const std::u32string_view label) {
+  if (label.empty()) {
+    return true;
+  }
+
+  ///////////////
+  // We have a normalization step which ensures that we are in NFC.
+  // If we receive punycode, we normalize and check that the normalized
+  // version matches the original.
+  // --------------------------------------
+  // The label must be in Unicode Normalization Form NFC.
+
+  // Current URL standard indicatest that CheckHyphens is set to false.
+  // ---------------------------------------
+  // If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character
+  // in both the third and fourth positions. If CheckHyphens, the label must
+  // neither begin nor end with a U+002D HYPHEN-MINUS character.
+
+  // This is not necessary because we segment the
+  // labels by '.'.
+  // ---------------------------------------
+  // The label must not contain a U+002E ( . ) FULL STOP.
+  // if (label.find('.') != std::string_view::npos) return false;
+
+  // The label must not begin with a combining mark, that is:
+  // General_Category=Mark.
+  constexpr static uint32_t combining[] = {
+      0x300,   0x301,   0x302,   0x303,   0x304,   0x305,   0x306,   0x307,
+      0x308,   0x309,   0x30a,   0x30b,   0x30c,   0x30d,   0x30e,   0x30f,
+      0x310,   0x311,   0x312,   0x313,   0x314,   0x315,   0x316,   0x317,
+      0x318,   0x319,   0x31a,   0x31b,   0x31c,   0x31d,   0x31e,   0x31f,
+      0x320,   0x321,   0x322,   0x323,   0x324,   0x325,   0x326,   0x327,
+      0x328,   0x329,   0x32a,   0x32b,   0x32c,   0x32d,   0x32e,   0x32f,
+      0x330,   0x331,   0x332,   0x333,   0x334,   0x335,   0x336,   0x337,
+      0x338,   0x339,   0x33a,   0x33b,   0x33c,   0x33d,   0x33e,   0x33f,
+      0x340,   0x341,   0x342,   0x343,   0x344,   0x345,   0x346,   0x347,
+      0x348,   0x349,   0x34a,   0x34b,   0x34c,   0x34d,   0x34e,   0x34f,
+      0x350,   0x351,   0x352,   0x353,   0x354,   0x355,   0x356,   0x357,
+      0x358,   0x359,   0x35a,   0x35b,   0x35c,   0x35d,   0x35e,   0x35f,
+      0x360,   0x361,   0x362,   0x363,   0x364,   0x365,   0x366,   0x367,
+      0x368,   0x369,   0x36a,   0x36b,   0x36c,   0x36d,   0x36e,   0x36f,
+      0x483,   0x484,   0x485,   0x486,   0x487,   0x488,   0x489,   0x591,
+      0x592,   0x593,   0x594,   0x595,   0x596,   0x597,   0x598,   0x599,
+      0x59a,   0x59b,   0x59c,   0x59d,   0x59e,   0x59f,   0x5a0,   0x5a1,
+      0x5a2,   0x5a3,   0x5a4,   0x5a5,   0x5a6,   0x5a7,   0x5a8,   0x5a9,
+      0x5aa,   0x5ab,   0x5ac,   0x5ad,   0x5ae,   0x5af,   0x5b0,   0x5b1,
+      0x5b2,   0x5b3,   0x5b4,   0x5b5,   0x5b6,   0x5b7,   0x5b8,   0x5b9,
+      0x5ba,   0x5bb,   0x5bc,   0x5bd,   0x5bf,   0x5c1,   0x5c2,   0x5c4,
+      0x5c5,   0x5c7,   0x610,   0x611,   0x612,   0x613,   0x614,   0x615,
+      0x616,   0x617,   0x618,   0x619,   0x61a,   0x64b,   0x64c,   0x64d,
+      0x64e,   0x64f,   0x650,   0x651,   0x652,   0x653,   0x654,   0x655,
+      0x656,   0x657,   0x658,   0x659,   0x65a,   0x65b,   0x65c,   0x65d,
+      0x65e,   0x65f,   0x670,   0x6d6,   0x6d7,   0x6d8,   0x6d9,   0x6da,
+      0x6db,   0x6dc,   0x6df,   0x6e0,   0x6e1,   0x6e2,   0x6e3,   0x6e4,
+      0x6e7,   0x6e8,   0x6ea,   0x6eb,   0x6ec,   0x6ed,   0x711,   0x730,
+      0x731,   0x732,   0x733,   0x734,   0x735,   0x736,   0x737,   0x738,
+      0x739,   0x73a,   0x73b,   0x73c,   0x73d,   0x73e,   0x73f,   0x740,
+      0x741,   0x742,   0x743,   0x744,   0x745,   0x746,   0x747,   0x748,
+      0x749,   0x74a,   0x7a6,   0x7a7,   0x7a8,   0x7a9,   0x7aa,   0x7ab,
+      0x7ac,   0x7ad,   0x7ae,   0x7af,   0x7b0,   0x7eb,   0x7ec,   0x7ed,
+      0x7ee,   0x7ef,   0x7f0,   0x7f1,   0x7f2,   0x7f3,   0x7fd,   0x816,
+      0x817,   0x818,   0x819,   0x81b,   0x81c,   0x81d,   0x81e,   0x81f,
+      0x820,   0x821,   0x822,   0x823,   0x825,   0x826,   0x827,   0x829,
+      0x82a,   0x82b,   0x82c,   0x82d,   0x859,   0x85a,   0x85b,   0x8d3,
+      0x8d4,   0x8d5,   0x8d6,   0x8d7,   0x8d8,   0x8d9,   0x8da,   0x8db,
+      0x8dc,   0x8dd,   0x8de,   0x8df,   0x8e0,   0x8e1,   0x8e3,   0x8e4,
+      0x8e5,   0x8e6,   0x8e7,   0x8e8,   0x8e9,   0x8ea,   0x8eb,   0x8ec,
+      0x8ed,   0x8ee,   0x8ef,   0x8f0,   0x8f1,   0x8f2,   0x8f3,   0x8f4,
+      0x8f5,   0x8f6,   0x8f7,   0x8f8,   0x8f9,   0x8fa,   0x8fb,   0x8fc,
+      0x8fd,   0x8fe,   0x8ff,   0x900,   0x901,   0x902,   0x903,   0x93a,
+      0x93b,   0x93c,   0x93e,   0x93f,   0x940,   0x941,   0x942,   0x943,
+      0x944,   0x945,   0x946,   0x947,   0x948,   0x949,   0x94a,   0x94b,
+      0x94c,   0x94d,   0x94e,   0x94f,   0x951,   0x952,   0x953,   0x954,
+      0x955,   0x956,   0x957,   0x962,   0x963,   0x981,   0x982,   0x983,
+      0x9bc,   0x9be,   0x9bf,   0x9c0,   0x9c1,   0x9c2,   0x9c3,   0x9c4,
+      0x9c7,   0x9c8,   0x9cb,   0x9cc,   0x9cd,   0x9d7,   0x9e2,   0x9e3,
+      0x9fe,   0xa01,   0xa02,   0xa03,   0xa3c,   0xa3e,   0xa3f,   0xa40,
+      0xa41,   0xa42,   0xa47,   0xa48,   0xa4b,   0xa4c,   0xa4d,   0xa51,
+      0xa70,   0xa71,   0xa75,   0xa81,   0xa82,   0xa83,   0xabc,   0xabe,
+      0xabf,   0xac0,   0xac1,   0xac2,   0xac3,   0xac4,   0xac5,   0xac7,
+      0xac8,   0xac9,   0xacb,   0xacc,   0xacd,   0xae2,   0xae3,   0xafa,
+      0xafb,   0xafc,   0xafd,   0xafe,   0xaff,   0xb01,   0xb02,   0xb03,
+      0xb3c,   0xb3e,   0xb3f,   0xb40,   0xb41,   0xb42,   0xb43,   0xb44,
+      0xb47,   0xb48,   0xb4b,   0xb4c,   0xb4d,   0xb55,   0xb56,   0xb57,
+      0xb62,   0xb63,   0xb82,   0xbbe,   0xbbf,   0xbc0,   0xbc1,   0xbc2,
+      0xbc6,   0xbc7,   0xbc8,   0xbca,   0xbcb,   0xbcc,   0xbcd,   0xbd7,
+      0xc00,   0xc01,   0xc02,   0xc03,   0xc04,   0xc3e,   0xc3f,   0xc40,
+      0xc41,   0xc42,   0xc43,   0xc44,   0xc46,   0xc47,   0xc48,   0xc4a,
+      0xc4b,   0xc4c,   0xc4d,   0xc55,   0xc56,   0xc62,   0xc63,   0xc81,
+      0xc82,   0xc83,   0xcbc,   0xcbe,   0xcbf,   0xcc0,   0xcc1,   0xcc2,
+      0xcc3,   0xcc4,   0xcc6,   0xcc7,   0xcc8,   0xcca,   0xccb,   0xccc,
+      0xccd,   0xcd5,   0xcd6,   0xce2,   0xce3,   0xd00,   0xd01,   0xd02,
+      0xd03,   0xd3b,   0xd3c,   0xd3e,   0xd3f,   0xd40,   0xd41,   0xd42,
+      0xd43,   0xd44,   0xd46,   0xd47,   0xd48,   0xd4a,   0xd4b,   0xd4c,
+      0xd4d,   0xd57,   0xd62,   0xd63,   0xd81,   0xd82,   0xd83,   0xdca,
+      0xdcf,   0xdd0,   0xdd1,   0xdd2,   0xdd3,   0xdd4,   0xdd6,   0xdd8,
+      0xdd9,   0xdda,   0xddb,   0xddc,   0xddd,   0xdde,   0xddf,   0xdf2,
+      0xdf3,   0xe31,   0xe34,   0xe35,   0xe36,   0xe37,   0xe38,   0xe39,
+      0xe3a,   0xe47,   0xe48,   0xe49,   0xe4a,   0xe4b,   0xe4c,   0xe4d,
+      0xe4e,   0xeb1,   0xeb4,   0xeb5,   0xeb6,   0xeb7,   0xeb8,   0xeb9,
+      0xeba,   0xebb,   0xebc,   0xec8,   0xec9,   0xeca,   0xecb,   0xecc,
+      0xecd,   0xf18,   0xf19,   0xf35,   0xf37,   0xf39,   0xf3e,   0xf3f,
+      0xf71,   0xf72,   0xf73,   0xf74,   0xf75,   0xf76,   0xf77,   0xf78,
+      0xf79,   0xf7a,   0xf7b,   0xf7c,   0xf7d,   0xf7e,   0xf7f,   0xf80,
+      0xf81,   0xf82,   0xf83,   0xf84,   0xf86,   0xf87,   0xf8d,   0xf8e,
+      0xf8f,   0xf90,   0xf91,   0xf92,   0xf93,   0xf94,   0xf95,   0xf96,
+      0xf97,   0xf99,   0xf9a,   0xf9b,   0xf9c,   0xf9d,   0xf9e,   0xf9f,
+      0xfa0,   0xfa1,   0xfa2,   0xfa3,   0xfa4,   0xfa5,   0xfa6,   0xfa7,
+      0xfa8,   0xfa9,   0xfaa,   0xfab,   0xfac,   0xfad,   0xfae,   0xfaf,
+      0xfb0,   0xfb1,   0xfb2,   0xfb3,   0xfb4,   0xfb5,   0xfb6,   0xfb7,
+      0xfb8,   0xfb9,   0xfba,   0xfbb,   0xfbc,   0xfc6,   0x102b,  0x102c,
+      0x102d,  0x102e,  0x102f,  0x1030,  0x1031,  0x1032,  0x1033,  0x1034,
+      0x1035,  0x1036,  0x1037,  0x1038,  0x1039,  0x103a,  0x103b,  0x103c,
+      0x103d,  0x103e,  0x1056,  0x1057,  0x1058,  0x1059,  0x105e,  0x105f,
+      0x1060,  0x1062,  0x1063,  0x1064,  0x1067,  0x1068,  0x1069,  0x106a,
+      0x106b,  0x106c,  0x106d,  0x1071,  0x1072,  0x1073,  0x1074,  0x1082,
+      0x1083,  0x1084,  0x1085,  0x1086,  0x1087,  0x1088,  0x1089,  0x108a,
+      0x108b,  0x108c,  0x108d,  0x108f,  0x109a,  0x109b,  0x109c,  0x109d,
+      0x135d,  0x135e,  0x135f,  0x1712,  0x1713,  0x1714,  0x1732,  0x1733,
+      0x1734,  0x1752,  0x1753,  0x1772,  0x1773,  0x17b4,  0x17b5,  0x17b6,
+      0x17b7,  0x17b8,  0x17b9,  0x17ba,  0x17bb,  0x17bc,  0x17bd,  0x17be,
+      0x17bf,  0x17c0,  0x17c1,  0x17c2,  0x17c3,  0x17c4,  0x17c5,  0x17c6,
+      0x17c7,  0x17c8,  0x17c9,  0x17ca,  0x17cb,  0x17cc,  0x17cd,  0x17ce,
+      0x17cf,  0x17d0,  0x17d1,  0x17d2,  0x17d3,  0x17dd,  0x180b,  0x180c,
+      0x180d,  0x1885,  0x1886,  0x18a9,  0x1920,  0x1921,  0x1922,  0x1923,
+      0x1924,  0x1925,  0x1926,  0x1927,  0x1928,  0x1929,  0x192a,  0x192b,
+      0x1930,  0x1931,  0x1932,  0x1933,  0x1934,  0x1935,  0x1936,  0x1937,
+      0x1938,  0x1939,  0x193a,  0x193b,  0x1a17,  0x1a18,  0x1a19,  0x1a1a,
+      0x1a1b,  0x1a55,  0x1a56,  0x1a57,  0x1a58,  0x1a59,  0x1a5a,  0x1a5b,
+      0x1a5c,  0x1a5d,  0x1a5e,  0x1a60,  0x1a61,  0x1a62,  0x1a63,  0x1a64,
+      0x1a65,  0x1a66,  0x1a67,  0x1a68,  0x1a69,  0x1a6a,  0x1a6b,  0x1a6c,
+      0x1a6d,  0x1a6e,  0x1a6f,  0x1a70,  0x1a71,  0x1a72,  0x1a73,  0x1a74,
+      0x1a75,  0x1a76,  0x1a77,  0x1a78,  0x1a79,  0x1a7a,  0x1a7b,  0x1a7c,
+      0x1a7f,  0x1ab0,  0x1ab1,  0x1ab2,  0x1ab3,  0x1ab4,  0x1ab5,  0x1ab6,
+      0x1ab7,  0x1ab8,  0x1ab9,  0x1aba,  0x1abb,  0x1abc,  0x1abd,  0x1abe,
+      0x1abf,  0x1ac0,  0x1b00,  0x1b01,  0x1b02,  0x1b03,  0x1b04,  0x1b34,
+      0x1b35,  0x1b36,  0x1b37,  0x1b38,  0x1b39,  0x1b3a,  0x1b3b,  0x1b3c,
+      0x1b3d,  0x1b3e,  0x1b3f,  0x1b40,  0x1b41,  0x1b42,  0x1b43,  0x1b44,
+      0x1b6b,  0x1b6c,  0x1b6d,  0x1b6e,  0x1b6f,  0x1b70,  0x1b71,  0x1b72,
+      0x1b73,  0x1b80,  0x1b81,  0x1b82,  0x1ba1,  0x1ba2,  0x1ba3,  0x1ba4,
+      0x1ba5,  0x1ba6,  0x1ba7,  0x1ba8,  0x1ba9,  0x1baa,  0x1bab,  0x1bac,
+      0x1bad,  0x1be6,  0x1be7,  0x1be8,  0x1be9,  0x1bea,  0x1beb,  0x1bec,
+      0x1bed,  0x1bee,  0x1bef,  0x1bf0,  0x1bf1,  0x1bf2,  0x1bf3,  0x1c24,
+      0x1c25,  0x1c26,  0x1c27,  0x1c28,  0x1c29,  0x1c2a,  0x1c2b,  0x1c2c,
+      0x1c2d,  0x1c2e,  0x1c2f,  0x1c30,  0x1c31,  0x1c32,  0x1c33,  0x1c34,
+      0x1c35,  0x1c36,  0x1c37,  0x1cd0,  0x1cd1,  0x1cd2,  0x1cd4,  0x1cd5,
+      0x1cd6,  0x1cd7,  0x1cd8,  0x1cd9,  0x1cda,  0x1cdb,  0x1cdc,  0x1cdd,
+      0x1cde,  0x1cdf,  0x1ce0,  0x1ce1,  0x1ce2,  0x1ce3,  0x1ce4,  0x1ce5,
+      0x1ce6,  0x1ce7,  0x1ce8,  0x1ced,  0x1cf4,  0x1cf7,  0x1cf8,  0x1cf9,
+      0x1dc0,  0x1dc1,  0x1dc2,  0x1dc3,  0x1dc4,  0x1dc5,  0x1dc6,  0x1dc7,
+      0x1dc8,  0x1dc9,  0x1dca,  0x1dcb,  0x1dcc,  0x1dcd,  0x1dce,  0x1dcf,
+      0x1dd0,  0x1dd1,  0x1dd2,  0x1dd3,  0x1dd4,  0x1dd5,  0x1dd6,  0x1dd7,
+      0x1dd8,  0x1dd9,  0x1dda,  0x1ddb,  0x1ddc,  0x1ddd,  0x1dde,  0x1ddf,
+      0x1de0,  0x1de1,  0x1de2,  0x1de3,  0x1de4,  0x1de5,  0x1de6,  0x1de7,
+      0x1de8,  0x1de9,  0x1dea,  0x1deb,  0x1dec,  0x1ded,  0x1dee,  0x1def,
+      0x1df0,  0x1df1,  0x1df2,  0x1df3,  0x1df4,  0x1df5,  0x1df6,  0x1df7,
+      0x1df8,  0x1df9,  0x1dfb,  0x1dfc,  0x1dfd,  0x1dfe,  0x1dff,  0x20d0,
+      0x20d1,  0x20d2,  0x20d3,  0x20d4,  0x20d5,  0x20d6,  0x20d7,  0x20d8,
+      0x20d9,  0x20da,  0x20db,  0x20dc,  0x20dd,  0x20de,  0x20df,  0x20e0,
+      0x20e1,  0x20e2,  0x20e3,  0x20e4,  0x20e5,  0x20e6,  0x20e7,  0x20e8,
+      0x20e9,  0x20ea,  0x20eb,  0x20ec,  0x20ed,  0x20ee,  0x20ef,  0x20f0,
+      0x2cef,  0x2cf0,  0x2cf1,  0x2d7f,  0x2de0,  0x2de1,  0x2de2,  0x2de3,
+      0x2de4,  0x2de5,  0x2de6,  0x2de7,  0x2de8,  0x2de9,  0x2dea,  0x2deb,
+      0x2dec,  0x2ded,  0x2dee,  0x2def,  0x2df0,  0x2df1,  0x2df2,  0x2df3,
+      0x2df4,  0x2df5,  0x2df6,  0x2df7,  0x2df8,  0x2df9,  0x2dfa,  0x2dfb,
+      0x2dfc,  0x2dfd,  0x2dfe,  0x2dff,  0x302a,  0x302b,  0x302c,  0x302d,
+      0x302e,  0x302f,  0x3099,  0x309a,  0xa66f,  0xa670,  0xa671,  0xa672,
+      0xa674,  0xa675,  0xa676,  0xa677,  0xa678,  0xa679,  0xa67a,  0xa67b,
+      0xa67c,  0xa67d,  0xa69e,  0xa69f,  0xa6f0,  0xa6f1,  0xa802,  0xa806,
+      0xa80b,  0xa823,  0xa824,  0xa825,  0xa826,  0xa827,  0xa82c,  0xa880,
+      0xa881,  0xa8b4,  0xa8b5,  0xa8b6,  0xa8b7,  0xa8b8,  0xa8b9,  0xa8ba,
+      0xa8bb,  0xa8bc,  0xa8bd,  0xa8be,  0xa8bf,  0xa8c0,  0xa8c1,  0xa8c2,
+      0xa8c3,  0xa8c4,  0xa8c5,  0xa8e0,  0xa8e1,  0xa8e2,  0xa8e3,  0xa8e4,
+      0xa8e5,  0xa8e6,  0xa8e7,  0xa8e8,  0xa8e9,  0xa8ea,  0xa8eb,  0xa8ec,
+      0xa8ed,  0xa8ee,  0xa8ef,  0xa8f0,  0xa8f1,  0xa8ff,  0xa926,  0xa927,
+      0xa928,  0xa929,  0xa92a,  0xa92b,  0xa92c,  0xa92d,  0xa947,  0xa948,
+      0xa949,  0xa94a,  0xa94b,  0xa94c,  0xa94d,  0xa94e,  0xa94f,  0xa950,
+      0xa951,  0xa952,  0xa953,  0xa980,  0xa981,  0xa982,  0xa983,  0xa9b3,
+      0xa9b4,  0xa9b5,  0xa9b6,  0xa9b7,  0xa9b8,  0xa9b9,  0xa9ba,  0xa9bb,
+      0xa9bc,  0xa9bd,  0xa9be,  0xa9bf,  0xa9c0,  0xa9e5,  0xaa29,  0xaa2a,
+      0xaa2b,  0xaa2c,  0xaa2d,  0xaa2e,  0xaa2f,  0xaa30,  0xaa31,  0xaa32,
+      0xaa33,  0xaa34,  0xaa35,  0xaa36,  0xaa43,  0xaa4c,  0xaa4d,  0xaa7b,
+      0xaa7c,  0xaa7d,  0xaab0,  0xaab2,  0xaab3,  0xaab4,  0xaab7,  0xaab8,
+      0xaabe,  0xaabf,  0xaac1,  0xaaeb,  0xaaec,  0xaaed,  0xaaee,  0xaaef,
+      0xaaf5,  0xaaf6,  0xabe3,  0xabe4,  0xabe5,  0xabe6,  0xabe7,  0xabe8,
+      0xabe9,  0xabea,  0xabec,  0xabed,  0xfb1e,  0xfe00,  0xfe01,  0xfe02,
+      0xfe03,  0xfe04,  0xfe05,  0xfe06,  0xfe07,  0xfe08,  0xfe09,  0xfe0a,
+      0xfe0b,  0xfe0c,  0xfe0d,  0xfe0e,  0xfe0f,  0xfe20,  0xfe21,  0xfe22,
+      0xfe23,  0xfe24,  0xfe25,  0xfe26,  0xfe27,  0xfe28,  0xfe29,  0xfe2a,
+      0xfe2b,  0xfe2c,  0xfe2d,  0xfe2e,  0xfe2f,  0x101fd, 0x102e0, 0x10376,
+      0x10377, 0x10378, 0x10379, 0x1037a, 0x10a01, 0x10a02, 0x10a03, 0x10a05,
+      0x10a06, 0x10a0c, 0x10a0d, 0x10a0e, 0x10a0f, 0x10a38, 0x10a39, 0x10a3a,
+      0x10a3f, 0x10ae5, 0x10ae6, 0x10d24, 0x10d25, 0x10d26, 0x10d27, 0x10eab,
+      0x10eac, 0x10f46, 0x10f47, 0x10f48, 0x10f49, 0x10f4a, 0x10f4b, 0x10f4c,
+      0x10f4d, 0x10f4e, 0x10f4f, 0x10f50, 0x11000, 0x11001, 0x11002, 0x11038,
+      0x11039, 0x1103a, 0x1103b, 0x1103c, 0x1103d, 0x1103e, 0x1103f, 0x11040,
+      0x11041, 0x11042, 0x11043, 0x11044, 0x11045, 0x11046, 0x1107f, 0x11080,
+      0x11081, 0x11082, 0x110b0, 0x110b1, 0x110b2, 0x110b3, 0x110b4, 0x110b5,
+      0x110b6, 0x110b7, 0x110b8, 0x110b9, 0x110ba, 0x11100, 0x11101, 0x11102,
+      0x11127, 0x11128, 0x11129, 0x1112a, 0x1112b, 0x1112c, 0x1112d, 0x1112e,
+      0x1112f, 0x11130, 0x11131, 0x11132, 0x11133, 0x11134, 0x11145, 0x11146,
+      0x11173, 0x11180, 0x11181, 0x11182, 0x111b3, 0x111b4, 0x111b5, 0x111b6,
+      0x111b7, 0x111b8, 0x111b9, 0x111ba, 0x111bb, 0x111bc, 0x111bd, 0x111be,
+      0x111bf, 0x111c0, 0x111c9, 0x111ca, 0x111cb, 0x111cc, 0x111ce, 0x111cf,
+      0x1122c, 0x1122d, 0x1122e, 0x1122f, 0x11230, 0x11231, 0x11232, 0x11233,
+      0x11234, 0x11235, 0x11236, 0x11237, 0x1123e, 0x112df, 0x112e0, 0x112e1,
+      0x112e2, 0x112e3, 0x112e4, 0x112e5, 0x112e6, 0x112e7, 0x112e8, 0x112e9,
+      0x112ea, 0x11300, 0x11301, 0x11302, 0x11303, 0x1133b, 0x1133c, 0x1133e,
+      0x1133f, 0x11340, 0x11341, 0x11342, 0x11343, 0x11344, 0x11347, 0x11348,
+      0x1134b, 0x1134c, 0x1134d, 0x11357, 0x11362, 0x11363, 0x11366, 0x11367,
+      0x11368, 0x11369, 0x1136a, 0x1136b, 0x1136c, 0x11370, 0x11371, 0x11372,
+      0x11373, 0x11374, 0x11435, 0x11436, 0x11437, 0x11438, 0x11439, 0x1143a,
+      0x1143b, 0x1143c, 0x1143d, 0x1143e, 0x1143f, 0x11440, 0x11441, 0x11442,
+      0x11443, 0x11444, 0x11445, 0x11446, 0x1145e, 0x114b0, 0x114b1, 0x114b2,
+      0x114b3, 0x114b4, 0x114b5, 0x114b6, 0x114b7, 0x114b8, 0x114b9, 0x114ba,
+      0x114bb, 0x114bc, 0x114bd, 0x114be, 0x114bf, 0x114c0, 0x114c1, 0x114c2,
+      0x114c3, 0x115af, 0x115b0, 0x115b1, 0x115b2, 0x115b3, 0x115b4, 0x115b5,
+      0x115b8, 0x115b9, 0x115ba, 0x115bb, 0x115bc, 0x115bd, 0x115be, 0x115bf,
+      0x115c0, 0x115dc, 0x115dd, 0x11630, 0x11631, 0x11632, 0x11633, 0x11634,
+      0x11635, 0x11636, 0x11637, 0x11638, 0x11639, 0x1163a, 0x1163b, 0x1163c,
+      0x1163d, 0x1163e, 0x1163f, 0x11640, 0x116ab, 0x116ac, 0x116ad, 0x116ae,
+      0x116af, 0x116b0, 0x116b1, 0x116b2, 0x116b3, 0x116b4, 0x116b5, 0x116b6,
+      0x116b7, 0x1171d, 0x1171e, 0x1171f, 0x11720, 0x11721, 0x11722, 0x11723,
+      0x11724, 0x11725, 0x11726, 0x11727, 0x11728, 0x11729, 0x1172a, 0x1172b,
+      0x1182c, 0x1182d, 0x1182e, 0x1182f, 0x11830, 0x11831, 0x11832, 0x11833,
+      0x11834, 0x11835, 0x11836, 0x11837, 0x11838, 0x11839, 0x1183a, 0x11930,
+      0x11931, 0x11932, 0x11933, 0x11934, 0x11935, 0x11937, 0x11938, 0x1193b,
+      0x1193c, 0x1193d, 0x1193e, 0x11940, 0x11942, 0x11943, 0x119d1, 0x119d2,
+      0x119d3, 0x119d4, 0x119d5, 0x119d6, 0x119d7, 0x119da, 0x119db, 0x119dc,
+      0x119dd, 0x119de, 0x119df, 0x119e0, 0x119e4, 0x11a01, 0x11a02, 0x11a03,
+      0x11a04, 0x11a05, 0x11a06, 0x11a07, 0x11a08, 0x11a09, 0x11a0a, 0x11a33,
+      0x11a34, 0x11a35, 0x11a36, 0x11a37, 0x11a38, 0x11a39, 0x11a3b, 0x11a3c,
+      0x11a3d, 0x11a3e, 0x11a47, 0x11a51, 0x11a52, 0x11a53, 0x11a54, 0x11a55,
+      0x11a56, 0x11a57, 0x11a58, 0x11a59, 0x11a5a, 0x11a5b, 0x11a8a, 0x11a8b,
+      0x11a8c, 0x11a8d, 0x11a8e, 0x11a8f, 0x11a90, 0x11a91, 0x11a92, 0x11a93,
+      0x11a94, 0x11a95, 0x11a96, 0x11a97, 0x11a98, 0x11a99, 0x11c2f, 0x11c30,
+      0x11c31, 0x11c32, 0x11c33, 0x11c34, 0x11c35, 0x11c36, 0x11c38, 0x11c39,
+      0x11c3a, 0x11c3b, 0x11c3c, 0x11c3d, 0x11c3e, 0x11c3f, 0x11c92, 0x11c93,
+      0x11c94, 0x11c95, 0x11c96, 0x11c97, 0x11c98, 0x11c99, 0x11c9a, 0x11c9b,
+      0x11c9c, 0x11c9d, 0x11c9e, 0x11c9f, 0x11ca0, 0x11ca1, 0x11ca2, 0x11ca3,
+      0x11ca4, 0x11ca5, 0x11ca6, 0x11ca7, 0x11ca9, 0x11caa, 0x11cab, 0x11cac,
+      0x11cad, 0x11cae, 0x11caf, 0x11cb0, 0x11cb1, 0x11cb2, 0x11cb3, 0x11cb4,
+      0x11cb5, 0x11cb6, 0x11d31, 0x11d32, 0x11d33, 0x11d34, 0x11d35, 0x11d36,
+      0x11d3a, 0x11d3c, 0x11d3d, 0x11d3f, 0x11d40, 0x11d41, 0x11d42, 0x11d43,
+      0x11d44, 0x11d45, 0x11d47, 0x11d8a, 0x11d8b, 0x11d8c, 0x11d8d, 0x11d8e,
+      0x11d90, 0x11d91, 0x11d93, 0x11d94, 0x11d95, 0x11d96, 0x11d97, 0x11ef3,
+      0x11ef4, 0x11ef5, 0x11ef6, 0x16af0, 0x16af1, 0x16af2, 0x16af3, 0x16af4,
+      0x16b30, 0x16b31, 0x16b32, 0x16b33, 0x16b34, 0x16b35, 0x16b36, 0x16f4f,
+      0x16f51, 0x16f52, 0x16f53, 0x16f54, 0x16f55, 0x16f56, 0x16f57, 0x16f58,
+      0x16f59, 0x16f5a, 0x16f5b, 0x16f5c, 0x16f5d, 0x16f5e, 0x16f5f, 0x16f60,
+      0x16f61, 0x16f62, 0x16f63, 0x16f64, 0x16f65, 0x16f66, 0x16f67, 0x16f68,
+      0x16f69, 0x16f6a, 0x16f6b, 0x16f6c, 0x16f6d, 0x16f6e, 0x16f6f, 0x16f70,
+      0x16f71, 0x16f72, 0x16f73, 0x16f74, 0x16f75, 0x16f76, 0x16f77, 0x16f78,
+      0x16f79, 0x16f7a, 0x16f7b, 0x16f7c, 0x16f7d, 0x16f7e, 0x16f7f, 0x16f80,
+      0x16f81, 0x16f82, 0x16f83, 0x16f84, 0x16f85, 0x16f86, 0x16f87, 0x16f8f,
+      0x16f90, 0x16f91, 0x16f92, 0x16fe4, 0x16ff0, 0x16ff1, 0x1bc9d, 0x1bc9e,
+      0x1d165, 0x1d166, 0x1d167, 0x1d168, 0x1d169, 0x1d16d, 0x1d16e, 0x1d16f,
+      0x1d170, 0x1d171, 0x1d172, 0x1d17b, 0x1d17c, 0x1d17d, 0x1d17e, 0x1d17f,
+      0x1d180, 0x1d181, 0x1d182, 0x1d185, 0x1d186, 0x1d187, 0x1d188, 0x1d189,
+      0x1d18a, 0x1d18b, 0x1d1aa, 0x1d1ab, 0x1d1ac, 0x1d1ad, 0x1d242, 0x1d243,
+      0x1d244, 0x1da00, 0x1da01, 0x1da02, 0x1da03, 0x1da04, 0x1da05, 0x1da06,
+      0x1da07, 0x1da08, 0x1da09, 0x1da0a, 0x1da0b, 0x1da0c, 0x1da0d, 0x1da0e,
+      0x1da0f, 0x1da10, 0x1da11, 0x1da12, 0x1da13, 0x1da14, 0x1da15, 0x1da16,
+      0x1da17, 0x1da18, 0x1da19, 0x1da1a, 0x1da1b, 0x1da1c, 0x1da1d, 0x1da1e,
+      0x1da1f, 0x1da20, 0x1da21, 0x1da22, 0x1da23, 0x1da24, 0x1da25, 0x1da26,
+      0x1da27, 0x1da28, 0x1da29, 0x1da2a, 0x1da2b, 0x1da2c, 0x1da2d, 0x1da2e,
+      0x1da2f, 0x1da30, 0x1da31, 0x1da32, 0x1da33, 0x1da34, 0x1da35, 0x1da36,
+      0x1da3b, 0x1da3c, 0x1da3d, 0x1da3e, 0x1da3f, 0x1da40, 0x1da41, 0x1da42,
+      0x1da43, 0x1da44, 0x1da45, 0x1da46, 0x1da47, 0x1da48, 0x1da49, 0x1da4a,
+      0x1da4b, 0x1da4c, 0x1da4d, 0x1da4e, 0x1da4f, 0x1da50, 0x1da51, 0x1da52,
+      0x1da53, 0x1da54, 0x1da55, 0x1da56, 0x1da57, 0x1da58, 0x1da59, 0x1da5a,
+      0x1da5b, 0x1da5c, 0x1da5d, 0x1da5e, 0x1da5f, 0x1da60, 0x1da61, 0x1da62,
+      0x1da63, 0x1da64, 0x1da65, 0x1da66, 0x1da67, 0x1da68, 0x1da69, 0x1da6a,
+      0x1da6b, 0x1da6c, 0x1da75, 0x1da84, 0x1da9b, 0x1da9c, 0x1da9d, 0x1da9e,
+      0x1da9f, 0x1daa1, 0x1daa2, 0x1daa3, 0x1daa4, 0x1daa5, 0x1daa6, 0x1daa7,
+      0x1daa8, 0x1daa9, 0x1daaa, 0x1daab, 0x1daac, 0x1daad, 0x1daae, 0x1daaf,
+      0x1e000, 0x1e001, 0x1e002, 0x1e003, 0x1e004, 0x1e005, 0x1e006, 0x1e008,
+      0x1e009, 0x1e00a, 0x1e00b, 0x1e00c, 0x1e00d, 0x1e00e, 0x1e00f, 0x1e010,
+      0x1e011, 0x1e012, 0x1e013, 0x1e014, 0x1e015, 0x1e016, 0x1e017, 0x1e018,
+      0x1e01b, 0x1e01c, 0x1e01d, 0x1e01e, 0x1e01f, 0x1e020, 0x1e021, 0x1e023,
+      0x1e024, 0x1e026, 0x1e027, 0x1e028, 0x1e029, 0x1e02a, 0x1e130, 0x1e131,
+      0x1e132, 0x1e133, 0x1e134, 0x1e135, 0x1e136, 0x1e2ec, 0x1e2ed, 0x1e2ee,
+      0x1e2ef, 0x1e8d0, 0x1e8d1, 0x1e8d2, 0x1e8d3, 0x1e8d4, 0x1e8d5, 0x1e8d6,
+      0x1e944, 0x1e945, 0x1e946, 0x1e947, 0x1e948, 0x1e949, 0x1e94a, 0xe0100,
+      0xe0101, 0xe0102, 0xe0103, 0xe0104, 0xe0105, 0xe0106, 0xe0107, 0xe0108,
+      0xe0109, 0xe010a, 0xe010b, 0xe010c, 0xe010d, 0xe010e, 0xe010f, 0xe0110,
+      0xe0111, 0xe0112, 0xe0113, 0xe0114, 0xe0115, 0xe0116, 0xe0117, 0xe0118,
+      0xe0119, 0xe011a, 0xe011b, 0xe011c, 0xe011d, 0xe011e, 0xe011f, 0xe0120,
+      0xe0121, 0xe0122, 0xe0123, 0xe0124, 0xe0125, 0xe0126, 0xe0127, 0xe0128,
+      0xe0129, 0xe012a, 0xe012b, 0xe012c, 0xe012d, 0xe012e, 0xe012f, 0xe0130,
+      0xe0131, 0xe0132, 0xe0133, 0xe0134, 0xe0135, 0xe0136, 0xe0137, 0xe0138,
+      0xe0139, 0xe013a, 0xe013b, 0xe013c, 0xe013d, 0xe013e, 0xe013f, 0xe0140,
+      0xe0141, 0xe0142, 0xe0143, 0xe0144, 0xe0145, 0xe0146, 0xe0147, 0xe0148,
+      0xe0149, 0xe014a, 0xe014b, 0xe014c, 0xe014d, 0xe014e, 0xe014f, 0xe0150,
+      0xe0151, 0xe0152, 0xe0153, 0xe0154, 0xe0155, 0xe0156, 0xe0157, 0xe0158,
+      0xe0159, 0xe015a, 0xe015b, 0xe015c, 0xe015d, 0xe015e, 0xe015f, 0xe0160,
+      0xe0161, 0xe0162, 0xe0163, 0xe0164, 0xe0165, 0xe0166, 0xe0167, 0xe0168,
+      0xe0169, 0xe016a, 0xe016b, 0xe016c, 0xe016d, 0xe016e, 0xe016f, 0xe0170,
+      0xe0171, 0xe0172, 0xe0173, 0xe0174, 0xe0175, 0xe0176, 0xe0177, 0xe0178,
+      0xe0179, 0xe017a, 0xe017b, 0xe017c, 0xe017d, 0xe017e, 0xe017f, 0xe0180,
+      0xe0181, 0xe0182, 0xe0183, 0xe0184, 0xe0185, 0xe0186, 0xe0187, 0xe0188,
+      0xe0189, 0xe018a, 0xe018b, 0xe018c, 0xe018d, 0xe018e, 0xe018f, 0xe0190,
+      0xe0191, 0xe0192, 0xe0193, 0xe0194, 0xe0195, 0xe0196, 0xe0197, 0xe0198,
+      0xe0199, 0xe019a, 0xe019b, 0xe019c, 0xe019d, 0xe019e, 0xe019f, 0xe01a0,
+      0xe01a1, 0xe01a2, 0xe01a3, 0xe01a4, 0xe01a5, 0xe01a6, 0xe01a7, 0xe01a8,
+      0xe01a9, 0xe01aa, 0xe01ab, 0xe01ac, 0xe01ad, 0xe01ae, 0xe01af, 0xe01b0,
+      0xe01b1, 0xe01b2, 0xe01b3, 0xe01b4, 0xe01b5, 0xe01b6, 0xe01b7, 0xe01b8,
+      0xe01b9, 0xe01ba, 0xe01bb, 0xe01bc, 0xe01bd, 0xe01be, 0xe01bf, 0xe01c0,
+      0xe01c1, 0xe01c2, 0xe01c3, 0xe01c4, 0xe01c5, 0xe01c6, 0xe01c7, 0xe01c8,
+      0xe01c9, 0xe01ca, 0xe01cb, 0xe01cc, 0xe01cd, 0xe01ce, 0xe01cf, 0xe01d0,
+      0xe01d1, 0xe01d2, 0xe01d3, 0xe01d4, 0xe01d5, 0xe01d6, 0xe01d7, 0xe01d8,
+      0xe01d9, 0xe01da, 0xe01db, 0xe01dc, 0xe01dd, 0xe01de, 0xe01df, 0xe01e0,
+      0xe01e1, 0xe01e2, 0xe01e3, 0xe01e4, 0xe01e5, 0xe01e6, 0xe01e7, 0xe01e8,
+      0xe01e9, 0xe01ea, 0xe01eb, 0xe01ec, 0xe01ed, 0xe01ee, 0xe01ef};
+  if (std::binary_search(std::begin(combining), std::end(combining),
+                         label.front())) {
+    return false;
+  }
+  // We verify this next step as part of the mapping:
+  // ---------------------------------------------
+  // Each code point in the label must only have certain status values
+  // according to Section 5, IDNA Mapping Table:
+  // - For Transitional Processing, each value must be valid.
+  // - For Nontransitional Processing, each value must be either valid or
+  // deviation.
+
+  // If CheckJoiners, the label must satisfy the ContextJ rules from Appendix
+  // A, in The Unicode Code Points and Internationalized Domain Names for
+  // Applications (IDNA) [IDNA2008].
+  constexpr static uint32_t virama[] = {
+      0x094D,  0x09CD,  0x0A4D,  0x0ACD,  0x0B4D,  0x0BCD,  0x0C4D,  0x0CCD,
+      0x0D3B,  0x0D3C,  0x0D4D,  0x0DCA,  0x0E3A,  0x0EBA,  0x0F84,  0x1039,
+      0x103A,  0x1714,  0x1734,  0x17D2,  0x1A60,  0x1B44,  0x1BAA,  0x1BAB,
+      0x1BF2,  0x1BF3,  0x2D7F,  0xA806,  0xA82C,  0xA8C4,  0xA953,  0xA9C0,
+      0xAAF6,  0xABED,  0x10A3F, 0x11046, 0x1107F, 0x110B9, 0x11133, 0x11134,
+      0x111C0, 0x11235, 0x112EA, 0x1134D, 0x11442, 0x114C2, 0x115BF, 0x1163F,
+      0x116B6, 0x1172B, 0x11839, 0x1193D, 0x1193E, 0x119E0, 0x11A34, 0x11A47,
+      0x11A99, 0x11C3F, 0x11D44, 0x11D45, 0x11D97};
+  constexpr static uint32_t R[] = {
+      0x622, 0x623, 0x624, 0x625, 0x627, 0x629, 0x62f, 0x630, 0x631,
+      0x632, 0x648, 0x671, 0x672, 0x673, 0x675, 0x676, 0x677, 0x688,
+      0x689, 0x68a, 0x68b, 0x68c, 0x68d, 0x68e, 0x68f, 0x690, 0x691,
+      0x692, 0x693, 0x694, 0x695, 0x696, 0x697, 0x698, 0x699, 0x6c0,
+      0x6c3, 0x6c4, 0x6c5, 0x6c6, 0x6c7, 0x6c8, 0x6c9, 0x6ca, 0x6cb,
+      0x6cd, 0x6cf, 0x6d2, 0x6d3, 0x6d5, 0x6ee, 0x6ef, 0x710, 0x715,
+      0x716, 0x717, 0x718, 0x719, 0x71e, 0x728, 0x72a, 0x72c, 0x72f,
+      0x74d, 0x759, 0x75a, 0x75b, 0x854, 0x8aa, 0x8ab, 0x8ac};
+  constexpr static uint32_t L[] = {0xa872};
+  constexpr static uint32_t D[] = {
+      0x620,  0x626,  0x628,  0x62a,  0x62b,  0x62c,  0x62d,  0x62e,  0x633,
+      0x634,  0x635,  0x636,  0x637,  0x638,  0x639,  0x63a,  0x63b,  0x63c,
+      0x63d,  0x63e,  0x63f,  0x641,  0x642,  0x643,  0x644,  0x645,  0x646,
+      0x647,  0x649,  0x64a,  0x66e,  0x66f,  0x678,  0x679,  0x67a,  0x67b,
+      0x67c,  0x67d,  0x67e,  0x67f,  0x680,  0x681,  0x682,  0x683,  0x684,
+      0x685,  0x686,  0x687,  0x69a,  0x69b,  0x69c,  0x69d,  0x69e,  0x69f,
+      0x6a0,  0x6a1,  0x6a2,  0x6a3,  0x6a4,  0x6a5,  0x6a6,  0x6a7,  0x6a8,
+      0x6a9,  0x6aa,  0x6ab,  0x6ac,  0x6ad,  0x6ae,  0x6af,  0x6b0,  0x6b1,
+      0x6b2,  0x6b3,  0x6b4,  0x6b5,  0x6b6,  0x6b7,  0x6b8,  0x6b9,  0x6ba,
+      0x6bb,  0x6bc,  0x6bd,  0x6be,  0x6bf,  0x6c1,  0x6c2,  0x6cc,  0x6ce,
+      0x6d0,  0x6d1,  0x6fa,  0x6fb,  0x6fc,  0x6ff,  0x712,  0x713,  0x714,
+      0x71a,  0x71b,  0x71c,  0x71d,  0x71f,  0x720,  0x721,  0x722,  0x723,
+      0x724,  0x725,  0x726,  0x727,  0x729,  0x72b,  0x72d,  0x72e,  0x74e,
+      0x74f,  0x750,  0x751,  0x752,  0x753,  0x754,  0x755,  0x756,  0x757,
+      0x758,  0x75c,  0x75d,  0x75e,  0x75f,  0x760,  0x761,  0x762,  0x763,
+      0x764,  0x765,  0x766,  0x850,  0x851,  0x852,  0x853,  0x855,  0x8a0,
+      0x8a2,  0x8a3,  0x8a4,  0x8a5,  0x8a6,  0x8a7,  0x8a8,  0x8a9,  0x1807,
+      0x1820, 0x1821, 0x1822, 0x1823, 0x1824, 0x1825, 0x1826, 0x1827, 0x1828,
+      0x1829, 0x182a, 0x182b, 0x182c, 0x182d, 0x182e, 0x182f, 0x1830, 0x1831,
+      0x1832, 0x1833, 0x1834, 0x1835, 0x1836, 0x1837, 0x1838, 0x1839, 0x183a,
+      0x183b, 0x183c, 0x183d, 0x183e, 0x183f, 0x1840, 0x1841, 0x1842, 0x1843,
+      0x1844, 0x1845, 0x1846, 0x1847, 0x1848, 0x1849, 0x184a, 0x184b, 0x184c,
+      0x184d, 0x184e, 0x184f, 0x1850, 0x1851, 0x1852, 0x1853, 0x1854, 0x1855,
+      0x1856, 0x1857, 0x1858, 0x1859, 0x185a, 0x185b, 0x185c, 0x185d, 0x185e,
+      0x185f, 0x1860, 0x1861, 0x1862, 0x1863, 0x1864, 0x1865, 0x1866, 0x1867,
+      0x1868, 0x1869, 0x186a, 0x186b, 0x186c, 0x186d, 0x186e, 0x186f, 0x1870,
+      0x1871, 0x1872, 0x1873, 0x1874, 0x1875, 0x1876, 0x1877, 0x1887, 0x1888,
+      0x1889, 0x188a, 0x188b, 0x188c, 0x188d, 0x188e, 0x188f, 0x1890, 0x1891,
+      0x1892, 0x1893, 0x1894, 0x1895, 0x1896, 0x1897, 0x1898, 0x1899, 0x189a,
+      0x189b, 0x189c, 0x189d, 0x189e, 0x189f, 0x18a0, 0x18a1, 0x18a2, 0x18a3,
+      0x18a4, 0x18a5, 0x18a6, 0x18a7, 0x18a8, 0x18aa, 0xa840, 0xa841, 0xa842,
+      0xa843, 0xa844, 0xa845, 0xa846, 0xa847, 0xa848, 0xa849, 0xa84a, 0xa84b,
+      0xa84c, 0xa84d, 0xa84e, 0xa84f, 0xa850, 0xa851, 0xa852, 0xa853, 0xa854,
+      0xa855, 0xa856, 0xa857, 0xa858, 0xa859, 0xa85a, 0xa85b, 0xa85c, 0xa85d,
+      0xa85e, 0xa85f, 0xa860, 0xa861, 0xa862, 0xa863, 0xa864, 0xa865, 0xa866,
+      0xa867, 0xa868, 0xa869, 0xa86a, 0xa86b, 0xa86c, 0xa86d, 0xa86e, 0xa86f,
+      0xa870, 0xa871};
+
+  for (size_t i = 0; i < label.size(); i++) {
+    uint32_t c = label[i];
+    if (c == 0x200c) {
+      if (i > 0) {
+        if (std::binary_search(std::begin(virama), std::end(virama),
+                               label[i - 1])) {
+          return true;
+        }
+      }
+      if ((i == 0) || (i + 1 >= label.size())) {
+        return false;
+      }
+      // we go backward looking for L or D
+      auto is_l_or_d = [](uint32_t code) {
+        return std::binary_search(std::begin(L), std::end(L), code) ||
+               std::binary_search(std::begin(D), std::end(D), code);
+      };
+      auto is_r_or_d = [](uint32_t code) {
+        return std::binary_search(std::begin(R), std::end(R), code) ||
+               std::binary_search(std::begin(D), std::end(D), code);
+      };
+      std::u32string_view before = label.substr(0, i);
+      std::u32string_view after = label.substr(i + 1);
+      return (std::find_if(before.begin(), before.end(), is_l_or_d) !=
+              before.end()) &&
+             (std::find_if(after.begin(), after.end(), is_r_or_d) !=
+              after.end());
+    } else if (c == 0x200d) {
+      if (i > 0) {
+        if (std::binary_search(std::begin(virama), std::end(virama),
+                               label[i - 1])) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  // If CheckBidi, and if the domain name is a  Bidi domain name, then the label
+  // must satisfy all six of the numbered conditions in [IDNA2008] RFC 5893,
+  // Section 2.
+
+  // The following rule, consisting of six conditions, applies to labels
+  // in Bidi domain names.  The requirements that this rule satisfies are
+  // described in Section 3.  All of the conditions must be satisfied for
+  // the rule to be satisfied.
+  //
+  //  1.  The first character must be a character with Bidi property L, R,
+  //     or AL.  If it has the R or AL property, it is an RTL label; if it
+  //     has the L property, it is an LTR label.
+  //
+  //  2.  In an RTL label, only characters with the Bidi properties R, AL,
+  //      AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
+  //
+  //   3.  In an RTL label, the end of the label must be a character with
+  //       Bidi property R, AL, EN, or AN, followed by zero or more
+  //       characters with Bidi property NSM.
+  //
+  //   4.  In an RTL label, if an EN is present, no AN may be present, and
+  //       vice versa.
+  //
+  //  5.  In an LTR label, only characters with the Bidi properties L, EN,
+  //       ES, CS, ET, ON, BN, or NSM are allowed.
+  //
+  //   6.  In an LTR label, the end of the label must be a character with
+  //       Bidi property L or EN, followed by zero or more characters with
+  //       Bidi property NSM.
+
+  size_t last_non_nsm_char = find_last_not_of_nsm(label);
+  if (last_non_nsm_char == std::u32string_view::npos) {
+    return false;
+  }
+
+  // A "Bidi domain name" is a domain name that contains at least one RTL label.
+  // The following rule, consisting of six conditions, applies to labels in Bidi
+  // domain names.
+  if (is_rtl_label(label)) {
+    // The first character must be a character with Bidi property L, R,
+    // or AL. If it has the R or AL property, it is an RTL label; if it
+    // has the L property, it is an LTR label.
+
+    if (find_direction(label[0]) == direction::L) {
+      // Eval as LTR
+
+      // In an LTR label, only characters with the Bidi properties L, EN,
+      // ES, CS, ET, ON, BN, or NSM are allowed.
+      for (size_t i = 0; i < last_non_nsm_char; i++) {
+        const direction d = find_direction(label[i]);
+        if (!(d == direction::L || d == direction::EN || d == direction::ES ||
+              d == direction::CS || d == direction::ET || d == direction::ON ||
+              d == direction::BN || d == direction::NSM)) {
+          return false;
+        }
+
+        if ((i == last_non_nsm_char) &&
+            !(d == direction::L || d == direction::EN)) {
+          return false;
+        }
+      }
+
+      return true;
+
+    } else {
+      // Eval as RTL
+
+      bool has_an = false;
+      bool has_en = false;
+      for (size_t i = 0; i <= last_non_nsm_char; i++) {
+        const direction d = find_direction(label[i]);
+
+        // In an RTL label, if an EN is present, no AN may be present, and vice
+        // versa.
+        if ((d == direction::EN && ((has_en = true) && has_an)) ||
+            (d == direction::AN && ((has_an = true) && has_en))) {
+          return false;
+        }
+
+        if (!(d == direction::R || d == direction::AL || d == direction::AN ||
+              d == direction::EN || d == direction::ES || d == direction::CS ||
+              d == direction::ET || d == direction::ON || d == direction::BN ||
+              d == direction::NSM)) {
+          return false;
+        }
+
+        if (i == last_non_nsm_char &&
+            !(d == direction::R || d == direction::AL || d == direction::AN ||
+              d == direction::EN)) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace ada::idna
+/* end file src/validity.cpp */
+/* begin file src/to_ascii.cpp */
+
+#include <algorithm>
+#include <cstdint>
+
+
+namespace ada::idna {
+
+bool begins_with(std::u32string_view view, std::u32string_view prefix) {
+  if (view.size() < prefix.size()) {
+    return false;
+  }
+  // constexpr as of C++20
+  return std::equal(prefix.begin(), prefix.end(), view.begin());
+}
+
+bool begins_with(std::string_view view, std::string_view prefix) {
+  if (view.size() < prefix.size()) {
+    return false;
+  }
+  // constexpr as of C++20
+  return std::equal(prefix.begin(), prefix.end(), view.begin());
+}
+
+bool constexpr is_ascii(std::u32string_view view) {
+  for (uint32_t c : view) {
+    if (c >= 0x80) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool constexpr is_ascii(std::string_view view) {
+  for (uint8_t c : view) {
+    if (c >= 0x80) {
+      return false;
+    }
+  }
+  return true;
+}
+
+constexpr static uint8_t is_forbidden_domain_code_point_table[] = {
+    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, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 1, 0, 0, 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, 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, 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};
+
+static_assert(sizeof(is_forbidden_domain_code_point_table) == 256);
+
+inline bool is_forbidden_domain_code_point(const char c) noexcept {
+  return is_forbidden_domain_code_point_table[uint8_t(c)];
+}
+
+bool contains_forbidden_domain_code_point(std::string_view view) {
+  return (
+      std::any_of(view.begin(), view.end(), is_forbidden_domain_code_point));
+}
+
+// We return "" on error.
+static std::string from_ascii_to_ascii(std::string_view ut8_string) {
+  static const std::string error = "";
+  // copy and map
+  // we could be more efficient by avoiding the copy when unnecessary.
+  std::string mapped_string = std::string(ut8_string);
+  ascii_map(mapped_string.data(), mapped_string.size());
+  std::string out;
+  size_t label_start = 0;
+
+  while (label_start != mapped_string.size()) {
+    size_t loc_dot = mapped_string.find('.', label_start);
+    bool is_last_label = (loc_dot == std::string_view::npos);
+    size_t label_size = is_last_label ? mapped_string.size() - label_start
+                                      : loc_dot - label_start;
+    size_t label_size_with_dot = is_last_label ? label_size : label_size + 1;
+    std::string_view label_view(mapped_string.data() + label_start, label_size);
+    label_start += label_size_with_dot;
+    if (label_size == 0) {
+      // empty label? Nothing to do.
+    } else if (begins_with(label_view, "xn--")) {
+      // The xn-- part is the expensive game.
+      out.append(label_view);
+      std::string_view puny_segment_ascii(
+          out.data() + out.size() - label_view.size() + 4,
+          label_view.size() - 4);
+      std::u32string tmp_buffer;
+      bool is_ok = ada::idna::punycode_to_utf32(puny_segment_ascii, tmp_buffer);
+      if (!is_ok) {
+        return error;
+      }
+      std::u32string post_map = ada::idna::map(tmp_buffer);
+      if (tmp_buffer != post_map) {
+        return error;
+      }
+      std::u32string pre_normal = post_map;
+      normalize(post_map);
+      if (post_map != pre_normal) {
+        return error;
+      }
+      if (post_map.empty()) {
+        return error;
+      }
+      if (!is_label_valid(post_map)) {
+        return error;
+      }
+    } else {
+      out.append(label_view);
+    }
+    if (!is_last_label) {
+      out.push_back('.');
+    }
+  }
+  return out;
+}
+
+// We return "" on error.
+std::string to_ascii(std::string_view ut8_string) {
+  if (is_ascii(ut8_string)) {
+    return from_ascii_to_ascii(ut8_string);
+  }
+  static const std::string error = "";
+  // We convert to UTF-32
+  size_t utf32_length =
+      ada::idna::utf32_length_from_utf8(ut8_string.data(), ut8_string.size());
+  std::u32string utf32(utf32_length, '\0');
+  size_t actual_utf32_length = ada::idna::utf8_to_utf32(
+      ut8_string.data(), ut8_string.size(), utf32.data());
+  if (actual_utf32_length == 0) {
+    return error;
+  }
+  // mapping
+  utf32 = ada::idna::map(utf32);
+  normalize(utf32);
+  std::string out;
+  size_t label_start = 0;
+
+  while (label_start != utf32.size()) {
+    size_t loc_dot = utf32.find('.', label_start);
+    bool is_last_label = (loc_dot == std::string_view::npos);
+    size_t label_size =
+        is_last_label ? utf32.size() - label_start : loc_dot - label_start;
+    size_t label_size_with_dot = is_last_label ? label_size : label_size + 1;
+    std::u32string_view label_view(utf32.data() + label_start, label_size);
+    label_start += label_size_with_dot;
+    if (label_size == 0) {
+      // empty label? Nothing to do.
+    } else if (begins_with(label_view, U"xn--")) {
+      // we do not need to check, e.g., Xn-- because mapping goes to lower case
+      for (char32_t c : label_view) {
+        if (c >= 0x80) {
+          return error;
+        }
+        out += (unsigned char)(c);
+      }
+      std::string_view puny_segment_ascii(
+          out.data() + out.size() - label_view.size() + 4,
+          label_view.size() - 4);
+      std::u32string tmp_buffer;
+      bool is_ok = ada::idna::punycode_to_utf32(puny_segment_ascii, tmp_buffer);
+      if (!is_ok) {
+        return error;
+      }
+      std::u32string post_map = ada::idna::map(tmp_buffer);
+      if (tmp_buffer != post_map) {
+        return error;
+      }
+      std::u32string pre_normal = post_map;
+      normalize(post_map);
+      if (post_map != pre_normal) {
+        return error;
+      }
+      if (post_map.empty()) {
+        return error;
+      }
+      if (!is_label_valid(post_map)) {
+        return error;
+      }
+    } else {
+      // The fast path here is an ascii label.
+      if (is_ascii(label_view)) {
+        // no validation needed.
+        for (char32_t c : label_view) {
+          out += (unsigned char)(c);
+        }
+      } else {
+        // slow path.
+        // first check validity.
+        if (!is_label_valid(label_view)) {
+          return error;
+        }
+        // It is valid! So now we must encode it as punycode...
+        out.append("xn--");
+        bool is_ok = ada::idna::utf32_to_punycode(label_view, out);
+        if (!is_ok) {
+          return error;
+        }
+      }
+    }
+    if (!is_last_label) {
+      out.push_back('.');
+    }
+  }
+  return out;
+}
+}  // namespace ada::idna
+/* end file src/to_ascii.cpp */
+/* begin file src/to_unicode.cpp */
+
+#include <algorithm>
+#include <string>
+
+
+namespace ada::idna {
+std::string to_unicode(std::string_view input) {
+  std::string output;
+  output.reserve(input.size());
+
+  size_t label_start = 0;
+  while (label_start < input.size()) {
+    size_t loc_dot = input.find('.', label_start);
+    bool is_last_label = (loc_dot == std::string_view::npos);
+    size_t label_size =
+        is_last_label ? input.size() - label_start : loc_dot - label_start;
+    auto label_view = std::string_view(input.data() + label_start, label_size);
+
+    if (ada::idna::begins_with(label_view, "xn--") &&
+        ada::idna::is_ascii(label_view)) {
+      label_view.remove_prefix(4);
+      if (ada::idna::verify_punycode(label_view)) {
+        std::u32string tmp_buffer;
+        if (ada::idna::punycode_to_utf32(label_view, tmp_buffer)) {
+          auto utf8_size = ada::idna::utf8_length_from_utf32(tmp_buffer.data(),
+                                                             tmp_buffer.size());
+          std::string final_utf8(utf8_size, '\0');
+          ada::idna::utf32_to_utf8(tmp_buffer.data(), tmp_buffer.size(),
+                                   final_utf8.data());
+          output.append(final_utf8);
+        } else {
+          // ToUnicode never fails.  If any step fails, then the original input
+          // sequence is returned immediately in that step.
+          output.append(
+              std::string_view(input.data() + label_start, label_size));
+        }
+      } else {
+        output.append(std::string_view(input.data() + label_start, label_size));
+      }
+    } else {
+      output.append(label_view);
+    }
+
+    if (!is_last_label) {
+      output.push_back('.');
+    }
+
+    label_start += label_size + 1;
+  }
+
+  return output;
+}
+}  // namespace ada::idna
+/* end file src/to_unicode.cpp */
+/* end file src/idna.cpp */
diff --git a/src/checkers.cpp b/src/checkers.cpp
new file mode 100644 (file)
index 0000000..69f8936
--- /dev/null
@@ -0,0 +1,127 @@
+#include "ada/checkers.h"
+#include <algorithm>
+
+namespace ada::checkers {
+
+ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept {
+  // The string is not empty and does not contain upper case ASCII characters.
+  //
+  // Optimization. To be considered as a possible ipv4, the string must end
+  // with 'x' or a lowercase hex character.
+  // Most of the time, this will be false so this simple check will save a lot
+  // of effort.
+  char last_char = view.back();
+  // If the address ends with a dot, we need to prune it (special case).
+  if (last_char == '.') {
+    view.remove_suffix(1);
+    if (view.empty()) {
+      return false;
+    }
+    last_char = view.back();
+  }
+  bool possible_ipv4 = (last_char >= '0' && last_char <= '9') ||
+                       (last_char >= 'a' && last_char <= 'f') ||
+                       last_char == 'x';
+  if (!possible_ipv4) {
+    return false;
+  }
+  // From the last character, find the last dot.
+  size_t last_dot = view.rfind('.');
+  if (last_dot != std::string_view::npos) {
+    // We have at least one dot.
+    view = view.substr(last_dot + 1);
+  }
+  /** Optimization opportunity: we have basically identified the last number of
+     the ipv4 if we return true here. We might as well parse it and have at
+     least one number parsed when we get to parse_ipv4. */
+  if (std::all_of(view.begin(), view.end(), ada::checkers::is_digit)) {
+    return true;
+  }
+  // It could be hex (0x), but not if there is a single character.
+  if (view.size() == 1) {
+    return false;
+  }
+  // It must start with 0x.
+  if (!std::equal(view.begin(), view.begin() + 2, "0x")) {
+    return false;
+  }
+  // We must allow "0x".
+  if (view.size() == 2) {
+    return true;
+  }
+  // We have 0x followed by some characters, we need to check that they are
+  // hexadecimals.
+  return std::all_of(view.begin() + 2, view.end(),
+                     ada::unicode::is_lowercase_hex);
+}
+
+// for use with path_signature, we include all characters that need percent
+// encoding.
+static constexpr std::array<uint8_t, 256> path_signature_table =
+    []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (size_t i = 0; i < 256; i++) {
+        if (i <= 0x20 || i == 0x22 || i == 0x23 || i == 0x3c || i == 0x3e ||
+            i == 0x3f || i == 0x60 || i == 0x7b || i == 0x7b || i == 0x7d ||
+            i > 0x7e) {
+          result[i] = 1;
+        } else if (i == 0x25) {
+          result[i] = 8;
+        } else if (i == 0x2e) {
+          result[i] = 4;
+        } else if (i == 0x5c) {
+          result[i] = 2;
+        } else {
+          result[i] = 0;
+        }
+      }
+      return result;
+    }();
+
+ada_really_inline constexpr uint8_t path_signature(
+    std::string_view input) noexcept {
+  // The path percent-encode set is the query percent-encode set and U+003F (?),
+  // U+0060 (`), U+007B ({), and U+007D (}). The query percent-encode set is the
+  // C0 control percent-encode set and U+0020 SPACE, U+0022 ("), U+0023 (#),
+  // U+003C (<), and U+003E (>). The C0 control percent-encode set are the C0
+  // controls and all code points greater than U+007E (~).
+  size_t i = 0;
+  uint8_t accumulator{};
+  for (; i + 7 < input.size(); i += 8) {
+    accumulator |= uint8_t(path_signature_table[uint8_t(input[i])] |
+                           path_signature_table[uint8_t(input[i + 1])] |
+                           path_signature_table[uint8_t(input[i + 2])] |
+                           path_signature_table[uint8_t(input[i + 3])] |
+                           path_signature_table[uint8_t(input[i + 4])] |
+                           path_signature_table[uint8_t(input[i + 5])] |
+                           path_signature_table[uint8_t(input[i + 6])] |
+                           path_signature_table[uint8_t(input[i + 7])]);
+  }
+  for (; i < input.size(); i++) {
+    accumulator |= uint8_t(path_signature_table[uint8_t(input[i])]);
+  }
+  return accumulator;
+}
+
+ada_really_inline constexpr bool verify_dns_length(
+    std::string_view input) noexcept {
+  if (input.back() == '.') {
+    if (input.size() > 254) return false;
+  } else if (input.size() > 253)
+    return false;
+
+  size_t start = 0;
+  while (start < input.size()) {
+    auto dot_location = input.find('.', start);
+    // If not found, it's likely the end of the domain
+    if (dot_location == std::string_view::npos) dot_location = input.size();
+
+    auto label_size = dot_location - start;
+    if (label_size > 63 || label_size == 0) return false;
+
+    start = dot_location + 1;
+  }
+
+  return true;
+}
+}  // namespace ada::checkers
diff --git a/src/helpers.cpp b/src/helpers.cpp
new file mode 100644 (file)
index 0000000..6c91e31
--- /dev/null
@@ -0,0 +1,797 @@
+#include "ada.h"
+#include "ada/checkers-inl.h"
+#include "ada/checkers.h"
+#include "ada/common_defs.h"  // make sure ADA_IS_BIG_ENDIAN gets defined.
+#include "ada/unicode.h"
+#include "ada/scheme.h"
+
+#include <algorithm>
+#include <charconv>
+#include <cstring>
+#include <sstream>
+
+namespace ada::helpers {
+
+template <typename out_iter>
+void encode_json(std::string_view view, out_iter out) {
+  // trivial implementation. could be faster.
+  const char* hexvalues =
+      "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
+  for (uint8_t c : view) {
+    if (c == '\\') {
+      *out++ = '\\';
+      *out++ = '\\';
+    } else if (c == '"') {
+      *out++ = '\\';
+      *out++ = '"';
+    } else if (c <= 0x1f) {
+      *out++ = '\\';
+      *out++ = 'u';
+      *out++ = '0';
+      *out++ = '0';
+      *out++ = hexvalues[2 * c];
+      *out++ = hexvalues[2 * c + 1];
+    } else {
+      *out++ = c;
+    }
+  }
+}
+
+ada_unused std::string get_state(ada::state s) {
+  switch (s) {
+    case ada::state::AUTHORITY:
+      return "Authority";
+    case ada::state::SCHEME_START:
+      return "Scheme Start";
+    case ada::state::SCHEME:
+      return "Scheme";
+    case ada::state::HOST:
+      return "Host";
+    case ada::state::NO_SCHEME:
+      return "No Scheme";
+    case ada::state::FRAGMENT:
+      return "Fragment";
+    case ada::state::RELATIVE_SCHEME:
+      return "Relative Scheme";
+    case ada::state::RELATIVE_SLASH:
+      return "Relative Slash";
+    case ada::state::FILE:
+      return "File";
+    case ada::state::FILE_HOST:
+      return "File Host";
+    case ada::state::FILE_SLASH:
+      return "File Slash";
+    case ada::state::PATH_OR_AUTHORITY:
+      return "Path or Authority";
+    case ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES:
+      return "Special Authority Ignore Slashes";
+    case ada::state::SPECIAL_AUTHORITY_SLASHES:
+      return "Special Authority Slashes";
+    case ada::state::SPECIAL_RELATIVE_OR_AUTHORITY:
+      return "Special Relative or Authority";
+    case ada::state::QUERY:
+      return "Query";
+    case ada::state::PATH:
+      return "Path";
+    case ada::state::PATH_START:
+      return "Path Start";
+    case ada::state::OPAQUE_PATH:
+      return "Opaque Path";
+    case ada::state::PORT:
+      return "Port";
+    default:
+      return "unknown state";
+  }
+}
+
+ada_really_inline std::optional<std::string_view> prune_hash(
+    std::string_view& input) noexcept {
+  // compiles down to 20--30 instructions including a class to memchr (C
+  // function). this function should be quite fast.
+  size_t location_of_first = input.find('#');
+  if (location_of_first == std::string_view::npos) {
+    return std::nullopt;
+  }
+  std::string_view hash = input;
+  hash.remove_prefix(location_of_first + 1);
+  input.remove_suffix(input.size() - location_of_first);
+  return hash;
+}
+
+ada_really_inline bool shorten_path(std::string& path,
+                                    ada::scheme::type type) noexcept {
+  size_t first_delimiter = path.find_first_of('/', 1);
+
+  // Let path be url's path.
+  // If url's scheme is "file", path's size is 1, and path[0] is a normalized
+  // Windows drive letter, then return.
+  if (type == ada::scheme::type::FILE &&
+      first_delimiter == std::string_view::npos && !path.empty()) {
+    if (checkers::is_normalized_windows_drive_letter(
+            helpers::substring(path, 1))) {
+      return false;
+    }
+  }
+
+  // Remove path's last item, if any.
+  size_t last_delimiter = path.rfind('/');
+  if (last_delimiter != std::string::npos) {
+    path.erase(last_delimiter);
+    return true;
+  }
+
+  return false;
+}
+
+ada_really_inline bool shorten_path(std::string_view& path,
+                                    ada::scheme::type type) noexcept {
+  size_t first_delimiter = path.find_first_of('/', 1);
+
+  // Let path be url's path.
+  // If url's scheme is "file", path's size is 1, and path[0] is a normalized
+  // Windows drive letter, then return.
+  if (type == ada::scheme::type::FILE &&
+      first_delimiter == std::string_view::npos && !path.empty()) {
+    if (checkers::is_normalized_windows_drive_letter(
+            helpers::substring(path, 1))) {
+      return false;
+    }
+  }
+
+  // Remove path's last item, if any.
+  if (!path.empty()) {
+    size_t slash_loc = path.rfind('/');
+    if (slash_loc != std::string_view::npos) {
+      path.remove_suffix(path.size() - slash_loc);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+ada_really_inline void remove_ascii_tab_or_newline(
+    std::string& input) noexcept {
+  // if this ever becomes a performance issue, we could use an approach similar
+  // to has_tabs_or_newline
+  input.erase(std::remove_if(input.begin(), input.end(),
+                             [](char c) {
+                               return ada::unicode::is_ascii_tab_or_newline(c);
+                             }),
+              input.end());
+}
+
+ada_really_inline std::string_view substring(std::string_view input,
+                                             size_t pos) noexcept {
+  ADA_ASSERT_TRUE(pos <= input.size());
+  // The following is safer but unneeded if we have the above line:
+  // return pos > input.size() ? std::string_view() : input.substr(pos);
+  return input.substr(pos);
+}
+
+ada_really_inline void resize(std::string_view& input, size_t pos) noexcept {
+  ADA_ASSERT_TRUE(pos <= input.size());
+  input.remove_suffix(input.size() - pos);
+}
+
+// computes the number of trailing zeroes
+// this is a private inline function only defined in this source file.
+ada_really_inline int trailing_zeroes(uint32_t input_num) noexcept {
+#ifdef ADA_REGULAR_VISUAL_STUDIO
+  unsigned long ret;
+  // Search the mask data from least significant bit (LSB)
+  // to the most significant bit (MSB) for a set bit (1).
+  _BitScanForward(&ret, input_num);
+  return (int)ret;
+#else   // ADA_REGULAR_VISUAL_STUDIO
+  return __builtin_ctzl(input_num);
+#endif  // ADA_REGULAR_VISUAL_STUDIO
+}
+
+// starting at index location, this finds the next location of a character
+// :, /, \\, ? or [. If none is found, view.size() is returned.
+// For use within get_host_delimiter_location.
+#if ADA_NEON
+// The ada_make_uint8x16_t macro is necessary because Visual Studio does not
+// support direct initialization of uint8x16_t. See
+// https://developercommunity.visualstudio.com/t/error-C2078:-too-many-initializers-whe/402911?q=backend+neon
+#ifndef ada_make_uint8x16_t
+#define ada_make_uint8x16_t(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, \
+                            x13, x14, x15, x16)                                \
+  ([=]() {                                                                     \
+    static uint8_t array[16] = {x1, x2,  x3,  x4,  x5,  x6,  x7,  x8,          \
+                                x9, x10, x11, x12, x13, x14, x15, x16};        \
+    return vld1q_u8(array);                                                    \
+  }())
+#endif
+
+ada_really_inline size_t find_next_host_delimiter_special(
+    std::string_view view, size_t location) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (view.size() - location < 16) {  // slow path
+    for (size_t i = location; i < view.size(); i++) {
+      if (view[i] == ':' || view[i] == '/' || view[i] == '\\' ||
+          view[i] == '?' || view[i] == '[') {
+        return i;
+      }
+    }
+    return size_t(view.size());
+  }
+  auto to_bitmask = [](uint8x16_t input) -> uint16_t {
+    uint8x16_t bit_mask =
+        ada_make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01,
+                            0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80);
+    uint8x16_t minput = vandq_u8(input, bit_mask);
+    uint8x16_t tmp = vpaddq_u8(minput, minput);
+    tmp = vpaddq_u8(tmp, tmp);
+    tmp = vpaddq_u8(tmp, tmp);
+    return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0);
+  };
+
+  // fast path for long strings (expected to be common)
+  size_t i = location;
+  uint8x16_t low_mask =
+      ada_make_uint8x16_t(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                          0x00, 0x01, 0x04, 0x04, 0x00, 0x00, 0x03);
+  uint8x16_t high_mask =
+      ada_make_uint8x16_t(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
+                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+  uint8x16_t fmask = vmovq_n_u8(0xf);
+  uint8x16_t zero{0};
+  for (; i + 15 < view.size(); i += 16) {
+    uint8x16_t word = vld1q_u8((const uint8_t*)view.data() + i);
+    uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask));
+    uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4));
+    uint8x16_t classify = vandq_u8(lowpart, highpart);
+    if (vmaxvq_u8(classify) != 0) {
+      uint8x16_t is_zero = vceqq_u8(classify, zero);
+      uint16_t is_non_zero = ~to_bitmask(is_zero);
+      return i + trailing_zeroes(is_non_zero);
+    }
+  }
+
+  if (i < view.size()) {
+    uint8x16_t word =
+        vld1q_u8((const uint8_t*)view.data() + view.length() - 16);
+    uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask));
+    uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4));
+    uint8x16_t classify = vandq_u8(lowpart, highpart);
+    if (vmaxvq_u8(classify) != 0) {
+      uint8x16_t is_zero = vceqq_u8(classify, zero);
+      uint16_t is_non_zero = ~to_bitmask(is_zero);
+      return view.length() - 16 + trailing_zeroes(is_non_zero);
+    }
+  }
+  return size_t(view.size());
+}
+#elif ADA_SSE2
+ada_really_inline size_t find_next_host_delimiter_special(
+    std::string_view view, size_t location) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (view.size() - location < 16) {  // slow path
+    for (size_t i = location; i < view.size(); i++) {
+      if (view[i] == ':' || view[i] == '/' || view[i] == '\\' ||
+          view[i] == '?' || view[i] == '[') {
+        return i;
+      }
+    }
+    return size_t(view.size());
+  }
+  // fast path for long strings (expected to be common)
+  size_t i = location;
+  const __m128i mask1 = _mm_set1_epi8(':');
+  const __m128i mask2 = _mm_set1_epi8('/');
+  const __m128i mask3 = _mm_set1_epi8('\\');
+  const __m128i mask4 = _mm_set1_epi8('?');
+  const __m128i mask5 = _mm_set1_epi8('[');
+
+  for (; i + 15 < view.size(); i += 16) {
+    __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i));
+    __m128i m1 = _mm_cmpeq_epi8(word, mask1);
+    __m128i m2 = _mm_cmpeq_epi8(word, mask2);
+    __m128i m3 = _mm_cmpeq_epi8(word, mask3);
+    __m128i m4 = _mm_cmpeq_epi8(word, mask4);
+    __m128i m5 = _mm_cmpeq_epi8(word, mask5);
+    __m128i m = _mm_or_si128(
+        _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m3, m4)), m5);
+    int mask = _mm_movemask_epi8(m);
+    if (mask != 0) {
+      return i + trailing_zeroes(mask);
+    }
+  }
+  if (i < view.size()) {
+    __m128i word =
+        _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16));
+    __m128i m1 = _mm_cmpeq_epi8(word, mask1);
+    __m128i m2 = _mm_cmpeq_epi8(word, mask2);
+    __m128i m3 = _mm_cmpeq_epi8(word, mask3);
+    __m128i m4 = _mm_cmpeq_epi8(word, mask4);
+    __m128i m5 = _mm_cmpeq_epi8(word, mask5);
+    __m128i m = _mm_or_si128(
+        _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m3, m4)), m5);
+    int mask = _mm_movemask_epi8(m);
+    if (mask != 0) {
+      return view.length() - 16 + trailing_zeroes(mask);
+    }
+  }
+  return size_t(view.length());
+}
+#else
+// : / [ \\ ?
+static constexpr std::array<uint8_t, 256> special_host_delimiters =
+    []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (int i : {':', '/', '[', '\\', '?'}) {
+        result[i] = 1;
+      }
+      return result;
+    }();
+// credit: @the-moisrex recommended a table-based approach
+ada_really_inline size_t find_next_host_delimiter_special(
+    std::string_view view, size_t location) noexcept {
+  auto const str = view.substr(location);
+  for (auto pos = str.begin(); pos != str.end(); ++pos) {
+    if (special_host_delimiters[(uint8_t)*pos]) {
+      return pos - str.begin() + location;
+    }
+  }
+  return size_t(view.size());
+}
+#endif
+
+// starting at index location, this finds the next location of a character
+// :, /, ? or [. If none is found, view.size() is returned.
+// For use within get_host_delimiter_location.
+#if ADA_NEON
+ada_really_inline size_t find_next_host_delimiter(std::string_view view,
+                                                  size_t location) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (view.size() - location < 16) {  // slow path
+    for (size_t i = location; i < view.size(); i++) {
+      if (view[i] == ':' || view[i] == '/' || view[i] == '?' ||
+          view[i] == '[') {
+        return i;
+      }
+    }
+    return size_t(view.size());
+  }
+  auto to_bitmask = [](uint8x16_t input) -> uint16_t {
+    uint8x16_t bit_mask =
+        ada_make_uint8x16_t(0x01, 0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x01,
+                            0x02, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80);
+    uint8x16_t minput = vandq_u8(input, bit_mask);
+    uint8x16_t tmp = vpaddq_u8(minput, minput);
+    tmp = vpaddq_u8(tmp, tmp);
+    tmp = vpaddq_u8(tmp, tmp);
+    return vgetq_lane_u16(vreinterpretq_u16_u8(tmp), 0);
+  };
+
+  // fast path for long strings (expected to be common)
+  size_t i = location;
+  uint8x16_t low_mask =
+      ada_make_uint8x16_t(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                          0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x03);
+  uint8x16_t high_mask =
+      ada_make_uint8x16_t(0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
+                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+  uint8x16_t fmask = vmovq_n_u8(0xf);
+  uint8x16_t zero{0};
+  for (; i + 15 < view.size(); i += 16) {
+    uint8x16_t word = vld1q_u8((const uint8_t*)view.data() + i);
+    uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask));
+    uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4));
+    uint8x16_t classify = vandq_u8(lowpart, highpart);
+    if (vmaxvq_u8(classify) != 0) {
+      uint8x16_t is_zero = vceqq_u8(classify, zero);
+      uint16_t is_non_zero = ~to_bitmask(is_zero);
+      return i + trailing_zeroes(is_non_zero);
+    }
+  }
+
+  if (i < view.size()) {
+    uint8x16_t word =
+        vld1q_u8((const uint8_t*)view.data() + view.length() - 16);
+    uint8x16_t lowpart = vqtbl1q_u8(low_mask, vandq_u8(word, fmask));
+    uint8x16_t highpart = vqtbl1q_u8(high_mask, vshrq_n_u8(word, 4));
+    uint8x16_t classify = vandq_u8(lowpart, highpart);
+    if (vmaxvq_u8(classify) != 0) {
+      uint8x16_t is_zero = vceqq_u8(classify, zero);
+      uint16_t is_non_zero = ~to_bitmask(is_zero);
+      return view.length() - 16 + trailing_zeroes(is_non_zero);
+    }
+  }
+  return size_t(view.size());
+}
+#elif ADA_SSE2
+ada_really_inline size_t find_next_host_delimiter(std::string_view view,
+                                                  size_t location) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (view.size() - location < 16) {  // slow path
+    for (size_t i = location; i < view.size(); i++) {
+      if (view[i] == ':' || view[i] == '/' || view[i] == '?' ||
+          view[i] == '[') {
+        return i;
+      }
+    }
+    return size_t(view.size());
+  }
+  // fast path for long strings (expected to be common)
+  size_t i = location;
+  const __m128i mask1 = _mm_set1_epi8(':');
+  const __m128i mask2 = _mm_set1_epi8('/');
+  const __m128i mask4 = _mm_set1_epi8('?');
+  const __m128i mask5 = _mm_set1_epi8('[');
+
+  for (; i + 15 < view.size(); i += 16) {
+    __m128i word = _mm_loadu_si128((const __m128i*)(view.data() + i));
+    __m128i m1 = _mm_cmpeq_epi8(word, mask1);
+    __m128i m2 = _mm_cmpeq_epi8(word, mask2);
+    __m128i m4 = _mm_cmpeq_epi8(word, mask4);
+    __m128i m5 = _mm_cmpeq_epi8(word, mask5);
+    __m128i m = _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m4, m5));
+    int mask = _mm_movemask_epi8(m);
+    if (mask != 0) {
+      return i + trailing_zeroes(mask);
+    }
+  }
+  if (i < view.size()) {
+    __m128i word =
+        _mm_loadu_si128((const __m128i*)(view.data() + view.length() - 16));
+    __m128i m1 = _mm_cmpeq_epi8(word, mask1);
+    __m128i m2 = _mm_cmpeq_epi8(word, mask2);
+    __m128i m4 = _mm_cmpeq_epi8(word, mask4);
+    __m128i m5 = _mm_cmpeq_epi8(word, mask5);
+    __m128i m = _mm_or_si128(_mm_or_si128(m1, m2), _mm_or_si128(m4, m5));
+    int mask = _mm_movemask_epi8(m);
+    if (mask != 0) {
+      return view.length() - 16 + trailing_zeroes(mask);
+    }
+  }
+  return size_t(view.length());
+}
+#else
+// : / [ ?
+static constexpr std::array<uint8_t, 256> host_delimiters = []() constexpr {
+  std::array<uint8_t, 256> result{};
+  for (int i : {':', '/', '?', '['}) {
+    result[i] = 1;
+  }
+  return result;
+}();
+// credit: @the-moisrex recommended a table-based approach
+ada_really_inline size_t find_next_host_delimiter(std::string_view view,
+                                                  size_t location) noexcept {
+  auto const str = view.substr(location);
+  for (auto pos = str.begin(); pos != str.end(); ++pos) {
+    if (host_delimiters[(uint8_t)*pos]) {
+      return pos - str.begin() + location;
+    }
+  }
+  return size_t(view.size());
+}
+#endif
+
+ada_really_inline std::pair<size_t, bool> get_host_delimiter_location(
+    const bool is_special, std::string_view& view) noexcept {
+  /**
+   * The spec at https://url.spec.whatwg.org/#hostname-state expects us to
+   * compute a variable called insideBrackets but this variable is only used
+   * once, to check whether a ':' character was found outside brackets. Exact
+   * text: "Otherwise, if c is U+003A (:) and insideBrackets is false, then:".
+   * It is conceptually simpler and arguably more efficient to just return a
+   * Boolean indicating whether ':' was found outside brackets.
+   */
+  const size_t view_size = view.size();
+  size_t location = 0;
+  bool found_colon = false;
+  /**
+   * Performance analysis:
+   *
+   * We are basically seeking the end of the hostname which can be indicated
+   * by the end of the view, or by one of the characters ':', '/', '?', '\\'
+   * (where '\\' is only applicable for special URLs). However, these must
+   * appear outside a bracket range. E.g., if you have [something?]fd: then the
+   * '?' does not count.
+   *
+   * So we can skip ahead to the next delimiter, as long as we include '[' in
+   * the set of delimiters, and that we handle it first.
+   *
+   * So the trick is to have a fast function that locates the next delimiter.
+   * Unless we find '[', then it only needs to be called once! Ideally, such a
+   * function would be provided by the C++ standard library, but it seems that
+   * find_first_of is not very fast, so we are forced to roll our own.
+   *
+   * We do not break into two loops for speed, but for clarity.
+   */
+  if (is_special) {
+    // We move to the next delimiter.
+    location = find_next_host_delimiter_special(view, location);
+    // Unless we find '[' then we are going only going to have to call
+    // find_next_host_delimiter_special once.
+    for (; location < view_size;
+         location = find_next_host_delimiter_special(view, location)) {
+      if (view[location] == '[') {
+        location = view.find(']', location);
+        if (location == std::string_view::npos) {
+          // performance: view.find might get translated to a memchr, which
+          // has no notion of std::string_view::npos, so the code does not
+          // reflect the assembly.
+          location = view_size;
+          break;
+        }
+      } else {
+        found_colon = view[location] == ':';
+        break;
+      }
+    }
+  } else {
+    // We move to the next delimiter.
+    location = find_next_host_delimiter(view, location);
+    // Unless we find '[' then we are going only going to have to call
+    // find_next_host_delimiter_special once.
+    for (; location < view_size;
+         location = find_next_host_delimiter(view, location)) {
+      if (view[location] == '[') {
+        location = view.find(']', location);
+        if (location == std::string_view::npos) {
+          // performance: view.find might get translated to a memchr, which
+          // has no notion of std::string_view::npos, so the code does not
+          // reflect the assembly.
+          location = view_size;
+          break;
+        }
+      } else {
+        found_colon = view[location] == ':';
+        break;
+      }
+    }
+  }
+  // performance: remove_suffix may translate into a single instruction.
+  view.remove_suffix(view_size - location);
+  return {location, found_colon};
+}
+
+ada_really_inline void trim_c0_whitespace(std::string_view& input) noexcept {
+  while (!input.empty() &&
+         ada::unicode::is_c0_control_or_space(input.front())) {
+    input.remove_prefix(1);
+  }
+  while (!input.empty() && ada::unicode::is_c0_control_or_space(input.back())) {
+    input.remove_suffix(1);
+  }
+}
+
+ada_really_inline void parse_prepared_path(std::string_view input,
+                                           ada::scheme::type type,
+                                           std::string& path) {
+  ada_log("parse_prepared_path ", input);
+  uint8_t accumulator = checkers::path_signature(input);
+  // Let us first detect a trivial case.
+  // If it is special, we check that we have no dot, no %,  no \ and no
+  // character needing percent encoding. Otherwise, we check that we have no %,
+  // no dot, and no character needing percent encoding.
+  constexpr uint8_t need_encoding = 1;
+  constexpr uint8_t backslash_char = 2;
+  constexpr uint8_t dot_char = 4;
+  constexpr uint8_t percent_char = 8;
+  bool special = type != ada::scheme::NOT_SPECIAL;
+  bool may_need_slow_file_handling = (type == ada::scheme::type::FILE &&
+                                      checkers::is_windows_drive_letter(input));
+  bool trivial_path =
+      (special ? (accumulator == 0)
+               : ((accumulator & (need_encoding | dot_char | percent_char)) ==
+                  0)) &&
+      (!may_need_slow_file_handling);
+  if (accumulator == dot_char && !may_need_slow_file_handling) {
+    // '4' means that we have at least one dot, but nothing that requires
+    // percent encoding or decoding. The only part that is not trivial is
+    // that we may have single dots and double dots path segments.
+    // If we have such segments, then we either have a path that begins
+    // with '.' (easy to check), or we have the sequence './'.
+    // Note: input cannot be empty, it must at least contain one character ('.')
+    // Note: we know that '\' is not present.
+    if (input[0] != '.') {
+      size_t slashdot = input.find("/.");
+      if (slashdot == std::string_view::npos) {  // common case
+        trivial_path = true;
+      } else {  // uncommon
+        // only three cases matter: /./, /.. or a final /
+        trivial_path =
+            !(slashdot + 2 == input.size() || input[slashdot + 2] == '.' ||
+              input[slashdot + 2] == '/');
+      }
+    }
+  }
+  if (trivial_path) {
+    ada_log("parse_path trivial");
+    path += '/';
+    path += input;
+    return;
+  }
+  // We are going to need to look a bit at the path, but let us see if we can
+  // ignore percent encoding *and* backslashes *and* percent characters.
+  // Except for the trivial case, this is likely to capture 99% of paths out
+  // there.
+  bool fast_path =
+      (special &&
+       (accumulator & (need_encoding | backslash_char | percent_char)) == 0) &&
+      (type != ada::scheme::type::FILE);
+  if (fast_path) {
+    ada_log("parse_prepared_path fast");
+    // Here we don't need to worry about \ or percent encoding.
+    // We also do not have a file protocol. We might have dots, however,
+    // but dots must as appear as '.', and they cannot be encoded because
+    // the symbol '%' is not present.
+    size_t previous_location = 0;  // We start at 0.
+    do {
+      size_t new_location = input.find('/', previous_location);
+      // std::string_view path_view = input;
+      //  We process the last segment separately:
+      if (new_location == std::string_view::npos) {
+        std::string_view path_view = input.substr(previous_location);
+        if (path_view == "..") {  // The path ends with ..
+          // e.g., if you receive ".." with an empty path, you go to "/".
+          if (path.empty()) {
+            path = '/';
+            return;
+          }
+          // Fast case where we have nothing to do:
+          if (path.back() == '/') {
+            return;
+          }
+          // If you have the path "/joe/myfriend",
+          // then you delete 'myfriend'.
+          path.resize(path.rfind('/') + 1);
+          return;
+        }
+        path += '/';
+        if (path_view != ".") {
+          path.append(path_view);
+        }
+        return;
+      } else {
+        // This is a non-final segment.
+        std::string_view path_view =
+            input.substr(previous_location, new_location - previous_location);
+        previous_location = new_location + 1;
+        if (path_view == "..") {
+          size_t last_delimiter = path.rfind('/');
+          if (last_delimiter != std::string::npos) {
+            path.erase(last_delimiter);
+          }
+        } else if (path_view != ".") {
+          path += '/';
+          path.append(path_view);
+        }
+      }
+    } while (true);
+  } else {
+    ada_log("parse_path slow");
+    // we have reached the general case
+    bool needs_percent_encoding = (accumulator & 1);
+    std::string path_buffer_tmp;
+    do {
+      size_t location = (special && (accumulator & 2))
+                            ? input.find_first_of("/\\")
+                            : input.find('/');
+      std::string_view path_view = input;
+      if (location != std::string_view::npos) {
+        path_view.remove_suffix(path_view.size() - location);
+        input.remove_prefix(location + 1);
+      }
+      // path_buffer is either path_view or it might point at a percent encoded
+      // temporary file.
+      std::string_view path_buffer =
+          (needs_percent_encoding &&
+           ada::unicode::percent_encode<false>(
+               path_view, character_sets::PATH_PERCENT_ENCODE, path_buffer_tmp))
+              ? path_buffer_tmp
+              : path_view;
+      if (unicode::is_double_dot_path_segment(path_buffer)) {
+        if ((helpers::shorten_path(path, type) || special) &&
+            location == std::string_view::npos) {
+          path += '/';
+        }
+      } else if (unicode::is_single_dot_path_segment(path_buffer) &&
+                 (location == std::string_view::npos)) {
+        path += '/';
+      }
+      // Otherwise, if path_buffer is not a single-dot path segment, then:
+      else if (!unicode::is_single_dot_path_segment(path_buffer)) {
+        // If url's scheme is "file", url's path is empty, and path_buffer is a
+        // Windows drive letter, then replace the second code point in
+        // path_buffer with U+003A (:).
+        if (type == ada::scheme::type::FILE && path.empty() &&
+            checkers::is_windows_drive_letter(path_buffer)) {
+          path += '/';
+          path += path_buffer[0];
+          path += ':';
+          path_buffer.remove_prefix(2);
+          path.append(path_buffer);
+        } else {
+          // Append path_buffer to url's path.
+          path += '/';
+          path.append(path_buffer);
+        }
+      }
+      if (location == std::string_view::npos) {
+        return;
+      }
+    } while (true);
+  }
+}
+
+bool overlaps(std::string_view input1, const std::string& input2) noexcept {
+  ada_log("helpers::overlaps check if string_view '", input1, "' [",
+          input1.size(), " bytes] is part of string '", input2, "' [",
+          input2.size(), " bytes]");
+  return !input1.empty() && !input2.empty() && input1.data() >= input2.data() &&
+         input1.data() < input2.data() + input2.size();
+}
+
+template <class url_type>
+ada_really_inline void strip_trailing_spaces_from_opaque_path(
+    url_type& url) noexcept {
+  ada_log("helpers::strip_trailing_spaces_from_opaque_path");
+  if (!url.has_opaque_path) return;
+  if (url.has_hash()) return;
+  if (url.has_search()) return;
+
+  auto path = std::string(url.get_pathname());
+  while (!path.empty() && path.back() == ' ') {
+    path.resize(path.size() - 1);
+  }
+  url.update_base_pathname(path);
+}
+
+// @ / \\ ?
+static constexpr std::array<uint8_t, 256> authority_delimiter_special =
+    []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (int i : {'@', '/', '\\', '?'}) {
+        result[i] = 1;
+      }
+      return result;
+    }();
+// credit: @the-moisrex recommended a table-based approach
+ada_really_inline size_t
+find_authority_delimiter_special(std::string_view view) noexcept {
+  // performance note: we might be able to gain further performance
+  // with SIMD instrinsics.
+  for (auto pos = view.begin(); pos != view.end(); ++pos) {
+    if (authority_delimiter_special[(uint8_t)*pos]) {
+      return pos - view.begin();
+    }
+  }
+  return size_t(view.size());
+}
+
+// @ / ?
+static constexpr std::array<uint8_t, 256> authority_delimiter = []() constexpr {
+  std::array<uint8_t, 256> result{};
+  for (int i : {'@', '/', '?'}) {
+    result[i] = 1;
+  }
+  return result;
+}();
+// credit: @the-moisrex recommended a table-based approach
+ada_really_inline size_t
+find_authority_delimiter(std::string_view view) noexcept {
+  // performance note: we might be able to gain further performance
+  // with SIMD instrinsics.
+  for (auto pos = view.begin(); pos != view.end(); ++pos) {
+    if (authority_delimiter[(uint8_t)*pos]) {
+      return pos - view.begin();
+    }
+  }
+  return size_t(view.size());
+}
+
+}  // namespace ada::helpers
+
+namespace ada {
+ada_warn_unused std::string to_string(ada::state state) {
+  return ada::helpers::get_state(state);
+}
+#undef ada_make_uint8x16_t
+}  // namespace ada
diff --git a/src/implementation.cpp b/src/implementation.cpp
new file mode 100644 (file)
index 0000000..e5ad039
--- /dev/null
@@ -0,0 +1,75 @@
+#include <string_view>
+
+#include "ada.h"
+#include "ada/common_defs.h"
+#include "ada/parser.h"
+#include "ada/url.h"
+#include "ada/url_aggregator.h"
+
+namespace ada {
+
+template <class result_type>
+ada_warn_unused tl::expected<result_type, ada::errors> parse(
+    std::string_view input, const result_type* base_url) {
+  result_type u = ada::parser::parse_url<result_type>(input, base_url);
+  if (!u.is_valid) {
+    return tl::unexpected(errors::generic_error);
+  }
+  return u;
+}
+
+template ada::result<url> parse<url>(std::string_view input,
+                                     const url* base_url = nullptr);
+template ada::result<url_aggregator> parse<url_aggregator>(
+    std::string_view input, const url_aggregator* base_url = nullptr);
+
+std::string href_from_file(std::string_view input) {
+  // This is going to be much faster than constructing a URL.
+  std::string tmp_buffer;
+  std::string_view internal_input;
+  if (unicode::has_tabs_or_newline(input)) {
+    tmp_buffer = input;
+    helpers::remove_ascii_tab_or_newline(tmp_buffer);
+    internal_input = tmp_buffer;
+  } else {
+    internal_input = input;
+  }
+  std::string path;
+  if (internal_input.empty()) {
+    path = "/";
+  } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) {
+    helpers::parse_prepared_path(internal_input.substr(1),
+                                 ada::scheme::type::FILE, path);
+  } else {
+    helpers::parse_prepared_path(internal_input, ada::scheme::type::FILE, path);
+  }
+  return "file://" + path;
+}
+
+bool can_parse(std::string_view input, const std::string_view* base_input) {
+  ada::result<ada::url_aggregator> base;
+  ada::url_aggregator* base_pointer = nullptr;
+  if (base_input != nullptr) {
+    base = ada::parse<url_aggregator>(*base_input);
+    if (!base) {
+      return false;
+    }
+    base_pointer = &base.value();
+  }
+  return ada::parse<url_aggregator>(input, base_pointer).has_value();
+}
+
+ada_warn_unused std::string to_string(ada::encoding_type type) {
+  switch (type) {
+    case ada::encoding_type::UTF8:
+      return "UTF-8";
+    case ada::encoding_type::UTF_16LE:
+      return "UTF-16LE";
+    case ada::encoding_type::UTF_16BE:
+      return "UTF-16BE";
+    default:
+      unreachable();
+  }
+}
+
+}  // namespace ada
diff --git a/src/parser.cpp b/src/parser.cpp
new file mode 100644 (file)
index 0000000..680ce35
--- /dev/null
@@ -0,0 +1,915 @@
+#include "ada.h"
+#include "ada/common_defs.h"
+#include "ada/character_sets-inl.h"
+#include "ada/unicode.h"
+#include "ada/url-inl.h"
+#include "ada/log.h"
+#include "ada/parser.h"
+
+#include <numeric>
+#include <limits>
+
+namespace ada::parser {
+
+template <class result_type>
+result_type parse_url(std::string_view user_input,
+                      const result_type* base_url) {
+  // We can specialize the implementation per type.
+  // Important: result_type_is_ada_url is evaluated at *compile time*. This
+  // means that doing if constexpr(result_type_is_ada_url) { something } else {
+  // something else } is free (at runtime). This means that ada::url_aggregator
+  // and ada::url **do not have to support the exact same API**.
+  constexpr bool result_type_is_ada_url =
+      std::is_same<ada::url, result_type>::value;
+  constexpr bool result_type_is_ada_url_aggregator =
+      std::is_same<ada::url_aggregator, result_type>::value;
+  static_assert(result_type_is_ada_url ||
+                result_type_is_ada_url_aggregator);  // We don't support
+                                                     // anything else for now.
+
+  ada_log("ada::parser::parse_url('", user_input, "' [", user_input.size(),
+          " bytes],", (base_url != nullptr ? base_url->to_string() : "null"),
+          ")");
+
+  ada::state state = ada::state::SCHEME_START;
+  result_type url{};
+
+  // We refuse to parse URL strings that exceed 4GB. Such strings are almost
+  // surely the result of a bug or are otherwise a security concern.
+  if (user_input.size() > std::numeric_limits<uint32_t>::max()) {
+    url.is_valid = false;
+  }
+  // Going forward, user_input.size() is in [0,
+  // std::numeric_limits<uint32_t>::max). If we are provided with an invalid
+  // base, or the optional_url was invalid, we must return.
+  if (base_url != nullptr) {
+    url.is_valid &= base_url->is_valid;
+  }
+  if (!url.is_valid) {
+    return url;
+  }
+  if constexpr (result_type_is_ada_url_aggregator) {
+    // Most of the time, we just need user_input.size().
+    // In some instances, we may need a bit more.
+    ///////////////////////////
+    // This is *very* important. This line should *not* be removed
+    // hastily. There are principled reasons why reserve is important
+    // for performance. If you have a benchmark with small inputs,
+    // it may not matter, but in other instances, it could.
+    ////
+    // This rounds up to the next power of two.
+    // We know that user_input.size() is in [0,
+    // std::numeric_limits<uint32_t>::max).
+    uint32_t reserve_capacity =
+        (0xFFFFFFFF >>
+         helpers::leading_zeroes(uint32_t(1 | user_input.size()))) +
+        1;
+    url.reserve(reserve_capacity);
+    //
+    //
+    //
+  }
+  std::string tmp_buffer;
+  std::string_view internal_input;
+  if (unicode::has_tabs_or_newline(user_input)) {
+    tmp_buffer = user_input;
+    // Optimization opportunity: Instead of copying and then pruning, we could
+    // just directly build the string from user_input.
+    helpers::remove_ascii_tab_or_newline(tmp_buffer);
+    internal_input = tmp_buffer;
+  } else {
+    internal_input = user_input;
+  }
+
+  // Leading and trailing control characters are uncommon and easy to deal with
+  // (no performance concern).
+  std::string_view url_data = internal_input;
+  helpers::trim_c0_whitespace(url_data);
+
+  // Optimization opportunity. Most websites do not have fragment.
+  std::optional<std::string_view> fragment = helpers::prune_hash(url_data);
+  // We add it last so that an implementation like ada::url_aggregator
+  // can append it last to its internal buffer, thus improving performance.
+
+  // Here url_data no longer has its fragment.
+  // We are going to access the data from url_data (it is immutable).
+  // At any given time, we are pointing at byte 'input_position' in url_data.
+  // The input_position variable should range from 0 to input_size.
+  // It is illegal to access url_data at input_size.
+  size_t input_position = 0;
+  const size_t input_size = url_data.size();
+  // Keep running the following state machine by switching on state.
+  // If after a run pointer points to the EOF code point, go to the next step.
+  // Otherwise, increase pointer by 1 and continue with the state machine.
+  // We never decrement input_position.
+  while (input_position <= input_size) {
+    ada_log("In parsing at ", input_position, " out of ", input_size,
+            " in state ", ada::to_string(state));
+    switch (state) {
+      case ada::state::SCHEME_START: {
+        ada_log("SCHEME_START ", helpers::substring(url_data, input_position));
+        // If c is an ASCII alpha, append c, lowercased, to buffer, and set
+        // state to scheme state.
+        if ((input_position != input_size) &&
+            checkers::is_alpha(url_data[input_position])) {
+          state = ada::state::SCHEME;
+          input_position++;
+        } else {
+          // Otherwise, if state override is not given, set state to no scheme
+          // state and decrease pointer by 1.
+          state = ada::state::NO_SCHEME;
+        }
+        break;
+      }
+      case ada::state::SCHEME: {
+        ada_log("SCHEME ", helpers::substring(url_data, input_position));
+        // If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.),
+        // append c, lowercased, to buffer.
+        while ((input_position != input_size) &&
+               (ada::unicode::is_alnum_plus(url_data[input_position]))) {
+          input_position++;
+        }
+        // Otherwise, if c is U+003A (:), then:
+        if ((input_position != input_size) &&
+            (url_data[input_position] == ':')) {
+          ada_log("SCHEME the scheme should be ",
+                  url_data.substr(0, input_position));
+          if constexpr (result_type_is_ada_url) {
+            if (!url.parse_scheme(url_data.substr(0, input_position))) {
+              return url;
+            }
+          } else {
+            // we pass the colon along instead of painfully adding it back.
+            if (!url.parse_scheme_with_colon(
+                    url_data.substr(0, input_position + 1))) {
+              return url;
+            }
+          }
+          ada_log("SCHEME the scheme is ", url.get_protocol());
+
+          // If url's scheme is "file", then:
+          if (url.type == ada::scheme::type::FILE) {
+            // Set state to file state.
+            state = ada::state::FILE;
+          }
+          // Otherwise, if url is special, base is non-null, and base's scheme
+          // is url's scheme: Note: Doing base_url->scheme is unsafe if base_url
+          // != nullptr is false.
+          else if (url.is_special() && base_url != nullptr &&
+                   base_url->type == url.type) {
+            // Set state to special relative or authority state.
+            state = ada::state::SPECIAL_RELATIVE_OR_AUTHORITY;
+          }
+          // Otherwise, if url is special, set state to special authority
+          // slashes state.
+          else if (url.is_special()) {
+            state = ada::state::SPECIAL_AUTHORITY_SLASHES;
+          }
+          // Otherwise, if remaining starts with an U+002F (/), set state to
+          // path or authority state and increase pointer by 1.
+          else if (input_position + 1 < input_size &&
+                   url_data[input_position + 1] == '/') {
+            state = ada::state::PATH_OR_AUTHORITY;
+            input_position++;
+          }
+          // Otherwise, set url's path to the empty string and set state to
+          // opaque path state.
+          else {
+            state = ada::state::OPAQUE_PATH;
+          }
+        }
+        // Otherwise, if state override is not given, set buffer to the empty
+        // string, state to no scheme state, and start over (from the first code
+        // point in input).
+        else {
+          state = ada::state::NO_SCHEME;
+          input_position = 0;
+          break;
+        }
+        input_position++;
+        break;
+      }
+      case ada::state::NO_SCHEME: {
+        ada_log("NO_SCHEME ", helpers::substring(url_data, input_position));
+        // If base is null, or base has an opaque path and c is not U+0023 (#),
+        // validation error, return failure.
+        if (base_url == nullptr ||
+            (base_url->has_opaque_path && !fragment.has_value())) {
+          ada_log("NO_SCHEME validation error");
+          url.is_valid = false;
+          return url;
+        }
+        // Otherwise, if base has an opaque path and c is U+0023 (#),
+        // set url's scheme to base's scheme, url's path to base's path, url's
+        // query to base's query, and set state to fragment state.
+        else if (base_url->has_opaque_path && fragment.has_value() &&
+                 input_position == input_size) {
+          ada_log("NO_SCHEME opaque base with fragment");
+          url.copy_scheme(*base_url);
+          url.has_opaque_path = base_url->has_opaque_path;
+
+          if constexpr (result_type_is_ada_url) {
+            url.path = base_url->path;
+            url.query = base_url->query;
+          } else {
+            url.update_base_pathname(base_url->get_pathname());
+            url.update_base_search(base_url->get_search());
+          }
+          url.update_unencoded_base_hash(*fragment);
+          return url;
+        }
+        // Otherwise, if base's scheme is not "file", set state to relative
+        // state and decrease pointer by 1.
+        else if (base_url->type != ada::scheme::type::FILE) {
+          ada_log("NO_SCHEME non-file relative path");
+          state = ada::state::RELATIVE_SCHEME;
+        }
+        // Otherwise, set state to file state and decrease pointer by 1.
+        else {
+          ada_log("NO_SCHEME file base type");
+          state = ada::state::FILE;
+        }
+        break;
+      }
+      case ada::state::AUTHORITY: {
+        ada_log("AUTHORITY ", helpers::substring(url_data, input_position));
+        // most URLs have no @. Having no @ tells us that we don't have to worry
+        // about AUTHORITY. Of course, we could have @ and still not have to
+        // worry about AUTHORITY.
+        // TODO: Instead of just collecting a bool, collect the location of the
+        // '@' and do something useful with it.
+        // TODO: We could do various processing early on, using a single pass
+        // over the string to collect information about it, e.g., telling us
+        // whether there is a @ and if so, where (or how many).
+        const bool contains_ampersand =
+            (url_data.find('@', input_position) != std::string_view::npos);
+
+        if (!contains_ampersand) {
+          state = ada::state::HOST;
+          break;
+        }
+        bool at_sign_seen{false};
+        bool password_token_seen{false};
+        /**
+         * We expect something of the sort...
+         * https://user:pass@example.com:1234/foo/bar?baz#quux
+         * --------^
+         */
+        do {
+          std::string_view view = helpers::substring(url_data, input_position);
+          // The delimiters are @, /, ? \\.
+          size_t location =
+              url.is_special() ? helpers::find_authority_delimiter_special(view)
+                               : helpers::find_authority_delimiter(view);
+          std::string_view authority_view(view.data(), location);
+          size_t end_of_authority = input_position + authority_view.size();
+          // If c is U+0040 (@), then:
+          if ((end_of_authority != input_size) &&
+              (url_data[end_of_authority] == '@')) {
+            // If atSignSeen is true, then prepend "%40" to buffer.
+            if (at_sign_seen) {
+              if (password_token_seen) {
+                if constexpr (result_type_is_ada_url) {
+                  url.password += "%40";
+                } else {
+                  url.append_base_password("%40");
+                }
+              } else {
+                if constexpr (result_type_is_ada_url) {
+                  url.username += "%40";
+                } else {
+                  url.append_base_username("%40");
+                }
+              }
+            }
+
+            at_sign_seen = true;
+
+            if (!password_token_seen) {
+              size_t password_token_location = authority_view.find(':');
+              password_token_seen =
+                  password_token_location != std::string_view::npos;
+
+              if (!password_token_seen) {
+                if constexpr (result_type_is_ada_url) {
+                  url.username += unicode::percent_encode(
+                      authority_view, character_sets::USERINFO_PERCENT_ENCODE);
+                } else {
+                  url.append_base_username(unicode::percent_encode(
+                      authority_view, character_sets::USERINFO_PERCENT_ENCODE));
+                }
+              } else {
+                if constexpr (result_type_is_ada_url) {
+                  url.username += unicode::percent_encode(
+                      authority_view.substr(0, password_token_location),
+                      character_sets::USERINFO_PERCENT_ENCODE);
+                  url.password += unicode::percent_encode(
+                      authority_view.substr(password_token_location + 1),
+                      character_sets::USERINFO_PERCENT_ENCODE);
+                } else {
+                  url.append_base_username(unicode::percent_encode(
+                      authority_view.substr(0, password_token_location),
+                      character_sets::USERINFO_PERCENT_ENCODE));
+                  url.append_base_password(unicode::percent_encode(
+                      authority_view.substr(password_token_location + 1),
+                      character_sets::USERINFO_PERCENT_ENCODE));
+                }
+              }
+            } else {
+              if constexpr (result_type_is_ada_url) {
+                url.password += unicode::percent_encode(
+                    authority_view, character_sets::USERINFO_PERCENT_ENCODE);
+              } else {
+                url.append_base_password(unicode::percent_encode(
+                    authority_view, character_sets::USERINFO_PERCENT_ENCODE));
+              }
+            }
+          }
+          // Otherwise, if one of the following is true:
+          // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
+          // - url is special and c is U+005C (\)
+          else if (end_of_authority == input_size ||
+                   url_data[end_of_authority] == '/' ||
+                   url_data[end_of_authority] == '?' ||
+                   (url.is_special() && url_data[end_of_authority] == '\\')) {
+            // If atSignSeen is true and authority_view is the empty string,
+            // validation error, return failure.
+            if (at_sign_seen && authority_view.empty()) {
+              url.is_valid = false;
+              return url;
+            }
+            state = ada::state::HOST;
+            break;
+          }
+          if (end_of_authority == input_size) {
+            if (fragment.has_value()) {
+              url.update_unencoded_base_hash(*fragment);
+            }
+            return url;
+          }
+          input_position = end_of_authority + 1;
+        } while (true);
+
+        break;
+      }
+      case ada::state::SPECIAL_RELATIVE_OR_AUTHORITY: {
+        ada_log("SPECIAL_RELATIVE_OR_AUTHORITY ",
+                helpers::substring(url_data, input_position));
+
+        // If c is U+002F (/) and remaining starts with U+002F (/),
+        // then set state to special authority ignore slashes state and increase
+        // pointer by 1.
+        std::string_view view = helpers::substring(url_data, input_position);
+        if (ada::checkers::begins_with(view, "//")) {
+          state = ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES;
+          input_position += 2;
+        } else {
+          // Otherwise, validation error, set state to relative state and
+          // decrease pointer by 1.
+          state = ada::state::RELATIVE_SCHEME;
+        }
+
+        break;
+      }
+      case ada::state::PATH_OR_AUTHORITY: {
+        ada_log("PATH_OR_AUTHORITY ",
+                helpers::substring(url_data, input_position));
+
+        // If c is U+002F (/), then set state to authority state.
+        if ((input_position != input_size) &&
+            (url_data[input_position] == '/')) {
+          state = ada::state::AUTHORITY;
+          input_position++;
+        } else {
+          // Otherwise, set state to path state, and decrease pointer by 1.
+          state = ada::state::PATH;
+        }
+
+        break;
+      }
+      case ada::state::RELATIVE_SCHEME: {
+        ada_log("RELATIVE_SCHEME ",
+                helpers::substring(url_data, input_position));
+
+        // Set url's scheme to base's scheme.
+        url.copy_scheme(*base_url);
+
+        // If c is U+002F (/), then set state to relative slash state.
+        if ((input_position != input_size) &&
+            (url_data[input_position] == '/')) {
+          ada_log(
+              "RELATIVE_SCHEME if c is U+002F (/), then set state to relative "
+              "slash state");
+          state = ada::state::RELATIVE_SLASH;
+        } else if (url.is_special() && (input_position != input_size) &&
+                   (url_data[input_position] == '\\')) {
+          // Otherwise, if url is special and c is U+005C (\), validation error,
+          // set state to relative slash state.
+          ada_log(
+              "RELATIVE_SCHEME  if url is special and c is U+005C, validation "
+              "error, set state to relative slash state");
+          state = ada::state::RELATIVE_SLASH;
+        } else {
+          ada_log("RELATIVE_SCHEME otherwise");
+          // Set url's username to base's username, url's password to base's
+          // password, url's host to base's host, url's port to base's port,
+          // url's path to a clone of base's path, and url's query to base's
+          // query.
+          if constexpr (result_type_is_ada_url) {
+            url.username = base_url->username;
+            url.password = base_url->password;
+            url.host = base_url->host;
+            url.port = base_url->port;
+            // cloning the base path includes cloning the has_opaque_path flag
+            url.has_opaque_path = base_url->has_opaque_path;
+            url.path = base_url->path;
+            url.query = base_url->query;
+          } else {
+            url.update_base_authority(base_url->get_href(),
+                                      base_url->get_components());
+            // TODO: Get rid of set_hostname and replace it with
+            // update_base_hostname
+            url.set_hostname(base_url->get_hostname());
+            url.update_base_port(base_url->retrieve_base_port());
+            // cloning the base path includes cloning the has_opaque_path flag
+            url.has_opaque_path = base_url->has_opaque_path;
+            url.update_base_pathname(base_url->get_pathname());
+            url.update_base_search(base_url->get_search());
+          }
+
+          url.has_opaque_path = base_url->has_opaque_path;
+
+          // If c is U+003F (?), then set url's query to the empty string, and
+          // state to query state.
+          if ((input_position != input_size) &&
+              (url_data[input_position] == '?')) {
+            state = ada::state::QUERY;
+          }
+          // Otherwise, if c is not the EOF code point:
+          else if (input_position != input_size) {
+            // Set url's query to null.
+            url.clear_search();
+            if constexpr (result_type_is_ada_url) {
+              // Shorten url's path.
+              helpers::shorten_path(url.path, url.type);
+            } else {
+              std::string_view path = url.get_pathname();
+              if (helpers::shorten_path(path, url.type)) {
+                url.update_base_pathname(std::string(path));
+              }
+            }
+            // Set state to path state and decrease pointer by 1.
+            state = ada::state::PATH;
+            break;
+          }
+        }
+        input_position++;
+        break;
+      }
+      case ada::state::RELATIVE_SLASH: {
+        ada_log("RELATIVE_SLASH ",
+                helpers::substring(url_data, input_position));
+
+        // If url is special and c is U+002F (/) or U+005C (\), then:
+        if (url.is_special() && (input_position != input_size) &&
+            (url_data[input_position] == '/' ||
+             url_data[input_position] == '\\')) {
+          // Set state to special authority ignore slashes state.
+          state = ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES;
+        }
+        // Otherwise, if c is U+002F (/), then set state to authority state.
+        else if ((input_position != input_size) &&
+                 (url_data[input_position] == '/')) {
+          state = ada::state::AUTHORITY;
+        }
+        // Otherwise, set
+        // - url's username to base's username,
+        // - url's password to base's password,
+        // - url's host to base's host,
+        // - url's port to base's port,
+        // - state to path state, and then, decrease pointer by 1.
+        else {
+          if constexpr (result_type_is_ada_url) {
+            url.username = base_url->username;
+            url.password = base_url->password;
+            url.host = base_url->host;
+            url.port = base_url->port;
+          } else {
+            url.update_base_authority(base_url->get_href(),
+                                      base_url->get_components());
+            // TODO: Get rid of set_hostname and replace it with
+            // update_base_hostname
+            url.set_hostname(base_url->get_hostname());
+            url.update_base_port(base_url->retrieve_base_port());
+          }
+          state = ada::state::PATH;
+          break;
+        }
+
+        input_position++;
+        break;
+      }
+      case ada::state::SPECIAL_AUTHORITY_SLASHES: {
+        ada_log("SPECIAL_AUTHORITY_SLASHES ",
+                helpers::substring(url_data, input_position));
+
+        // If c is U+002F (/) and remaining starts with U+002F (/),
+        // then set state to special authority ignore slashes state and increase
+        // pointer by 1.
+        std::string_view view = helpers::substring(url_data, input_position);
+        if (ada::checkers::begins_with(view, "//")) {
+          input_position += 2;
+        }
+
+        [[fallthrough]];
+      }
+      case ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES: {
+        ada_log("SPECIAL_AUTHORITY_IGNORE_SLASHES ",
+                helpers::substring(url_data, input_position));
+
+        // If c is neither U+002F (/) nor U+005C (\), then set state to
+        // authority state and decrease pointer by 1.
+        while ((input_position != input_size) &&
+               ((url_data[input_position] == '/') ||
+                (url_data[input_position] == '\\'))) {
+          input_position++;
+        }
+        state = ada::state::AUTHORITY;
+
+        break;
+      }
+      case ada::state::QUERY: {
+        ada_log("QUERY ", helpers::substring(url_data, input_position));
+        // Let queryPercentEncodeSet be the special-query percent-encode set if
+        // url is special; otherwise the query percent-encode set.
+        const uint8_t* query_percent_encode_set =
+            url.is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE
+                             : ada::character_sets::QUERY_PERCENT_ENCODE;
+
+        // Percent-encode after encoding, with encoding, buffer, and
+        // queryPercentEncodeSet, and append the result to url's query.
+        url.update_base_search(helpers::substring(url_data, input_position),
+                               query_percent_encode_set);
+        ada_log("QUERY update_base_search completed ");
+        if (fragment.has_value()) {
+          url.update_unencoded_base_hash(*fragment);
+        }
+        return url;
+      }
+      case ada::state::HOST: {
+        ada_log("HOST ", helpers::substring(url_data, input_position));
+
+        std::string_view host_view =
+            helpers::substring(url_data, input_position);
+        auto [location, found_colon] =
+            helpers::get_host_delimiter_location(url.is_special(), host_view);
+        input_position = (location != std::string_view::npos)
+                             ? input_position + location
+                             : input_size;
+        // Otherwise, if c is U+003A (:) and insideBrackets is false, then:
+        // Note: the 'found_colon' value is true if and only if a colon was
+        // encountered while not inside brackets.
+        if (found_colon) {
+          // If buffer is the empty string, validation error, return failure.
+          // Let host be the result of host parsing buffer with url is not
+          // special.
+          ada_log("HOST parsing ", host_view);
+          if (!url.parse_host(host_view)) {
+            return url;
+          }
+          ada_log("HOST parsing results in ", url.get_hostname());
+          // Set url's host to host, buffer to the empty string, and state to
+          // port state.
+          state = ada::state::PORT;
+          input_position++;
+        }
+        // Otherwise, if one of the following is true:
+        // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#)
+        // - url is special and c is U+005C (\)
+        // The get_host_delimiter_location function either brings us to
+        // the colon outside of the bracket, or to one of those characters.
+        else {
+          // If url is special and host_view is the empty string, validation
+          // error, return failure.
+          if (url.is_special() && host_view.empty()) {
+            url.is_valid = false;
+            return url;
+          }
+          ada_log("HOST parsing ", host_view, " href=", url.get_href());
+          // Let host be the result of host parsing host_view with url is not
+          // special.
+          if (host_view.empty()) {
+            url.update_base_hostname("");
+          } else if (!url.parse_host(host_view)) {
+            return url;
+          }
+          ada_log("HOST parsing results in ", url.get_hostname(),
+                  " href=", url.get_href());
+
+          // Set url's host to host, and state to path start state.
+          state = ada::state::PATH_START;
+        }
+
+        break;
+      }
+      case ada::state::OPAQUE_PATH: {
+        ada_log("OPAQUE_PATH ", helpers::substring(url_data, input_position));
+        std::string_view view = helpers::substring(url_data, input_position);
+        // If c is U+003F (?), then set url's query to the empty string and
+        // state to query state.
+        size_t location = view.find('?');
+        if (location != std::string_view::npos) {
+          view.remove_suffix(view.size() - location);
+          state = ada::state::QUERY;
+          input_position += location + 1;
+        } else {
+          input_position = input_size + 1;
+        }
+        url.has_opaque_path = true;
+        // This is a really unlikely scenario in real world. We should not seek
+        // to optimize it.
+        url.update_base_pathname(unicode::percent_encode(
+            view, character_sets::C0_CONTROL_PERCENT_ENCODE));
+        break;
+      }
+      case ada::state::PORT: {
+        ada_log("PORT ", helpers::substring(url_data, input_position));
+        std::string_view port_view =
+            helpers::substring(url_data, input_position);
+        size_t consumed_bytes = url.parse_port(port_view, true);
+        input_position += consumed_bytes;
+        if (!url.is_valid) {
+          return url;
+        }
+        state = state::PATH_START;
+        [[fallthrough]];
+      }
+      case ada::state::PATH_START: {
+        ada_log("PATH_START ", helpers::substring(url_data, input_position));
+
+        // If url is special, then:
+        if (url.is_special()) {
+          // Set state to path state.
+          state = ada::state::PATH;
+
+          // Optimization: Avoiding going into PATH state improves the
+          // performance of urls ending with /.
+          if (input_position == input_size) {
+            url.update_base_pathname("/");
+            if (fragment.has_value()) {
+              url.update_unencoded_base_hash(*fragment);
+            }
+            return url;
+          }
+          // If c is neither U+002F (/) nor U+005C (\), then decrease pointer
+          // by 1. We know that (input_position == input_size) is impossible
+          // here, because of the previous if-check.
+          if ((url_data[input_position] != '/') &&
+              (url_data[input_position] != '\\')) {
+            break;
+          }
+        }
+        // Otherwise, if state override is not given and c is U+003F (?),
+        // set url's query to the empty string and state to query state.
+        else if ((input_position != input_size) &&
+                 (url_data[input_position] == '?')) {
+          state = ada::state::QUERY;
+        }
+        // Otherwise, if c is not the EOF code point:
+        else if (input_position != input_size) {
+          // Set state to path state.
+          state = ada::state::PATH;
+
+          // If c is not U+002F (/), then decrease pointer by 1.
+          if (url_data[input_position] != '/') {
+            break;
+          }
+        }
+
+        input_position++;
+        break;
+      }
+      case ada::state::PATH: {
+        std::string_view view = helpers::substring(url_data, input_position);
+        ada_log("PATH ", helpers::substring(url_data, input_position));
+
+        // Most time, we do not need percent encoding.
+        // Furthermore, we can immediately locate the '?'.
+        size_t locofquestionmark = view.find('?');
+        if (locofquestionmark != std::string_view::npos) {
+          state = ada::state::QUERY;
+          view.remove_suffix(view.size() - locofquestionmark);
+          input_position += locofquestionmark + 1;
+        } else {
+          input_position = input_size + 1;
+        }
+        if constexpr (result_type_is_ada_url) {
+          helpers::parse_prepared_path(view, url.type, url.path);
+        } else {
+          url.consume_prepared_path(view);
+          ADA_ASSERT_TRUE(url.validate());
+        }
+        break;
+      }
+      case ada::state::FILE_SLASH: {
+        ada_log("FILE_SLASH ", helpers::substring(url_data, input_position));
+
+        // If c is U+002F (/) or U+005C (\), then:
+        if ((input_position != input_size) &&
+            (url_data[input_position] == '/' ||
+             url_data[input_position] == '\\')) {
+          ada_log("FILE_SLASH c is U+002F or U+005C");
+          // Set state to file host state.
+          state = ada::state::FILE_HOST;
+          input_position++;
+        } else {
+          ada_log("FILE_SLASH otherwise");
+          // If base is non-null and base's scheme is "file", then:
+          // Note: it is unsafe to do base_url->scheme unless you know that
+          // base_url_has_value() is true.
+          if (base_url != nullptr &&
+              base_url->type == ada::scheme::type::FILE) {
+            // Set url's host to base's host.
+            if constexpr (result_type_is_ada_url) {
+              url.host = base_url->host;
+            } else {
+              // TODO: Optimization opportunity.
+              url.set_host(base_url->get_host());
+            }
+            // If the code point substring from pointer to the end of input does
+            // not start with a Windows drive letter and base's path[0] is a
+            // normalized Windows drive letter, then append base's path[0] to
+            // url's path.
+            if (!base_url->get_pathname().empty()) {
+              if (!checkers::is_windows_drive_letter(
+                      helpers::substring(url_data, input_position))) {
+                std::string_view first_base_url_path =
+                    base_url->get_pathname().substr(1);
+                size_t loc = first_base_url_path.find('/');
+                if (loc != std::string_view::npos) {
+                  helpers::resize(first_base_url_path, loc);
+                }
+                if (checkers::is_normalized_windows_drive_letter(
+                        first_base_url_path)) {
+                  if constexpr (result_type_is_ada_url) {
+                    url.path += '/';
+                    url.path += first_base_url_path;
+                  } else {
+                    url.append_base_pathname(
+                        helpers::concat("/", first_base_url_path));
+                  }
+                }
+              }
+            }
+          }
+
+          // Set state to path state, and decrease pointer by 1.
+          state = ada::state::PATH;
+        }
+
+        break;
+      }
+      case ada::state::FILE_HOST: {
+        std::string_view view = helpers::substring(url_data, input_position);
+        ada_log("FILE_HOST ", helpers::substring(url_data, input_position));
+
+        size_t location = view.find_first_of("/\\?");
+        std::string_view file_host_buffer(
+            view.data(),
+            (location != std::string_view::npos) ? location : view.size());
+
+        if (checkers::is_windows_drive_letter(file_host_buffer)) {
+          state = ada::state::PATH;
+        } else if (file_host_buffer.empty()) {
+          // Set url's host to the empty string.
+          if constexpr (result_type_is_ada_url) {
+            url.host = "";
+          } else {
+            url.update_base_hostname("");
+          }
+          // Set state to path start state.
+          state = ada::state::PATH_START;
+        } else {
+          size_t consumed_bytes = file_host_buffer.size();
+          input_position += consumed_bytes;
+          // Let host be the result of host parsing buffer with url is not
+          // special.
+          if (!url.parse_host(file_host_buffer)) {
+            return url;
+          }
+
+          if constexpr (result_type_is_ada_url) {
+            // If host is "localhost", then set host to the empty string.
+            if (url.host.has_value() && url.host.value() == "localhost") {
+              url.host = "";
+            }
+          } else {
+            if (url.get_hostname() == "localhost") {
+              url.update_base_hostname("");
+            }
+          }
+
+          // Set buffer to the empty string and state to path start state.
+          state = ada::state::PATH_START;
+        }
+
+        break;
+      }
+      case ada::state::FILE: {
+        ada_log("FILE ", helpers::substring(url_data, input_position));
+        std::string_view file_view =
+            helpers::substring(url_data, input_position);
+
+        url.set_protocol_as_file();
+        if constexpr (result_type_is_ada_url) {
+          // Set url's host to the empty string.
+          url.host = "";
+        } else {
+          url.update_base_hostname("");
+        }
+        // If c is U+002F (/) or U+005C (\), then:
+        if (input_position != input_size &&
+            (url_data[input_position] == '/' ||
+             url_data[input_position] == '\\')) {
+          ada_log("FILE c is U+002F or U+005C");
+          // Set state to file slash state.
+          state = ada::state::FILE_SLASH;
+        }
+        // Otherwise, if base is non-null and base's scheme is "file":
+        else if (base_url != nullptr &&
+                 base_url->type == ada::scheme::type::FILE) {
+          // Set url's host to base's host, url's path to a clone of base's
+          // path, and url's query to base's query.
+          ada_log("FILE base non-null");
+          if constexpr (result_type_is_ada_url) {
+            url.host = base_url->host;
+            url.path = base_url->path;
+            url.query = base_url->query;
+          } else {
+            // TODO: Get rid of set_hostname and replace it with
+            // update_base_hostname
+            url.set_hostname(base_url->get_hostname());
+            url.update_base_pathname(base_url->get_pathname());
+            url.update_base_search(base_url->get_search());
+          }
+          url.has_opaque_path = base_url->has_opaque_path;
+
+          // If c is U+003F (?), then set url's query to the empty string and
+          // state to query state.
+          if (input_position != input_size && url_data[input_position] == '?') {
+            state = ada::state::QUERY;
+          }
+          // Otherwise, if c is not the EOF code point:
+          else if (input_position != input_size) {
+            // Set url's query to null.
+            url.clear_search();
+            // If the code point substring from pointer to the end of input does
+            // not start with a Windows drive letter, then shorten url's path.
+            if (!checkers::is_windows_drive_letter(file_view)) {
+              if constexpr (result_type_is_ada_url) {
+                helpers::shorten_path(url.path, url.type);
+              } else {
+                std::string_view path = url.get_pathname();
+                if (helpers::shorten_path(path, url.type)) {
+                  url.update_base_pathname(std::string(path));
+                }
+              }
+            }
+            // Otherwise:
+            else {
+              // Set url's path to an empty list.
+              url.clear_pathname();
+              url.has_opaque_path = true;
+            }
+
+            // Set state to path state and decrease pointer by 1.
+            state = ada::state::PATH;
+            break;
+          }
+        }
+        // Otherwise, set state to path state, and decrease pointer by 1.
+        else {
+          ada_log("FILE go to path");
+          state = ada::state::PATH;
+          break;
+        }
+
+        input_position++;
+        break;
+      }
+      default:
+        ada::unreachable();
+    }
+  }
+  if (fragment.has_value()) {
+    url.update_unencoded_base_hash(*fragment);
+  }
+  return url;
+}
+
+template url parse_url<url>(std::string_view user_input,
+                            const url* base_url = nullptr);
+template url_aggregator parse_url<url_aggregator>(
+    std::string_view user_input, const url_aggregator* base_url = nullptr);
+
+}  // namespace ada::parser
diff --git a/src/serializers.cpp b/src/serializers.cpp
new file mode 100644 (file)
index 0000000..91be39c
--- /dev/null
@@ -0,0 +1,80 @@
+#include "ada.h"
+
+#include <array>
+#include <string>
+
+namespace ada::serializers {
+
+void find_longest_sequence_of_ipv6_pieces(
+    const std::array<uint16_t, 8>& address, size_t& compress,
+    size_t& compress_length) noexcept {
+  for (size_t i = 0; i < 8; i++) {
+    if (address[i] == 0) {
+      size_t next = i + 1;
+      while (next != 8 && address[next] == 0) ++next;
+      const size_t count = next - i;
+      if (compress_length < count) {
+        compress_length = count;
+        compress = i;
+        if (next == 8) break;
+        i = next;
+      }
+    }
+  }
+}
+
+std::string ipv6(const std::array<uint16_t, 8>& address) noexcept {
+  size_t compress_length = 0;  // The length of a long sequence of zeros.
+  size_t compress = 0;         // The start of a long sequence of zeros.
+  find_longest_sequence_of_ipv6_pieces(address, compress, compress_length);
+
+  if (compress_length <= 1) {
+    // Optimization opportunity: Find a faster way then snprintf for imploding
+    // and return here.
+    compress = compress_length = 8;
+  }
+
+  std::string output(4 * 8 + 7 + 2, '\0');
+  size_t piece_index = 0;
+  char* point = output.data();
+  char* point_end = output.data() + output.size();
+  *point++ = '[';
+  while (true) {
+    if (piece_index == compress) {
+      *point++ = ':';
+      // If we skip a value initially, we need to write '::', otherwise
+      // a single ':' will do since it follows a previous ':'.
+      if (piece_index == 0) {
+        *point++ = ':';
+      }
+      piece_index += compress_length;
+      if (piece_index == 8) {
+        break;
+      }
+    }
+    point = std::to_chars(point, point_end, address[piece_index], 16).ptr;
+    piece_index++;
+    if (piece_index == 8) {
+      break;
+    }
+    *point++ = ':';
+  }
+  *point++ = ']';
+  output.resize(point - output.data());
+  return output;
+}
+
+std::string ipv4(const uint64_t address) noexcept {
+  std::string output(15, '\0');
+  char* point = output.data();
+  char* point_end = output.data() + output.size();
+  point = std::to_chars(point, point_end, uint8_t(address >> 24)).ptr;
+  for (int i = 2; i >= 0; i--) {
+    *point++ = '.';
+    point = std::to_chars(point, point_end, uint8_t(address >> (i * 8))).ptr;
+  }
+  output.resize(point - output.data());
+  return output;
+}
+
+}  // namespace ada::serializers
diff --git a/src/unicode.cpp b/src/unicode.cpp
new file mode 100644 (file)
index 0000000..4ffe4c9
--- /dev/null
@@ -0,0 +1,488 @@
+#include "ada.h"
+#include "ada/character_sets-inl.h"
+#include "ada/common_defs.h"
+#include "ada/unicode.h"
+
+ADA_PUSH_DISABLE_ALL_WARNINGS
+#include "ada_idna.cpp"
+ADA_POP_DISABLE_WARNINGS
+
+#include <algorithm>
+#if ADA_NEON
+#include <arm_neon.h>
+#elif ADA_SSE2
+#include <emmintrin.h>
+#endif
+
+namespace ada::unicode {
+
+constexpr uint64_t broadcast(uint8_t v) noexcept {
+  return 0x101010101010101ull * v;
+}
+
+constexpr bool to_lower_ascii(char* input, size_t length) noexcept {
+  uint64_t broadcast_80 = broadcast(0x80);
+  uint64_t broadcast_Ap = broadcast(128 - 'A');
+  uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1);
+  uint64_t non_ascii = 0;
+  size_t i = 0;
+
+  for (; i + 7 < length; i += 8) {
+    uint64_t word{};
+    memcpy(&word, input + i, sizeof(word));
+    non_ascii |= (word & broadcast_80);
+    word ^=
+        (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
+    memcpy(input + i, &word, sizeof(word));
+  }
+  if (i < length) {
+    uint64_t word{};
+    memcpy(&word, input + i, length - i);
+    non_ascii |= (word & broadcast_80);
+    word ^=
+        (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80) >> 2;
+    memcpy(input + i, &word, length - i);
+  }
+  return non_ascii == 0;
+}
+#if ADA_NEON
+ada_really_inline bool has_tabs_or_newline(
+    std::string_view user_input) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (user_input.size() < 16) {  // slow path
+    for (size_t i = 0; i < user_input.size(); i++) {
+      if (user_input[i] == '\r' || user_input[i] == '\n' ||
+          user_input[i] == '\t') {
+        return true;
+      }
+    }
+    return false;
+  }
+  // fast path for long strings (expected to be common)
+  size_t i = 0;
+  const uint8x16_t mask1 = vmovq_n_u8('\r');
+  const uint8x16_t mask2 = vmovq_n_u8('\n');
+  const uint8x16_t mask3 = vmovq_n_u8('\t');
+  uint8x16_t running{0};
+  for (; i + 15 < user_input.size(); i += 16) {
+    uint8x16_t word = vld1q_u8((const uint8_t*)user_input.data() + i);
+    running = vorrq_u8(vorrq_u8(running, vorrq_u8(vceqq_u8(word, mask1),
+                                                  vceqq_u8(word, mask2))),
+                       vceqq_u8(word, mask3));
+  }
+  if (i < user_input.size()) {
+    uint8x16_t word =
+        vld1q_u8((const uint8_t*)user_input.data() + user_input.length() - 16);
+    running = vorrq_u8(vorrq_u8(running, vorrq_u8(vceqq_u8(word, mask1),
+                                                  vceqq_u8(word, mask2))),
+                       vceqq_u8(word, mask3));
+  }
+  return vmaxvq_u8(running) != 0;
+}
+#elif ADA_SSE2
+ada_really_inline bool has_tabs_or_newline(
+    std::string_view user_input) noexcept {
+  // first check for short strings in which case we do it naively.
+  if (user_input.size() < 16) {  // slow path
+    for (size_t i = 0; i < user_input.size(); i++) {
+      if (user_input[i] == '\r' || user_input[i] == '\n' ||
+          user_input[i] == '\t') {
+        return true;
+      }
+    }
+    return false;
+  }
+  // fast path for long strings (expected to be common)
+  size_t i = 0;
+  const __m128i mask1 = _mm_set1_epi8('\r');
+  const __m128i mask2 = _mm_set1_epi8('\n');
+  const __m128i mask3 = _mm_set1_epi8('\t');
+  __m128i running{0};
+  for (; i + 15 < user_input.size(); i += 16) {
+    __m128i word = _mm_loadu_si128((const __m128i*)(user_input.data() + i));
+    running = _mm_or_si128(
+        _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1),
+                                           _mm_cmpeq_epi8(word, mask2))),
+        _mm_cmpeq_epi8(word, mask3));
+  }
+  if (i < user_input.size()) {
+    __m128i word = _mm_loadu_si128(
+        (const __m128i*)(user_input.data() + user_input.length() - 16));
+    running = _mm_or_si128(
+        _mm_or_si128(running, _mm_or_si128(_mm_cmpeq_epi8(word, mask1),
+                                           _mm_cmpeq_epi8(word, mask2))),
+        _mm_cmpeq_epi8(word, mask3));
+  }
+  return _mm_movemask_epi8(running) != 0;
+}
+#else
+ada_really_inline bool has_tabs_or_newline(
+    std::string_view user_input) noexcept {
+  auto has_zero_byte = [](uint64_t v) {
+    return ((v - 0x0101010101010101) & ~(v)&0x8080808080808080);
+  };
+  size_t i = 0;
+  uint64_t mask1 = broadcast('\r');
+  uint64_t mask2 = broadcast('\n');
+  uint64_t mask3 = broadcast('\t');
+  uint64_t running{0};
+  for (; i + 7 < user_input.size(); i += 8) {
+    uint64_t word{};
+    memcpy(&word, user_input.data() + i, sizeof(word));
+    uint64_t xor1 = word ^ mask1;
+    uint64_t xor2 = word ^ mask2;
+    uint64_t xor3 = word ^ mask3;
+    running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
+  }
+  if (i < user_input.size()) {
+    uint64_t word{};
+    memcpy(&word, user_input.data() + i, user_input.size() - i);
+    uint64_t xor1 = word ^ mask1;
+    uint64_t xor2 = word ^ mask2;
+    uint64_t xor3 = word ^ mask3;
+    running |= has_zero_byte(xor1) | has_zero_byte(xor2) | has_zero_byte(xor3);
+  }
+  return running;
+}
+#endif
+
+// A forbidden host code point is U+0000 NULL, U+0009 TAB, U+000A LF, U+000D CR,
+// U+0020 SPACE, U+0023 (#), U+002F (/), U+003A (:), U+003C (<), U+003E (>),
+// U+003F (?), U+0040 (@), U+005B ([), U+005C (\), U+005D (]), U+005E (^), or
+// U+007C (|).
+constexpr static std::array<uint8_t, 256> is_forbidden_host_code_point_table =
+    []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
+                        '>', '?', '@', '[', '\\', ']', '^', '|'}) {
+        result[c] = true;
+      }
+      return result;
+    }();
+
+ada_really_inline constexpr bool is_forbidden_host_code_point(
+    const char c) noexcept {
+  return is_forbidden_host_code_point_table[uint8_t(c)];
+}
+
+constexpr static std::array<uint8_t, 256> is_forbidden_domain_code_point_table =
+    []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
+                        '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) {
+        result[c] = true;
+      }
+      for (uint8_t c = 0; c <= 32; c++) {
+        result[c] = true;
+      }
+      for (size_t c = 127; c < 255; c++) {
+        result[c] = true;
+      }
+      return result;
+    }();
+
+static_assert(sizeof(is_forbidden_domain_code_point_table) == 256);
+
+ada_really_inline constexpr bool is_forbidden_domain_code_point(
+    const char c) noexcept {
+  return is_forbidden_domain_code_point_table[uint8_t(c)];
+}
+
+ada_really_inline constexpr bool contains_forbidden_domain_code_point(
+    const char* input, size_t length) noexcept {
+  size_t i = 0;
+  uint8_t accumulator{};
+  for (; i + 4 <= length; i += 4) {
+    accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])];
+    accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 1])];
+    accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 2])];
+    accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i + 3])];
+  }
+  for (; i < length; i++) {
+    accumulator |= is_forbidden_domain_code_point_table[uint8_t(input[i])];
+  }
+  return accumulator;
+}
+
+constexpr static std::array<uint8_t, 256>
+    is_forbidden_domain_code_point_table_or_upper = []() constexpr {
+      std::array<uint8_t, 256> result{};
+      for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<',
+                        '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) {
+        result[c] = 1;
+      }
+      for (uint8_t c = 'A'; c <= 'Z'; c++) {
+        result[c] = 2;
+      }
+      for (uint8_t c = 0; c <= 32; c++) {
+        result[c] = 1;
+      }
+      for (size_t c = 127; c < 255; c++) {
+        result[c] = 1;
+      }
+      return result;
+    }();
+
+ada_really_inline constexpr uint8_t
+contains_forbidden_domain_code_point_or_upper(const char* input,
+                                              size_t length) noexcept {
+  size_t i = 0;
+  uint8_t accumulator{};
+  for (; i + 4 <= length; i += 4) {
+    accumulator |=
+        is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i])];
+    accumulator |=
+        is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 1])];
+    accumulator |=
+        is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 2])];
+    accumulator |=
+        is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i + 3])];
+  }
+  for (; i < length; i++) {
+    accumulator |=
+        is_forbidden_domain_code_point_table_or_upper[uint8_t(input[i])];
+  }
+  return accumulator;
+}
+
+// std::isalnum(c) || c == '+' || c == '-' || c == '.') is true for
+constexpr static std::array<bool, 256> is_alnum_plus_table = []() constexpr {
+  std::array<bool, 256> result{};
+  for (size_t c = 0; c < 256; c++) {
+    if (c >= '0' && c <= '9') {
+      result[c] = true;
+    } else if (c >= 'a' && c <= 'z') {
+      result[c] = true;
+    } else if (c >= 'A' && c <= 'Z') {
+      result[c] = true;
+    } else if (c == '+' || c == '-' || c == '.') {
+      result[c] = true;
+    }
+  }
+  return result;
+}();
+
+ada_really_inline constexpr bool is_alnum_plus(const char c) noexcept {
+  return is_alnum_plus_table[uint8_t(c)];
+  // A table is almost surely much faster than the
+  // following under most compilers: return
+  // return (std::isalnum(c) || c == '+' || c == '-' || c == '.');
+}
+
+ada_really_inline constexpr bool is_ascii_hex_digit(const char c) noexcept {
+  return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') ||
+         (c >= 'a' && c <= 'f');
+}
+
+ada_really_inline constexpr bool is_c0_control_or_space(const char c) noexcept {
+  return (unsigned char)c <= ' ';
+}
+
+ada_really_inline constexpr bool is_ascii_tab_or_newline(
+    const char c) noexcept {
+  return c == '\t' || c == '\n' || c == '\r';
+}
+
+constexpr std::string_view table_is_double_dot_path_segment[] = {
+    "..", "%2e.", ".%2e", "%2e%2e"};
+
+ada_really_inline ada_constexpr bool is_double_dot_path_segment(
+    std::string_view input) noexcept {
+  // This will catch most cases:
+  // The length must be 2,4 or 6.
+  // We divide by two and require
+  // that the result be between 1 and 3 inclusively.
+  uint64_t half_length = uint64_t(input.size()) / 2;
+  if (half_length - 1 > 2) {
+    return false;
+  }
+  // We have a string of length 2, 4 or 6.
+  // We now check the first character:
+  if ((input[0] != '.') && (input[0] != '%')) {
+    return false;
+  }
+  // We are unlikely the get beyond this point.
+  int hash_value = (input.size() + (unsigned)(input[0])) & 3;
+  const std::string_view target = table_is_double_dot_path_segment[hash_value];
+  if (target.size() != input.size()) {
+    return false;
+  }
+  // We almost never get here.
+  // Optimizing the rest is relatively unimportant.
+  auto prefix_equal_unsafe = [](std::string_view a, std::string_view b) {
+    uint16_t A, B;
+    memcpy(&A, a.data(), sizeof(A));
+    memcpy(&B, b.data(), sizeof(B));
+    return A == B;
+  };
+  if (!prefix_equal_unsafe(input, target)) {
+    return false;
+  }
+  for (size_t i = 2; i < input.size(); i++) {
+    char c = input[i];
+    if ((uint8_t((c | 0x20) - 0x61) <= 25 ? (c | 0x20) : c) != target[i]) {
+      return false;
+    }
+  }
+  return true;
+  // The above code might be a bit better than the code below. Compilers
+  // are not stupid and may use the fact that these strings have length 2,4 and
+  // 6 and other tricks.
+  // return input == ".." ||
+  //  input == ".%2e" || input == ".%2E" ||
+  //  input == "%2e." || input == "%2E." ||
+  //  input == "%2e%2e" || input == "%2E%2E" || input == "%2E%2e" || input ==
+  //  "%2e%2E";
+}
+
+ada_really_inline constexpr bool is_single_dot_path_segment(
+    std::string_view input) noexcept {
+  return input == "." || input == "%2e" || input == "%2E";
+}
+
+ada_really_inline constexpr bool is_lowercase_hex(const char c) noexcept {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
+}
+
+constexpr static char hex_to_binary_table[] = {
+    0,  1,  2,  3,  4, 5, 6, 7, 8, 9, 0, 0,  0,  0,  0,  0,  0, 10, 11,
+    12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0,  0,  0,  0,  0,  0, 0,  0,
+    0,  0,  0,  0,  0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15};
+unsigned constexpr convert_hex_to_binary(const char c) noexcept {
+  return hex_to_binary_table[c - '0'];
+}
+
+std::string percent_decode(const std::string_view input, size_t first_percent) {
+  // next line is for safety only, we expect users to avoid calling
+  // percent_decode when first_percent is outside the range.
+  if (first_percent == std::string_view::npos) {
+    return std::string(input);
+  }
+  std::string dest;
+  dest.reserve(input.length());
+  dest.append(input.substr(0, first_percent));
+  const char* pointer = input.data() + first_percent;
+  const char* end = input.data() + input.size();
+  // Optimization opportunity: if the following code gets
+  // called often, it can be optimized quite a bit.
+  while (pointer < end) {
+    const char ch = pointer[0];
+    size_t remaining = end - pointer - 1;
+    if (ch != '%' || remaining < 2 ||
+        (  // ch == '%' && // It is unnecessary to check that ch == '%'.
+            (!is_ascii_hex_digit(pointer[1]) ||
+             !is_ascii_hex_digit(pointer[2])))) {
+      dest += ch;
+      pointer++;
+      continue;
+    } else {
+      unsigned a = convert_hex_to_binary(pointer[1]);
+      unsigned b = convert_hex_to_binary(pointer[2]);
+      char c = static_cast<char>(a * 16 + b);
+      dest += c;
+      pointer += 3;
+    }
+  }
+  return dest;
+}
+
+std::string percent_encode(const std::string_view input,
+                           const uint8_t character_set[]) {
+  auto pointer =
+      std::find_if(input.begin(), input.end(), [character_set](const char c) {
+        return character_sets::bit_at(character_set, c);
+      });
+  // Optimization: Don't iterate if percent encode is not required
+  if (pointer == input.end()) {
+    return std::string(input);
+  }
+
+  std::string result;
+  result.reserve(input.length());  // in the worst case, percent encoding might
+                                   // produce 3 characters.
+  result.append(input.substr(0, std::distance(input.begin(), pointer)));
+
+  for (; pointer != input.end(); pointer++) {
+    if (character_sets::bit_at(character_set, *pointer)) {
+      result.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
+    } else {
+      result += *pointer;
+    }
+  }
+
+  return result;
+}
+
+template <bool append>
+bool percent_encode(const std::string_view input, const uint8_t character_set[],
+                    std::string& out) {
+  ada_log("percent_encode ", input, " to output string while ",
+          append ? "appending" : "overwriting");
+  auto pointer =
+      std::find_if(input.begin(), input.end(), [character_set](const char c) {
+        return character_sets::bit_at(character_set, c);
+      });
+  ada_log("percent_encode done checking, moved to ",
+          std::distance(input.begin(), pointer));
+
+  // Optimization: Don't iterate if percent encode is not required
+  if (pointer == input.end()) {
+    ada_log("percent_encode encoding not needed.");
+    return false;
+  }
+  if (!append) {
+    out.clear();
+  }
+  ada_log("percent_encode appending ", std::distance(input.begin(), pointer),
+          " bytes");
+  out.append(input.data(), std::distance(input.begin(), pointer));
+  ada_log("percent_encode processing ", std::distance(pointer, input.end()),
+          " bytes");
+  for (; pointer != input.end(); pointer++) {
+    if (character_sets::bit_at(character_set, *pointer)) {
+      out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
+    } else {
+      out += *pointer;
+    }
+  }
+  return true;
+}
+
+bool to_ascii(std::optional<std::string>& out, const std::string_view plain,
+              size_t first_percent) {
+  std::string percent_decoded_buffer;
+  std::string_view input = plain;
+  if (first_percent != std::string_view::npos) {
+    percent_decoded_buffer = unicode::percent_decode(plain, first_percent);
+    input = percent_decoded_buffer;
+  }
+  // input is a non-empty UTF-8 string, must be percent decoded
+  std::string idna_ascii = ada::idna::to_ascii(input);
+  if (idna_ascii.empty() || contains_forbidden_domain_code_point(
+                                idna_ascii.data(), idna_ascii.size())) {
+    return false;
+  }
+  out = std::move(idna_ascii);
+  return true;
+}
+
+std::string percent_encode(const std::string_view input,
+                           const uint8_t character_set[], size_t index) {
+  std::string out;
+  out.append(input.data(), index);
+  auto pointer = input.begin() + index;
+  for (; pointer != input.end(); pointer++) {
+    if (character_sets::bit_at(character_set, *pointer)) {
+      out.append(character_sets::hex + uint8_t(*pointer) * 4, 3);
+    } else {
+      out += *pointer;
+    }
+  }
+  return out;
+}
+
+std::string to_unicode(std::string_view input) {
+  return ada::idna::to_unicode(input);
+}
+
+}  // namespace ada::unicode
diff --git a/src/url-getters.cpp b/src/url-getters.cpp
new file mode 100644 (file)
index 0000000..d965dc9
--- /dev/null
@@ -0,0 +1,96 @@
+/**
+ * @file url-getters.cpp
+ * Includes all the getters of `ada::url`
+ */
+#include "ada.h"
+#include "ada/implementation.h"
+#include "ada/helpers.h"
+#include "ada/scheme.h"
+
+#include <algorithm>
+#include <string>
+
+namespace ada {
+[[nodiscard]] std::string url::get_origin() const noexcept {
+  if (is_special()) {
+    // Return a new opaque origin.
+    if (type == scheme::FILE) {
+      return "null";
+    }
+    return ada::helpers::concat(get_protocol(), "//", get_host());
+  }
+
+  if (non_special_scheme == "blob") {
+    if (!path.empty()) {
+      auto result = ada::parse<ada::url>(path);
+      if (result &&
+          (result->type == scheme::HTTP || result->type == scheme::HTTPS)) {
+        // If pathURL's scheme is not "http" and not "https", then return a
+        // new opaque origin.
+        return ada::helpers::concat(result->get_protocol(), "//",
+                                    result->get_host());
+      }
+    }
+  }
+
+  // Return a new opaque origin.
+  return "null";
+}
+
+[[nodiscard]] std::string url::get_protocol() const noexcept {
+  if (is_special()) {
+    return helpers::concat(ada::scheme::details::is_special_list[type], ":");
+  }
+  // We only move the 'scheme' if it is non-special.
+  return helpers::concat(non_special_scheme, ":");
+}
+
+[[nodiscard]] std::string url::get_host() const noexcept {
+  // If url's host is null, then return the empty string.
+  // If url's port is null, return url's host, serialized.
+  // Return url's host, serialized, followed by U+003A (:) and url's port,
+  // serialized.
+  if (!host.has_value()) {
+    return "";
+  }
+  if (port.has_value()) {
+    return host.value() + ":" + get_port();
+  }
+  return host.value();
+}
+
+[[nodiscard]] std::string url::get_hostname() const noexcept {
+  return host.value_or("");
+}
+
+[[nodiscard]] std::string_view url::get_pathname() const noexcept {
+  return path;
+}
+
+[[nodiscard]] std::string url::get_search() const noexcept {
+  // If this's URL's query is either null or the empty string, then return the
+  // empty string. Return U+003F (?), followed by this's URL's query.
+  return (!query.has_value() || (query.value().empty())) ? ""
+                                                         : "?" + query.value();
+}
+
+[[nodiscard]] const std::string& url::get_username() const noexcept {
+  return username;
+}
+
+[[nodiscard]] const std::string& url::get_password() const noexcept {
+  return password;
+}
+
+[[nodiscard]] std::string url::get_port() const noexcept {
+  return port.has_value() ? std::to_string(port.value()) : "";
+}
+
+[[nodiscard]] std::string url::get_hash() const noexcept {
+  // If this's URL's fragment is either null or the empty string, then return
+  // the empty string. Return U+0023 (#), followed by this's URL's fragment.
+  return (!hash.has_value() || (hash.value().empty())) ? ""
+                                                       : "#" + hash.value();
+}
+
+}  // namespace ada
diff --git a/src/url-setters.cpp b/src/url-setters.cpp
new file mode 100644 (file)
index 0000000..99ca190
--- /dev/null
@@ -0,0 +1,237 @@
+/**
+ * @file url-setters.cpp
+ * Includes all the setters of `ada::url`
+ */
+#include "ada.h"
+#include "ada/helpers.h"
+
+#include <optional>
+#include <string>
+
+namespace ada {
+
+template <bool override_hostname>
+bool url::set_host_or_hostname(const std::string_view input) {
+  if (has_opaque_path) {
+    return false;
+  }
+
+  std::optional<std::string> previous_host = host;
+  std::optional<uint16_t> previous_port = port;
+
+  size_t host_end_pos = input.find('#');
+  std::string _host(input.data(), host_end_pos != std::string_view::npos
+                                      ? host_end_pos
+                                      : input.size());
+  helpers::remove_ascii_tab_or_newline(_host);
+  std::string_view new_host(_host);
+
+  // If url's scheme is "file", then set state to file host state, instead of
+  // host state.
+  if (type != ada::scheme::type::FILE) {
+    std::string_view host_view(_host.data(), _host.length());
+    auto [location, found_colon] =
+        helpers::get_host_delimiter_location(is_special(), host_view);
+
+    // Otherwise, if c is U+003A (:) and insideBrackets is false, then:
+    // Note: the 'found_colon' value is true if and only if a colon was
+    // encountered while not inside brackets.
+    if (found_colon) {
+      if (override_hostname) {
+        return false;
+      }
+      std::string_view buffer = new_host.substr(location + 1);
+      if (!buffer.empty()) {
+        set_port(buffer);
+      }
+    }
+    // If url is special and host_view is the empty string, validation error,
+    // return failure. Otherwise, if state override is given, host_view is the
+    // empty string, and either url includes credentials or url's port is
+    // non-null, return.
+    else if (host_view.empty() &&
+             (is_special() || has_credentials() || port.has_value())) {
+      return false;
+    }
+
+    // Let host be the result of host parsing host_view with url is not special.
+    if (host_view.empty() && !is_special()) {
+      host = "";
+      return true;
+    }
+
+    bool succeeded = parse_host(host_view);
+    if (!succeeded) {
+      host = previous_host;
+      update_base_port(previous_port);
+    }
+    return succeeded;
+  }
+
+  size_t location = new_host.find_first_of("/\\?");
+  if (location != std::string_view::npos) {
+    new_host.remove_suffix(new_host.length() - location);
+  }
+
+  if (new_host.empty()) {
+    // Set url's host to the empty string.
+    host = "";
+  } else {
+    // Let host be the result of host parsing buffer with url is not special.
+    if (!parse_host(new_host)) {
+      host = previous_host;
+      update_base_port(previous_port);
+      return false;
+    }
+
+    // If host is "localhost", then set host to the empty string.
+    if (host.has_value() && host.value() == "localhost") {
+      host = "";
+    }
+  }
+  return true;
+}
+
+bool url::set_host(const std::string_view input) {
+  return set_host_or_hostname<false>(input);
+}
+
+bool url::set_hostname(const std::string_view input) {
+  return set_host_or_hostname<true>(input);
+}
+
+bool url::set_username(const std::string_view input) {
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  username = ada::unicode::percent_encode(
+      input, character_sets::USERINFO_PERCENT_ENCODE);
+  return true;
+}
+
+bool url::set_password(const std::string_view input) {
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  password = ada::unicode::percent_encode(
+      input, character_sets::USERINFO_PERCENT_ENCODE);
+  return true;
+}
+
+bool url::set_port(const std::string_view input) {
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  std::string trimmed(input);
+  helpers::remove_ascii_tab_or_newline(trimmed);
+  if (trimmed.empty()) {
+    port = std::nullopt;
+    return true;
+  }
+  // Input should not start with control characters.
+  if (ada::unicode::is_c0_control_or_space(trimmed.front())) {
+    return false;
+  }
+  // Input should contain at least one ascii digit.
+  if (input.find_first_of("0123456789") == std::string_view::npos) {
+    return false;
+  }
+
+  // Revert changes if parse_port fails.
+  std::optional<uint16_t> previous_port = port;
+  parse_port(trimmed);
+  if (is_valid) {
+    return true;
+  }
+  port = previous_port;
+  is_valid = true;
+  return false;
+}
+
+void url::set_hash(const std::string_view input) {
+  if (input.empty()) {
+    hash = std::nullopt;
+    helpers::strip_trailing_spaces_from_opaque_path(*this);
+    return;
+  }
+
+  std::string new_value;
+  new_value = input[0] == '#' ? input.substr(1) : input;
+  helpers::remove_ascii_tab_or_newline(new_value);
+  hash = unicode::percent_encode(new_value,
+                                 ada::character_sets::FRAGMENT_PERCENT_ENCODE);
+  return;
+}
+
+void url::set_search(const std::string_view input) {
+  if (input.empty()) {
+    query = std::nullopt;
+    helpers::strip_trailing_spaces_from_opaque_path(*this);
+    return;
+  }
+
+  std::string new_value;
+  new_value = input[0] == '?' ? input.substr(1) : input;
+  helpers::remove_ascii_tab_or_newline(new_value);
+
+  auto query_percent_encode_set =
+      is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE
+                   : ada::character_sets::QUERY_PERCENT_ENCODE;
+
+  query = ada::unicode::percent_encode(std::string_view(new_value),
+                                       query_percent_encode_set);
+}
+
+bool url::set_pathname(const std::string_view input) {
+  if (has_opaque_path) {
+    return false;
+  }
+  path = "";
+  parse_path(input);
+  return true;
+}
+
+bool url::set_protocol(const std::string_view input) {
+  std::string view(input);
+  helpers::remove_ascii_tab_or_newline(view);
+  if (view.empty()) {
+    return true;
+  }
+
+  // Schemes should start with alpha values.
+  if (!checkers::is_alpha(view[0])) {
+    return false;
+  }
+
+  view.append(":");
+
+  std::string::iterator pointer =
+      std::find_if_not(view.begin(), view.end(), unicode::is_alnum_plus);
+
+  if (pointer != view.end() && *pointer == ':') {
+    return parse_scheme<true>(
+        std::string_view(view.data(), pointer - view.begin()));
+  }
+  return false;
+}
+
+bool url::set_href(const std::string_view input) {
+  ada::result<ada::url> out = ada::parse<ada::url>(input);
+
+  if (out) {
+    username = out->username;
+    password = out->password;
+    host = out->host;
+    port = out->port;
+    path = out->path;
+    query = out->query;
+    hash = out->hash;
+    type = out->type;
+    non_special_scheme = out->non_special_scheme;
+    has_opaque_path = out->has_opaque_path;
+  }
+
+  return out.has_value();
+}
+
+}  // namespace ada
diff --git a/src/url.cpp b/src/url.cpp
new file mode 100644 (file)
index 0000000..c03ba76
--- /dev/null
@@ -0,0 +1,592 @@
+#include "ada.h"
+#include "ada/scheme.h"
+#include "ada/log.h"
+
+#include <numeric>
+#include <algorithm>
+#include <string>
+
+namespace ada {
+
+bool url::parse_opaque_host(std::string_view input) {
+  ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]");
+  if (std::any_of(input.begin(), input.end(),
+                  ada::unicode::is_forbidden_host_code_point)) {
+    return is_valid = false;
+  }
+
+  // Return the result of running UTF-8 percent-encode on input using the C0
+  // control percent-encode set.
+  host = ada::unicode::percent_encode(
+      input, ada::character_sets::C0_CONTROL_PERCENT_ENCODE);
+  return true;
+}
+
+bool url::parse_ipv4(std::string_view input) {
+  ada_log("parse_ipv4 ", input, " [", input.size(), " bytes]");
+  if (input.back() == '.') {
+    input.remove_suffix(1);
+  }
+  size_t digit_count{0};
+  int pure_decimal_count = 0;  // entries that are decimal
+  std::string_view original_input =
+      input;  // we might use this if pure_decimal_count == 4.
+  uint64_t ipv4{0};
+  // we could unroll for better performance?
+  for (; (digit_count < 4) && !(input.empty()); digit_count++) {
+    uint32_t
+        segment_result{};  // If any number exceeds 32 bits, we have an error.
+    bool is_hex = checkers::has_hex_prefix(input);
+    if (is_hex && ((input.length() == 2) ||
+                   ((input.length() > 2) && (input[2] == '.')))) {
+      // special case
+      segment_result = 0;
+      input.remove_prefix(2);
+    } else {
+      std::from_chars_result r;
+      if (is_hex) {
+        r = std::from_chars(input.data() + 2, input.data() + input.size(),
+                            segment_result, 16);
+      } else if ((input.length() >= 2) && input[0] == '0' &&
+                 checkers::is_digit(input[1])) {
+        r = std::from_chars(input.data() + 1, input.data() + input.size(),
+                            segment_result, 8);
+      } else {
+        pure_decimal_count++;
+        r = std::from_chars(input.data(), input.data() + input.size(),
+                            segment_result, 10);
+      }
+      if (r.ec != std::errc()) {
+        return is_valid = false;
+      }
+      input.remove_prefix(r.ptr - input.data());
+    }
+    if (input.empty()) {
+      // We have the last value.
+      // At this stage, ipv4 contains digit_count*8 bits.
+      // So we have 32-digit_count*8 bits left.
+      if (segment_result >= (uint64_t(1) << (32 - digit_count * 8))) {
+        return is_valid = false;
+      }
+      ipv4 <<= (32 - digit_count * 8);
+      ipv4 |= segment_result;
+      goto final;
+    } else {
+      // There is more, so that the value must no be larger than 255
+      // and we must have a '.'.
+      if ((segment_result > 255) || (input[0] != '.')) {
+        return is_valid = false;
+      }
+      ipv4 <<= 8;
+      ipv4 |= segment_result;
+      input.remove_prefix(1);  // remove '.'
+    }
+  }
+  if ((digit_count != 4) || (!input.empty())) {
+    return is_valid = false;
+  }
+final:
+  // We could also check r.ptr to see where the parsing ended.
+  if (pure_decimal_count == 4) {
+    host = original_input;  // The original input was already all decimal and we
+                            // validated it.
+  } else {
+    host = ada::serializers::ipv4(ipv4);  // We have to reserialize the address.
+  }
+  host_type = IPV4;
+  return true;
+}
+
+bool url::parse_ipv6(std::string_view input) {
+  ada_log("parse_ipv6 ", input, " [", input.size(), " bytes]");
+
+  if (input.empty()) {
+    return is_valid = false;
+  }
+  // Let address be a new IPv6 address whose IPv6 pieces are all 0.
+  std::array<uint16_t, 8> address{};
+
+  // Let pieceIndex be 0.
+  int piece_index = 0;
+
+  // Let compress be null.
+  std::optional<int> compress{};
+
+  // Let pointer be a pointer for input.
+  std::string_view::iterator pointer = input.begin();
+
+  // If c is U+003A (:), then:
+  if (input[0] == ':') {
+    // If remaining does not start with U+003A (:), validation error, return
+    // failure.
+    if (input.size() == 1 || input[1] != ':') {
+      ada_log("parse_ipv6 starts with : but the rest does not start with :");
+      return is_valid = false;
+    }
+
+    // Increase pointer by 2.
+    pointer += 2;
+
+    // Increase pieceIndex by 1 and then set compress to pieceIndex.
+    compress = ++piece_index;
+  }
+
+  // While c is not the EOF code point:
+  while (pointer != input.end()) {
+    // If pieceIndex is 8, validation error, return failure.
+    if (piece_index == 8) {
+      ada_log("parse_ipv6 piece_index == 8");
+      return is_valid = false;
+    }
+
+    // If c is U+003A (:), then:
+    if (*pointer == ':') {
+      // If compress is non-null, validation error, return failure.
+      if (compress.has_value()) {
+        ada_log("parse_ipv6 compress is non-null");
+        return is_valid = false;
+      }
+
+      // Increase pointer and pieceIndex by 1, set compress to pieceIndex, and
+      // then continue.
+      pointer++;
+      compress = ++piece_index;
+      continue;
+    }
+
+    // Let value and length be 0.
+    uint16_t value = 0, length = 0;
+
+    // While length is less than 4 and c is an ASCII hex digit,
+    // set value to value times 0x10 + c interpreted as hexadecimal number, and
+    // increase pointer and length by 1.
+    while (length < 4 && pointer != input.end() &&
+           unicode::is_ascii_hex_digit(*pointer)) {
+      // https://stackoverflow.com/questions/39060852/why-does-the-addition-of-two-shorts-return-an-int
+      value = uint16_t(value * 0x10 + unicode::convert_hex_to_binary(*pointer));
+      pointer++;
+      length++;
+    }
+
+    // If c is U+002E (.), then:
+    if (pointer != input.end() && *pointer == '.') {
+      // If length is 0, validation error, return failure.
+      if (length == 0) {
+        ada_log("parse_ipv6 length is 0");
+        return is_valid = false;
+      }
+
+      // Decrease pointer by length.
+      pointer -= length;
+
+      // If pieceIndex is greater than 6, validation error, return failure.
+      if (piece_index > 6) {
+        ada_log("parse_ipv6 piece_index > 6");
+        return is_valid = false;
+      }
+
+      // Let numbersSeen be 0.
+      int numbers_seen = 0;
+
+      // While c is not the EOF code point:
+      while (pointer != input.end()) {
+        // Let ipv4Piece be null.
+        std::optional<uint16_t> ipv4_piece{};
+
+        // If numbersSeen is greater than 0, then:
+        if (numbers_seen > 0) {
+          // If c is a U+002E (.) and numbersSeen is less than 4, then increase
+          // pointer by 1.
+          if (*pointer == '.' && numbers_seen < 4) {
+            pointer++;
+          }
+          // Otherwise, validation error, return failure.
+          else {
+            ada_log("parse_ipv6 Otherwise, validation error, return failure");
+            return is_valid = false;
+          }
+        }
+
+        // If c is not an ASCII digit, validation error, return failure.
+        if (pointer == input.end() || !checkers::is_digit(*pointer)) {
+          ada_log(
+              "parse_ipv6 If c is not an ASCII digit, validation error, return "
+              "failure");
+          return is_valid = false;
+        }
+
+        // While c is an ASCII digit:
+        while (pointer != input.end() && checkers::is_digit(*pointer)) {
+          // Let number be c interpreted as decimal number.
+          int number = *pointer - '0';
+
+          // If ipv4Piece is null, then set ipv4Piece to number.
+          if (!ipv4_piece.has_value()) {
+            ipv4_piece = number;
+          }
+          // Otherwise, if ipv4Piece is 0, validation error, return failure.
+          else if (ipv4_piece == 0) {
+            ada_log("parse_ipv6 if ipv4Piece is 0, validation error");
+            return is_valid = false;
+          }
+          // Otherwise, set ipv4Piece to ipv4Piece times 10 + number.
+          else {
+            ipv4_piece = *ipv4_piece * 10 + number;
+          }
+
+          // If ipv4Piece is greater than 255, validation error, return failure.
+          if (ipv4_piece > 255) {
+            ada_log("parse_ipv6 ipv4_piece > 255");
+            return is_valid = false;
+          }
+
+          // Increase pointer by 1.
+          pointer++;
+        }
+
+        // Set address[pieceIndex] to address[pieceIndex] times 0x100 +
+        // ipv4Piece.
+        // https://stackoverflow.com/questions/39060852/why-does-the-addition-of-two-shorts-return-an-int
+        address[piece_index] =
+            uint16_t(address[piece_index] * 0x100 + *ipv4_piece);
+
+        // Increase numbersSeen by 1.
+        numbers_seen++;
+
+        // If numbersSeen is 2 or 4, then increase pieceIndex by 1.
+        if (numbers_seen == 2 || numbers_seen == 4) {
+          piece_index++;
+        }
+      }
+
+      // If numbersSeen is not 4, validation error, return failure.
+      if (numbers_seen != 4) {
+        return is_valid = false;
+      }
+
+      // Break.
+      break;
+    }
+    // Otherwise, if c is U+003A (:):
+    else if ((pointer != input.end()) && (*pointer == ':')) {
+      // Increase pointer by 1.
+      pointer++;
+
+      // If c is the EOF code point, validation error, return failure.
+      if (pointer == input.end()) {
+        ada_log(
+            "parse_ipv6 If c is the EOF code point, validation error, return "
+            "failure");
+        return is_valid = false;
+      }
+    }
+    // Otherwise, if c is not the EOF code point, validation error, return
+    // failure.
+    else if (pointer != input.end()) {
+      ada_log(
+          "parse_ipv6 Otherwise, if c is not the EOF code point, validation "
+          "error, return failure");
+      return is_valid = false;
+    }
+
+    // Set address[pieceIndex] to value.
+    address[piece_index] = value;
+
+    // Increase pieceIndex by 1.
+    piece_index++;
+  }
+
+  // If compress is non-null, then:
+  if (compress.has_value()) {
+    // Let swaps be pieceIndex - compress.
+    int swaps = piece_index - *compress;
+
+    // Set pieceIndex to 7.
+    piece_index = 7;
+
+    // While pieceIndex is not 0 and swaps is greater than 0,
+    // swap address[pieceIndex] with address[compress + swaps - 1], and then
+    // decrease both pieceIndex and swaps by 1.
+    while (piece_index != 0 && swaps > 0) {
+      std::swap(address[piece_index], address[*compress + swaps - 1]);
+      piece_index--;
+      swaps--;
+    }
+  }
+  // Otherwise, if compress is null and pieceIndex is not 8, validation error,
+  // return failure.
+  else if (piece_index != 8) {
+    ada_log(
+        "parse_ipv6 if compress is null and pieceIndex is not 8, validation "
+        "error, return failure");
+    return is_valid = false;
+  }
+  host = ada::serializers::ipv6(address);
+  ada_log("parse_ipv6 ", *host);
+  host_type = IPV6;
+  return true;
+}
+
+template <bool has_state_override>
+ada_really_inline bool url::parse_scheme(const std::string_view input) {
+  auto parsed_type = ada::scheme::get_scheme_type(input);
+  bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL);
+  /**
+   * In the common case, we will immediately recognize a special scheme (e.g.,
+   *http, https), in which case, we can go really fast.
+   **/
+  if (is_input_special) {  // fast path!!!
+    if (has_state_override) {
+      // If url's scheme is not a special scheme and buffer is a special scheme,
+      // then return.
+      if (is_special() != is_input_special) {
+        return true;
+      }
+
+      // If url includes credentials or has a non-null port, and buffer is
+      // "file", then return.
+      if ((has_credentials() || port.has_value()) &&
+          parsed_type == ada::scheme::type::FILE) {
+        return true;
+      }
+
+      // If url's scheme is "file" and its host is an empty host, then return.
+      // An empty host is the empty string.
+      if (type == ada::scheme::type::FILE && host.has_value() &&
+          host.value().empty()) {
+        return true;
+      }
+    }
+
+    type = parsed_type;
+
+    if (has_state_override) {
+      // This is uncommon.
+      uint16_t urls_scheme_port = get_special_port();
+
+      if (urls_scheme_port) {
+        // If url's port is url's scheme's default port, then set url's port to
+        // null.
+        if (port.has_value() && *port == urls_scheme_port) {
+          port = std::nullopt;
+        }
+      }
+    }
+  } else {  // slow path
+    std::string _buffer(input);
+    // Next function is only valid if the input is ASCII and returns false
+    // otherwise, but it seems that we always have ascii content so we do not
+    // need to check the return value.
+    // bool is_ascii =
+    unicode::to_lower_ascii(_buffer.data(), _buffer.size());
+
+    if (has_state_override) {
+      // If url's scheme is a special scheme and buffer is not a special scheme,
+      // then return. If url's scheme is not a special scheme and buffer is a
+      // special scheme, then return.
+      if (is_special() != ada::scheme::is_special(_buffer)) {
+        return true;
+      }
+
+      // If url includes credentials or has a non-null port, and buffer is
+      // "file", then return.
+      if ((has_credentials() || port.has_value()) && _buffer == "file") {
+        return true;
+      }
+
+      // If url's scheme is "file" and its host is an empty host, then return.
+      // An empty host is the empty string.
+      if (type == ada::scheme::type::FILE && host.has_value() &&
+          host.value().empty()) {
+        return true;
+      }
+    }
+
+    set_scheme(std::move(_buffer));
+
+    if (has_state_override) {
+      // This is uncommon.
+      uint16_t urls_scheme_port = get_special_port();
+
+      if (urls_scheme_port) {
+        // If url's port is url's scheme's default port, then set url's port to
+        // null.
+        if (port.has_value() && *port == urls_scheme_port) {
+          port = std::nullopt;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+ada_really_inline bool url::parse_host(std::string_view input) {
+  ada_log("parse_host ", input, " [", input.size(), " bytes]");
+  if (input.empty()) {
+    return is_valid = false;
+  }  // technically unnecessary.
+  // If input starts with U+005B ([), then:
+  if (input[0] == '[') {
+    // If input does not end with U+005D (]), validation error, return failure.
+    if (input.back() != ']') {
+      return is_valid = false;
+    }
+    ada_log("parse_host ipv6");
+
+    // Return the result of IPv6 parsing input with its leading U+005B ([) and
+    // trailing U+005D (]) removed.
+    input.remove_prefix(1);
+    input.remove_suffix(1);
+    return parse_ipv6(input);
+  }
+
+  // If isNotSpecial is true, then return the result of opaque-host parsing
+  // input.
+  if (!is_special()) {
+    return parse_opaque_host(input);
+  }
+  // Let domain be the result of running UTF-8 decode without BOM on the
+  // percent-decoding of input. Let asciiDomain be the result of running domain
+  // to ASCII with domain and false. The most common case is an ASCII input, in
+  // which case we do not need to call the expensive 'to_ascii' if a few
+  // conditions are met: no '%' and no 'xn-' subsequence.
+  std::string buffer = std::string(input);
+  // This next function checks that the result is ascii, but we are going to
+  // to check anyhow with is_forbidden.
+  // bool is_ascii =
+  unicode::to_lower_ascii(buffer.data(), buffer.size());
+  bool is_forbidden = unicode::contains_forbidden_domain_code_point(
+      buffer.data(), buffer.size());
+  if (is_forbidden == 0 && buffer.find("xn-") == std::string_view::npos) {
+    // fast path
+    host = std::move(buffer);
+    if (checkers::is_ipv4(host.value())) {
+      ada_log("parse_host fast path ipv4");
+      return parse_ipv4(host.value());
+    }
+    ada_log("parse_host fast path ", *host);
+    return true;
+  }
+  ada_log("parse_host calling to_ascii");
+  is_valid = ada::unicode::to_ascii(host, input, input.find('%'));
+  if (!is_valid) {
+    ada_log("parse_host to_ascii returns false");
+    return is_valid = false;
+  }
+  ada_log("parse_host to_ascii succeeded ", *host, " [", host->size(),
+          " bytes]");
+
+  if (std::any_of(host.value().begin(), host.value().end(),
+                  ada::unicode::is_forbidden_domain_code_point)) {
+    host = std::nullopt;
+    return is_valid = false;
+  }
+
+  // If asciiDomain ends in a number, then return the result of IPv4 parsing
+  // asciiDomain.
+  if (checkers::is_ipv4(host.value())) {
+    ada_log("parse_host got ipv4 ", *host);
+    return parse_ipv4(host.value());
+  }
+
+  return true;
+}
+
+ada_really_inline void url::parse_path(std::string_view input) {
+  ada_log("parse_path ", input);
+  std::string tmp_buffer;
+  std::string_view internal_input;
+  if (unicode::has_tabs_or_newline(input)) {
+    tmp_buffer = input;
+    // Optimization opportunity: Instead of copying and then pruning, we could
+    // just directly build the string from user_input.
+    helpers::remove_ascii_tab_or_newline(tmp_buffer);
+    internal_input = tmp_buffer;
+  } else {
+    internal_input = input;
+  }
+
+  // If url is special, then:
+  if (is_special()) {
+    if (internal_input.empty()) {
+      path = "/";
+    } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) {
+      helpers::parse_prepared_path(internal_input.substr(1), type, path);
+      return;
+    } else {
+      helpers::parse_prepared_path(internal_input, type, path);
+      return;
+    }
+  } else if (!internal_input.empty()) {
+    if (internal_input[0] == '/') {
+      helpers::parse_prepared_path(internal_input.substr(1), type, path);
+      return;
+    } else {
+      helpers::parse_prepared_path(internal_input, type, path);
+      return;
+    }
+  } else {
+    if (!host.has_value()) {
+      path = "/";
+    }
+  }
+}
+
+[[nodiscard]] std::string url::to_string() const {
+  if (!is_valid) {
+    return "null";
+  }
+  std::string answer;
+  auto back = std::back_insert_iterator(answer);
+  answer.append("{\n");
+  answer.append("\t\"protocol\":\"");
+  helpers::encode_json(get_protocol(), back);
+  answer.append("\",\n");
+  if (has_credentials()) {
+    answer.append("\t\"username\":\"");
+    helpers::encode_json(username, back);
+    answer.append("\",\n");
+    answer.append("\t\"password\":\"");
+    helpers::encode_json(password, back);
+    answer.append("\",\n");
+  }
+  if (host.has_value()) {
+    answer.append("\t\"host\":\"");
+    helpers::encode_json(host.value(), back);
+    answer.append("\",\n");
+  }
+  if (port.has_value()) {
+    answer.append("\t\"port\":\"");
+    answer.append(std::to_string(port.value()));
+    answer.append("\",\n");
+  }
+  answer.append("\t\"path\":\"");
+  helpers::encode_json(path, back);
+  answer.append("\",\n");
+  answer.append("\t\"opaque path\":");
+  answer.append((has_opaque_path ? "true" : "false"));
+  if (has_search()) {
+    answer.append(",\n");
+    answer.append("\t\"query\":\"");
+    helpers::encode_json(query.value(), back);
+    answer.append("\"");
+  }
+  if (hash.has_value()) {
+    answer.append(",\n");
+    answer.append("\t\"hash\":\"");
+    helpers::encode_json(hash.value(), back);
+    answer.append("\"");
+  }
+  answer.append("\n}");
+  return answer;
+}
+
+[[nodiscard]] bool url::has_valid_domain() const noexcept {
+  if (!host.has_value()) {
+    return false;
+  }
+  return checkers::verify_dns_length(host.value());
+}
+
+}  // namespace ada
diff --git a/src/url_aggregator.cpp b/src/url_aggregator.cpp
new file mode 100644 (file)
index 0000000..c1d06bc
--- /dev/null
@@ -0,0 +1,1732 @@
+#include "ada.h"
+#include "ada/checkers-inl.h"
+#include "ada/checkers.h"
+#include "ada/helpers.h"
+#include "ada/implementation.h"
+#include "ada/scheme.h"
+#include "ada/unicode-inl.h"
+#include "ada/url_components.h"
+#include "ada/url_aggregator.h"
+#include "ada/url_aggregator-inl.h"
+#include "ada/parser.h"
+
+#include <string>
+#include <string_view>
+
+namespace ada {
+template <bool has_state_override>
+[[nodiscard]] ada_really_inline bool url_aggregator::parse_scheme_with_colon(
+    const std::string_view input_with_colon) {
+  ada_log("url_aggregator::parse_scheme_with_colon ", input_with_colon);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input_with_colon, buffer));
+  std::string_view input{input_with_colon};
+  input.remove_suffix(1);
+  auto parsed_type = ada::scheme::get_scheme_type(input);
+  bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL);
+  /**
+   * In the common case, we will immediately recognize a special scheme (e.g.,
+   *http, https), in which case, we can go really fast.
+   **/
+  if (is_input_special) {  // fast path!!!
+    if (has_state_override) {
+      // If url's scheme is not a special scheme and buffer is a special scheme,
+      // then return.
+      if (is_special() != is_input_special) {
+        return true;
+      }
+
+      // If url includes credentials or has a non-null port, and buffer is
+      // "file", then return.
+      if ((has_credentials() || components.port != url_components::omitted) &&
+          parsed_type == ada::scheme::type::FILE) {
+        return true;
+      }
+
+      // If url's scheme is "file" and its host is an empty host, then return.
+      // An empty host is the empty string.
+      if (type == ada::scheme::type::FILE &&
+          components.host_start == components.host_end) {
+        return true;
+      }
+    }
+
+    type = parsed_type;
+    set_scheme_from_view_with_colon(input_with_colon);
+
+    if (has_state_override) {
+      // This is uncommon.
+      uint16_t urls_scheme_port = get_special_port();
+
+      // If url's port is url's scheme's default port, then set url's port to
+      // null.
+      if (components.port == urls_scheme_port) {
+        clear_port();
+      }
+    }
+  } else {  // slow path
+    std::string _buffer(input);
+    // Next function is only valid if the input is ASCII and returns false
+    // otherwise, but it seems that we always have ascii content so we do not
+    // need to check the return value.
+    unicode::to_lower_ascii(_buffer.data(), _buffer.size());
+
+    if (has_state_override) {
+      // If url's scheme is a special scheme and buffer is not a special scheme,
+      // then return. If url's scheme is not a special scheme and buffer is a
+      // special scheme, then return.
+      if (is_special() != ada::scheme::is_special(_buffer)) {
+        return true;
+      }
+
+      // If url includes credentials or has a non-null port, and buffer is
+      // "file", then return.
+      if ((has_credentials() || components.port != url_components::omitted) &&
+          _buffer == "file") {
+        return true;
+      }
+
+      // If url's scheme is "file" and its host is an empty host, then return.
+      // An empty host is the empty string.
+      if (type == ada::scheme::type::FILE &&
+          components.host_start == components.host_end) {
+        return true;
+      }
+    }
+
+    set_scheme(_buffer);
+
+    if (has_state_override) {
+      // This is uncommon.
+      uint16_t urls_scheme_port = get_special_port();
+
+      // If url's port is url's scheme's default port, then set url's port to
+      // null.
+      if (components.port == urls_scheme_port) {
+        clear_port();
+      }
+    }
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+inline void url_aggregator::copy_scheme(const url_aggregator& u) noexcept {
+  ada_log("url_aggregator::copy_scheme ", u.buffer);
+  ADA_ASSERT_TRUE(validate());
+  // next line could overflow but unsigned arithmetic has well-defined
+  // overflows.
+  uint32_t new_difference = u.components.protocol_end - components.protocol_end;
+  type = u.type;
+  buffer.erase(0, components.protocol_end);
+  buffer.insert(0, u.get_protocol());
+  components.protocol_end = u.components.protocol_end;
+
+  // No need to update the components
+  if (new_difference == 0) {
+    return;
+  }
+
+  // Update the rest of the components.
+  components.username_end += new_difference;
+  components.host_start += new_difference;
+  components.host_end += new_difference;
+  components.pathname_start += new_difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += new_difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += new_difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::set_scheme_from_view_with_colon(
+    std::string_view new_scheme_with_colon) noexcept {
+  ada_log("url_aggregator::set_scheme_from_view_with_colon ",
+          new_scheme_with_colon);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!new_scheme_with_colon.empty() &&
+                  new_scheme_with_colon.back() == ':');
+  // next line could overflow but unsigned arithmetic has well-defined
+  // overflows.
+  uint32_t new_difference =
+      uint32_t(new_scheme_with_colon.size()) - components.protocol_end;
+
+  if (buffer.empty()) {
+    buffer.append(new_scheme_with_colon);
+  } else {
+    buffer.erase(0, components.protocol_end);
+    buffer.insert(0, new_scheme_with_colon);
+  }
+  components.protocol_end += new_difference;
+
+  // Update the rest of the components.
+  components.username_end += new_difference;
+  components.host_start += new_difference;
+  components.host_end += new_difference;
+  components.pathname_start += new_difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += new_difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += new_difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+inline void url_aggregator::set_scheme(std::string_view new_scheme) noexcept {
+  ada_log("url_aggregator::set_scheme ", new_scheme);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(new_scheme.empty() || new_scheme.back() != ':');
+  // next line could overflow but unsigned arithmetic has well-defined
+  // overflows.
+  uint32_t new_difference =
+      uint32_t(new_scheme.size()) - components.protocol_end + 1;
+
+  type = ada::scheme::get_scheme_type(new_scheme);
+  if (buffer.empty()) {
+    buffer.append(helpers::concat(new_scheme, ":"));
+  } else {
+    buffer.erase(0, components.protocol_end);
+    buffer.insert(0, helpers::concat(new_scheme, ":"));
+  }
+  components.protocol_end = uint32_t(new_scheme.size() + 1);
+
+  // Update the rest of the components.
+  components.username_end += new_difference;
+  components.host_start += new_difference;
+  components.host_end += new_difference;
+  components.pathname_start += new_difference;
+  if (components.search_start != url_components::omitted) {
+    components.search_start += new_difference;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start += new_difference;
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+bool url_aggregator::set_protocol(const std::string_view input) {
+  ada_log("url_aggregator::set_protocol ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  std::string view(input);
+  helpers::remove_ascii_tab_or_newline(view);
+  if (view.empty()) {
+    return true;
+  }
+
+  // Schemes should start with alpha values.
+  if (!checkers::is_alpha(view[0])) {
+    return false;
+  }
+
+  view.append(":");
+
+  std::string::iterator pointer =
+      std::find_if_not(view.begin(), view.end(), unicode::is_alnum_plus);
+
+  if (pointer != view.end() && *pointer == ':') {
+    return parse_scheme_with_colon<true>(
+        std::string_view(view.data(), pointer - view.begin() + 1));
+  }
+  return false;
+}
+
+bool url_aggregator::set_username(const std::string_view input) {
+  ada_log("url_aggregator::set_username '", input, "' ");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  size_t idx = ada::unicode::percent_encode_index(
+      input, character_sets::USERINFO_PERCENT_ENCODE);
+  if (idx == input.size()) {
+    update_base_username(input);
+  } else {
+    // We only create a temporary string if we have to!
+    update_base_username(ada::unicode::percent_encode(
+        input, character_sets::USERINFO_PERCENT_ENCODE, idx));
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+bool url_aggregator::set_password(const std::string_view input) {
+  ada_log("url_aggregator::set_password '", input, "'");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  size_t idx = ada::unicode::percent_encode_index(
+      input, character_sets::USERINFO_PERCENT_ENCODE);
+  if (idx == input.size()) {
+    update_base_password(input);
+  } else {
+    // We only create a temporary string if we have to!
+    update_base_password(ada::unicode::percent_encode(
+        input, character_sets::USERINFO_PERCENT_ENCODE, idx));
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+bool url_aggregator::set_port(const std::string_view input) {
+  ada_log("url_aggregator::set_port ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (cannot_have_credentials_or_port()) {
+    return false;
+  }
+  std::string trimmed(input);
+  helpers::remove_ascii_tab_or_newline(trimmed);
+  if (trimmed.empty()) {
+    clear_port();
+    return true;
+  }
+  // Input should not start with control characters.
+  if (ada::unicode::is_c0_control_or_space(trimmed.front())) {
+    return false;
+  }
+  // Input should contain at least one ascii digit.
+  if (input.find_first_of("0123456789") == std::string_view::npos) {
+    return false;
+  }
+
+  // Revert changes if parse_port fails.
+  uint32_t previous_port = components.port;
+  parse_port(trimmed);
+  if (is_valid) {
+    return true;
+  }
+  update_base_port(previous_port);
+  is_valid = true;
+  ADA_ASSERT_TRUE(validate());
+  return false;
+}
+
+bool url_aggregator::set_pathname(const std::string_view input) {
+  ada_log("url_aggregator::set_pathname ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (has_opaque_path) {
+    return false;
+  }
+  clear_pathname();
+  parse_path(input);
+  if (checkers::begins_with(input, "//") && !has_authority() &&
+      !has_dash_dot()) {
+    buffer.insert(components.pathname_start, "/.");
+    components.pathname_start += 2;
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+ada_really_inline void url_aggregator::parse_path(std::string_view input) {
+  ada_log("url_aggregator::parse_path ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  std::string tmp_buffer;
+  std::string_view internal_input;
+  if (unicode::has_tabs_or_newline(input)) {
+    tmp_buffer = input;
+    // Optimization opportunity: Instead of copying and then pruning, we could
+    // just directly build the string from user_input.
+    helpers::remove_ascii_tab_or_newline(tmp_buffer);
+    internal_input = tmp_buffer;
+  } else {
+    internal_input = input;
+  }
+
+  // If url is special, then:
+  if (is_special()) {
+    if (internal_input.empty()) {
+      update_base_pathname("/");
+    } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) {
+      consume_prepared_path(internal_input.substr(1));
+    } else {
+      consume_prepared_path(internal_input);
+    }
+  } else if (!internal_input.empty()) {
+    if (internal_input[0] == '/') {
+      consume_prepared_path(internal_input.substr(1));
+    } else {
+      consume_prepared_path(internal_input);
+    }
+  } else {
+    // Non-special URLs with an empty host can have their paths erased
+    // Path-only URLs cannot have their paths erased
+    if (components.host_start == components.host_end && !has_authority()) {
+      update_base_pathname("/");
+    }
+  }
+  ADA_ASSERT_TRUE(validate());
+}
+
+void url_aggregator::set_search(const std::string_view input) {
+  ada_log("url_aggregator::set_search ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (input.empty()) {
+    clear_search();
+    helpers::strip_trailing_spaces_from_opaque_path(*this);
+    return;
+  }
+
+  std::string new_value;
+  new_value = input[0] == '?' ? input.substr(1) : input;
+  helpers::remove_ascii_tab_or_newline(new_value);
+
+  auto query_percent_encode_set =
+      is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE
+                   : ada::character_sets::QUERY_PERCENT_ENCODE;
+
+  update_base_search(new_value, query_percent_encode_set);
+  ADA_ASSERT_TRUE(validate());
+}
+
+void url_aggregator::set_hash(const std::string_view input) {
+  ada_log("url_aggregator::set_hash ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (input.empty()) {
+    if (components.hash_start != url_components::omitted) {
+      buffer.resize(components.hash_start);
+      components.hash_start = url_components::omitted;
+    }
+    helpers::strip_trailing_spaces_from_opaque_path(*this);
+    return;
+  }
+
+  std::string new_value;
+  new_value = input[0] == '#' ? input.substr(1) : input;
+  helpers::remove_ascii_tab_or_newline(new_value);
+  update_unencoded_base_hash(new_value);
+  ADA_ASSERT_TRUE(validate());
+}
+
+bool url_aggregator::set_href(const std::string_view input) {
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  ada_log("url_aggregator::set_href ", input, " [", input.size(), " bytes]");
+  ada::result<url_aggregator> out = ada::parse<url_aggregator>(input);
+  ada_log("url_aggregator::set_href, success :", out.has_value());
+
+  if (out) {
+    ada_log("url_aggregator::set_href, parsed ", out->to_string());
+    // TODO: Figure out why the following line puts test to never finish.
+    *this = *out;
+  }
+
+  return out.has_value();
+}
+
+ada_really_inline bool url_aggregator::parse_host(std::string_view input) {
+  ada_log("url_aggregator:parse_host \"", input, "\" [", input.size(),
+          " bytes]");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (input.empty()) {
+    return is_valid = false;
+  }  // technically unnecessary.
+  // If input starts with U+005B ([), then:
+  if (input[0] == '[') {
+    // If input does not end with U+005D (]), validation error, return failure.
+    if (input.back() != ']') {
+      return is_valid = false;
+    }
+    ada_log("parse_host ipv6");
+
+    // Return the result of IPv6 parsing input with its leading U+005B ([) and
+    // trailing U+005D (]) removed.
+    input.remove_prefix(1);
+    input.remove_suffix(1);
+    return parse_ipv6(input);
+  }
+
+  // If isNotSpecial is true, then return the result of opaque-host parsing
+  // input.
+  if (!is_special()) {
+    return parse_opaque_host(input);
+  }
+  // Let domain be the result of running UTF-8 decode without BOM on the
+  // percent-decoding of input. Let asciiDomain be the result of running domain
+  // to ASCII with domain and false. The most common case is an ASCII input, in
+  // which case we do not need to call the expensive 'to_ascii' if a few
+  // conditions are met: no '%' and no 'xn-' subsequence.
+
+  // Often, the input does not contain any forbidden code points, and no upper
+  // case ASCII letter, then we can just copy it to the buffer. We want to
+  // optimize for such a common case.
+  uint8_t is_forbidden_or_upper =
+      unicode::contains_forbidden_domain_code_point_or_upper(input.data(),
+                                                             input.size());
+  // Minor optimization opportunity:
+  // contains_forbidden_domain_code_point_or_upper could be extend to check for
+  // the presence of characters that cannot appear in the ipv4 address and we
+  // could also check whether x and n and - are present, and so we could skip
+  // some of the checks below. However, the gains are likely to be small, and
+  // the code would be more complex.
+  if (is_forbidden_or_upper == 0 &&
+      input.find("xn-") == std::string_view::npos) {
+    // fast path
+    update_base_hostname(input);
+    if (checkers::is_ipv4(get_hostname())) {
+      ada_log("parse_host fast path ipv4");
+      return parse_ipv4(get_hostname(), true);
+    }
+    ada_log("parse_host fast path ", get_hostname());
+    return true;
+  }
+  // We have encountered at least one forbidden code point or the input contains
+  // 'xn-' (case insensitive), so we need to call 'to_ascii' to perform the full
+  // conversion.
+
+  ada_log("parse_host calling to_ascii");
+  std::optional<std::string> host = std::string(get_hostname());
+  is_valid = ada::unicode::to_ascii(host, input, input.find('%'));
+  if (!is_valid) {
+    ada_log("parse_host to_ascii returns false");
+    return is_valid = false;
+  }
+  ada_log("parse_host to_ascii succeeded ", *host, " [", host->size(),
+          " bytes]");
+
+  if (std::any_of(host.value().begin(), host.value().end(),
+                  ada::unicode::is_forbidden_domain_code_point)) {
+    return is_valid = false;
+  }
+
+  // If asciiDomain ends in a number, then return the result of IPv4 parsing
+  // asciiDomain.
+  if (checkers::is_ipv4(host.value())) {
+    ada_log("parse_host got ipv4 ", *host);
+    return parse_ipv4(host.value(), false);
+  }
+
+  update_base_hostname(host.value());
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+template <bool override_hostname>
+bool url_aggregator::set_host_or_hostname(const std::string_view input) {
+  ada_log("url_aggregator::set_host_or_hostname ", input);
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (has_opaque_path) {
+    return false;
+  }
+
+  std::string previous_host(get_hostname());
+  uint32_t previous_port = components.port;
+
+  size_t host_end_pos = input.find('#');
+  std::string _host(input.data(), host_end_pos != std::string_view::npos
+                                      ? host_end_pos
+                                      : input.size());
+  helpers::remove_ascii_tab_or_newline(_host);
+  std::string_view new_host(_host);
+
+  // If url's scheme is "file", then set state to file host state, instead of
+  // host state.
+  if (type != ada::scheme::type::FILE) {
+    std::string_view host_view(_host.data(), _host.length());
+    auto [location, found_colon] =
+        helpers::get_host_delimiter_location(is_special(), host_view);
+
+    // Otherwise, if c is U+003A (:) and insideBrackets is false, then:
+    // Note: the 'found_colon' value is true if and only if a colon was
+    // encountered while not inside brackets.
+    if (found_colon) {
+      if (override_hostname) {
+        return false;
+      }
+      std::string_view sub_buffer = new_host.substr(location + 1);
+      if (!sub_buffer.empty()) {
+        set_port(sub_buffer);
+      }
+    }
+    // If url is special and host_view is the empty string, validation error,
+    // return failure. Otherwise, if state override is given, host_view is the
+    // empty string, and either url includes credentials or url's port is
+    // non-null, return.
+    else if (host_view.empty() &&
+             (is_special() || has_credentials() || has_port())) {
+      return false;
+    }
+
+    // Let host be the result of host parsing host_view with url is not special.
+    if (host_view.empty() && !is_special()) {
+      if (has_hostname()) {
+        clear_hostname();  // easy!
+      } else if (has_dash_dot()) {
+        add_authority_slashes_if_needed();
+        delete_dash_dot();
+      }
+      return true;
+    }
+
+    bool succeeded = parse_host(host_view);
+    if (!succeeded) {
+      update_base_hostname(previous_host);
+      update_base_port(previous_port);
+    } else if (has_dash_dot()) {
+      // Should remove dash_dot from pathname
+      delete_dash_dot();
+    }
+    return succeeded;
+  }
+
+  size_t location = new_host.find_first_of("/\\?");
+  if (location != std::string_view::npos) {
+    new_host.remove_suffix(new_host.length() - location);
+  }
+
+  if (new_host.empty()) {
+    // Set url's host to the empty string.
+    clear_hostname();
+  } else {
+    // Let host be the result of host parsing buffer with url is not special.
+    if (!parse_host(new_host)) {
+      update_base_hostname(previous_host);
+      update_base_port(previous_port);
+      return false;
+    }
+
+    // If host is "localhost", then set host to the empty string.
+    if (helpers::substring(buffer, components.host_start,
+                           components.host_end) == "localhost") {
+      clear_hostname();
+    }
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+bool url_aggregator::set_host(const std::string_view input) {
+  ada_log("url_aggregator::set_host '", input, "'");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  return set_host_or_hostname<false>(input);
+}
+
+bool url_aggregator::set_hostname(const std::string_view input) {
+  ada_log("url_aggregator::set_hostname '", input, "'");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  return set_host_or_hostname<true>(input);
+}
+
+[[nodiscard]] std::string url_aggregator::get_origin() const noexcept {
+  ada_log("url_aggregator::get_origin");
+  if (is_special()) {
+    // Return a new opaque origin.
+    if (type == scheme::FILE) {
+      return "null";
+    }
+
+    return helpers::concat(get_protocol(), "//", get_host());
+  }
+
+  if (get_protocol() == "blob:") {
+    std::string_view path = get_pathname();
+    if (!path.empty()) {
+      auto out = ada::parse<ada::url_aggregator>(path);
+      if (out && (out->type == scheme::HTTP || out->type == scheme::HTTPS)) {
+        // If pathURL's scheme is not "http" and not "https", then return a
+        // new opaque origin.
+        return helpers::concat(out->get_protocol(), "//", out->get_host());
+      }
+    }
+  }
+
+  // Return a new opaque origin.
+  return "null";
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_username() const noexcept {
+  ada_log("url_aggregator::get_username");
+  if (has_non_empty_username()) {
+    return helpers::substring(buffer, components.protocol_end + 2,
+                              components.username_end);
+  }
+  return "";
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_password() const noexcept {
+  ada_log("url_aggregator::get_password");
+  if (has_non_empty_password()) {
+    return helpers::substring(buffer, components.username_end + 1,
+                              components.host_start);
+  }
+  return "";
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_port() const noexcept {
+  ada_log("url_aggregator::get_port");
+  if (components.port == url_components::omitted) {
+    return "";
+  }
+  return helpers::substring(buffer, components.host_end + 1,
+                            components.pathname_start);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_hash() const noexcept {
+  ada_log("url_aggregator::get_hash");
+  // If this's URL's fragment is either null or the empty string, then return
+  // the empty string. Return U+0023 (#), followed by this's URL's fragment.
+  if (components.hash_start == url_components::omitted) {
+    return "";
+  }
+  if (buffer.size() - components.hash_start <= 1) {
+    return "";
+  }
+  return helpers::substring(buffer, components.hash_start);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_host() const noexcept {
+  ada_log("url_aggregator::get_host");
+  // Technically, we should check if there is a hostname, but
+  // the code below works even if there isn't.
+  // if(!has_hostname()) { return ""; }
+  size_t start = components.host_start;
+  if (components.host_end > components.host_start &&
+      buffer[components.host_start] == '@') {
+    start++;
+  }
+  // if we have an empty host, then the space between components.host_end and
+  // components.pathname_start may be occupied by /.
+  if (start == components.host_end) {
+    return std::string_view();
+  }
+  return helpers::substring(buffer, start, components.pathname_start);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_hostname() const noexcept {
+  ada_log("url_aggregator::get_hostname");
+  // Technically, we should check if there is a hostname, but
+  // the code below works even if there isn't.
+  // if(!has_hostname()) { return ""; }
+  size_t start = components.host_start;
+  // So host_start is not where the host begins.
+  if (components.host_end > components.host_start &&
+      buffer[components.host_start] == '@') {
+    start++;
+  }
+  return helpers::substring(buffer, start, components.host_end);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_pathname() const noexcept {
+  ada_log("url_aggregator::get_pathname pathname_start = ",
+          components.pathname_start, " buffer.size() = ", buffer.size(),
+          " components.search_start = ", components.search_start,
+          " components.hash_start = ", components.hash_start);
+  uint32_t ending_index = uint32_t(buffer.size());
+  if (components.search_start != url_components::omitted) {
+    ending_index = components.search_start;
+  } else if (components.hash_start != url_components::omitted) {
+    ending_index = components.hash_start;
+  }
+  return helpers::substring(buffer, components.pathname_start, ending_index);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_search() const noexcept {
+  ada_log("url_aggregator::get_search");
+  // If this's URL's query is either null or the empty string, then return the
+  // empty string. Return U+003F (?), followed by this's URL's query.
+  if (components.search_start == url_components::omitted) {
+    return "";
+  }
+  uint32_t ending_index = uint32_t(buffer.size());
+  if (components.hash_start != url_components::omitted) {
+    ending_index = components.hash_start;
+  }
+  if (ending_index - components.search_start <= 1) {
+    return "";
+  }
+  return helpers::substring(buffer, components.search_start, ending_index);
+}
+
+[[nodiscard]] std::string_view url_aggregator::get_protocol() const noexcept {
+  ada_log("url_aggregator::get_protocol");
+  return helpers::substring(buffer, 0, components.protocol_end);
+}
+
+[[nodiscard]] std::string ada::url_aggregator::to_string() const {
+  ada_log("url_aggregator::to_string buffer:", buffer, " [", buffer.size(),
+          " bytes]");
+  if (!is_valid) {
+    return "null";
+  }
+
+  std::string answer;
+  auto back = std::back_insert_iterator(answer);
+  answer.append("{\n");
+
+  answer.append("\t\"buffer\":\"");
+  helpers::encode_json(buffer, back);
+  answer.append("\",\n");
+
+  answer.append("\t\"protocol\":\"");
+  helpers::encode_json(get_protocol(), back);
+  answer.append("\",\n");
+
+  if (has_credentials()) {
+    answer.append("\t\"username\":\"");
+    helpers::encode_json(get_username(), back);
+    answer.append("\",\n");
+    answer.append("\t\"password\":\"");
+    helpers::encode_json(get_password(), back);
+    answer.append("\",\n");
+  }
+
+  answer.append("\t\"host\":\"");
+  helpers::encode_json(get_host(), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"path\":\"");
+  helpers::encode_json(get_pathname(), back);
+  answer.append("\",\n");
+  answer.append("\t\"opaque path\":");
+  answer.append((has_opaque_path ? "true" : "false"));
+  answer.append(",\n");
+
+  if (components.search_start != url_components::omitted) {
+    answer.append("\t\"query\":\"");
+    helpers::encode_json(get_search(), back);
+    answer.append("\",\n");
+  }
+  if (components.hash_start != url_components::omitted) {
+    answer.append("\t\"fragment\":\"");
+    helpers::encode_json(get_hash(), back);
+    answer.append("\",\n");
+  }
+
+  auto convert_offset_to_string = [](uint32_t offset) -> std::string {
+    if (offset == url_components::omitted) {
+      return "null";
+    } else {
+      return std::to_string(offset);
+    }
+  };
+
+  answer.append("\t\"protocol_end\":");
+  answer.append(convert_offset_to_string(components.protocol_end));
+  answer.append(",\n");
+
+  answer.append("\t\"username_end\":");
+  answer.append(convert_offset_to_string(components.username_end));
+  answer.append(",\n");
+
+  answer.append("\t\"host_start\":");
+  answer.append(convert_offset_to_string(components.host_start));
+  answer.append(",\n");
+
+  answer.append("\t\"host_end\":");
+  answer.append(convert_offset_to_string(components.host_end));
+  answer.append(",\n");
+
+  answer.append("\t\"port\":");
+  answer.append(convert_offset_to_string(components.port));
+  answer.append(",\n");
+
+  answer.append("\t\"pathname_start\":");
+  answer.append(convert_offset_to_string(components.pathname_start));
+  answer.append(",\n");
+
+  answer.append("\t\"search_start\":");
+  answer.append(convert_offset_to_string(components.search_start));
+  answer.append(",\n");
+
+  answer.append("\t\"hash_start\":");
+  answer.append(convert_offset_to_string(components.hash_start));
+  answer.append("\n}");
+
+  return answer;
+}
+
+[[nodiscard]] bool url_aggregator::has_valid_domain() const noexcept {
+  if (components.host_start == components.host_end) {
+    return false;
+  }
+  return checkers::verify_dns_length(get_hostname());
+}
+
+bool url_aggregator::parse_ipv4(std::string_view input, bool in_place) {
+  ada_log("parse_ipv4 ", input, " [", input.size(),
+          " bytes], overlaps with buffer: ",
+          helpers::overlaps(input, buffer) ? "yes" : "no");
+  ADA_ASSERT_TRUE(validate());
+  const bool trailing_dot = (input.back() == '.');
+  if (trailing_dot) {
+    input.remove_suffix(1);
+  }
+  size_t digit_count{0};
+  int pure_decimal_count = 0;  // entries that are decimal
+  uint64_t ipv4{0};
+  // we could unroll for better performance?
+  for (; (digit_count < 4) && !(input.empty()); digit_count++) {
+    uint32_t
+        segment_result{};  // If any number exceeds 32 bits, we have an error.
+    bool is_hex = checkers::has_hex_prefix(input);
+    if (is_hex && ((input.length() == 2) ||
+                   ((input.length() > 2) && (input[2] == '.')))) {
+      // special case
+      segment_result = 0;
+      input.remove_prefix(2);
+    } else {
+      std::from_chars_result r;
+      if (is_hex) {
+        ada_log("parse_ipv4 trying to parse hex number");
+        r = std::from_chars(input.data() + 2, input.data() + input.size(),
+                            segment_result, 16);
+      } else if ((input.length() >= 2) && input[0] == '0' &&
+                 checkers::is_digit(input[1])) {
+        ada_log("parse_ipv4 trying to parse octal number");
+        r = std::from_chars(input.data() + 1, input.data() + input.size(),
+                            segment_result, 8);
+      } else {
+        ada_log("parse_ipv4 trying to parse decimal number");
+        pure_decimal_count++;
+        r = std::from_chars(input.data(), input.data() + input.size(),
+                            segment_result, 10);
+      }
+      if (r.ec != std::errc()) {
+        ada_log("parse_ipv4 parsing failed");
+        return is_valid = false;
+      }
+      ada_log("parse_ipv4 parsed ", segment_result);
+      input.remove_prefix(r.ptr - input.data());
+    }
+    if (input.empty()) {
+      // We have the last value.
+      // At this stage, ipv4 contains digit_count*8 bits.
+      // So we have 32-digit_count*8 bits left.
+      if (segment_result >= (uint64_t(1) << (32 - digit_count * 8))) {
+        return is_valid = false;
+      }
+      ipv4 <<= (32 - digit_count * 8);
+      ipv4 |= segment_result;
+      goto final;
+    } else {
+      // There is more, so that the value must no be larger than 255
+      // and we must have a '.'.
+      if ((segment_result > 255) || (input[0] != '.')) {
+        return is_valid = false;
+      }
+      ipv4 <<= 8;
+      ipv4 |= segment_result;
+      input.remove_prefix(1);  // remove '.'
+    }
+  }
+  if ((digit_count != 4) || (!input.empty())) {
+    ada_log("parse_ipv4 found invalid (more than 4 numbers or empty) ");
+    return is_valid = false;
+  }
+final:
+  ada_log("url_aggregator::parse_ipv4 completed ", get_href(),
+          " host: ", get_host());
+
+  // We could also check r.ptr to see where the parsing ended.
+  if (in_place && pure_decimal_count == 4 && !trailing_dot) {
+    ada_log(
+        "url_aggregator::parse_ipv4 completed and was already correct in the "
+        "buffer");
+    // The original input was already all decimal and we validated it. So we
+    // don't need to do anything.
+  } else {
+    ada_log("url_aggregator::parse_ipv4 completed and we need to update it");
+    // Optimization opportunity: Get rid of unnecessary string return in ipv4
+    // serializer.
+    // TODO: This is likely a bug because it goes back update_base_hostname, not
+    // what we want to do.
+    update_base_hostname(
+        ada::serializers::ipv4(ipv4));  // We have to reserialize the address.
+  }
+  host_type = IPV4;
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+bool url_aggregator::parse_ipv6(std::string_view input) {
+  // TODO: Implement in_place optimization: we know that input points
+  // in the buffer, so we can just check whether the buffer is already
+  // well formatted.
+  // TODO: Find a way to merge parse_ipv6 with url.cpp implementation.
+  ada_log("parse_ipv6 ", input, " [", input.size(), " bytes]");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (input.empty()) {
+    return is_valid = false;
+  }
+  // Let address be a new IPv6 address whose IPv6 pieces are all 0.
+  std::array<uint16_t, 8> address{};
+
+  // Let pieceIndex be 0.
+  int piece_index = 0;
+
+  // Let compress be null.
+  std::optional<int> compress{};
+
+  // Let pointer be a pointer for input.
+  std::string_view::iterator pointer = input.begin();
+
+  // If c is U+003A (:), then:
+  if (input[0] == ':') {
+    // If remaining does not start with U+003A (:), validation error, return
+    // failure.
+    if (input.size() == 1 || input[1] != ':') {
+      ada_log("parse_ipv6 starts with : but the rest does not start with :");
+      return is_valid = false;
+    }
+
+    // Increase pointer by 2.
+    pointer += 2;
+
+    // Increase pieceIndex by 1 and then set compress to pieceIndex.
+    compress = ++piece_index;
+  }
+
+  // While c is not the EOF code point:
+  while (pointer != input.end()) {
+    // If pieceIndex is 8, validation error, return failure.
+    if (piece_index == 8) {
+      ada_log("parse_ipv6 piece_index == 8");
+      return is_valid = false;
+    }
+
+    // If c is U+003A (:), then:
+    if (*pointer == ':') {
+      // If compress is non-null, validation error, return failure.
+      if (compress.has_value()) {
+        ada_log("parse_ipv6 compress is non-null");
+        return is_valid = false;
+      }
+
+      // Increase pointer and pieceIndex by 1, set compress to pieceIndex, and
+      // then continue.
+      pointer++;
+      compress = ++piece_index;
+      continue;
+    }
+
+    // Let value and length be 0.
+    uint16_t value = 0, length = 0;
+
+    // While length is less than 4 and c is an ASCII hex digit,
+    // set value to value times 0x10 + c interpreted as hexadecimal number, and
+    // increase pointer and length by 1.
+    while (length < 4 && pointer != input.end() &&
+           unicode::is_ascii_hex_digit(*pointer)) {
+      // https://stackoverflow.com/questions/39060852/why-does-the-addition-of-two-shorts-return-an-int
+      value = uint16_t(value * 0x10 + unicode::convert_hex_to_binary(*pointer));
+      pointer++;
+      length++;
+    }
+
+    // If c is U+002E (.), then:
+    if (pointer != input.end() && *pointer == '.') {
+      // If length is 0, validation error, return failure.
+      if (length == 0) {
+        ada_log("parse_ipv6 length is 0");
+        return is_valid = false;
+      }
+
+      // Decrease pointer by length.
+      pointer -= length;
+
+      // If pieceIndex is greater than 6, validation error, return failure.
+      if (piece_index > 6) {
+        ada_log("parse_ipv6 piece_index > 6");
+        return is_valid = false;
+      }
+
+      // Let numbersSeen be 0.
+      int numbers_seen = 0;
+
+      // While c is not the EOF code point:
+      while (pointer != input.end()) {
+        // Let ipv4Piece be null.
+        std::optional<uint16_t> ipv4_piece{};
+
+        // If numbersSeen is greater than 0, then:
+        if (numbers_seen > 0) {
+          // If c is a U+002E (.) and numbersSeen is less than 4, then increase
+          // pointer by 1.
+          if (*pointer == '.' && numbers_seen < 4) {
+            pointer++;
+          } else {
+            // Otherwise, validation error, return failure.
+            ada_log("parse_ipv6 Otherwise, validation error, return failure");
+            return is_valid = false;
+          }
+        }
+
+        // If c is not an ASCII digit, validation error, return failure.
+        if (pointer == input.end() || !checkers::is_digit(*pointer)) {
+          ada_log(
+              "parse_ipv6 If c is not an ASCII digit, validation error, return "
+              "failure");
+          return is_valid = false;
+        }
+
+        // While c is an ASCII digit:
+        while (pointer != input.end() && checkers::is_digit(*pointer)) {
+          // Let number be c interpreted as decimal number.
+          int number = *pointer - '0';
+
+          // If ipv4Piece is null, then set ipv4Piece to number.
+          if (!ipv4_piece.has_value()) {
+            ipv4_piece = number;
+          }
+          // Otherwise, if ipv4Piece is 0, validation error, return failure.
+          else if (ipv4_piece == 0) {
+            ada_log("parse_ipv6 if ipv4Piece is 0, validation error");
+            return is_valid = false;
+          }
+          // Otherwise, set ipv4Piece to ipv4Piece times 10 + number.
+          else {
+            ipv4_piece = *ipv4_piece * 10 + number;
+          }
+
+          // If ipv4Piece is greater than 255, validation error, return failure.
+          if (ipv4_piece > 255) {
+            ada_log("parse_ipv6 ipv4_piece > 255");
+            return is_valid = false;
+          }
+
+          // Increase pointer by 1.
+          pointer++;
+        }
+
+        // Set address[pieceIndex] to address[pieceIndex] times 0x100 +
+        // ipv4Piece.
+        // https://stackoverflow.com/questions/39060852/why-does-the-addition-of-two-shorts-return-an-int
+        address[piece_index] =
+            uint16_t(address[piece_index] * 0x100 + *ipv4_piece);
+
+        // Increase numbersSeen by 1.
+        numbers_seen++;
+
+        // If numbersSeen is 2 or 4, then increase pieceIndex by 1.
+        if (numbers_seen == 2 || numbers_seen == 4) {
+          piece_index++;
+        }
+      }
+
+      // If numbersSeen is not 4, validation error, return failure.
+      if (numbers_seen != 4) {
+        return is_valid = false;
+      }
+
+      // Break.
+      break;
+    }
+    // Otherwise, if c is U+003A (:):
+    else if ((pointer != input.end()) && (*pointer == ':')) {
+      // Increase pointer by 1.
+      pointer++;
+
+      // If c is the EOF code point, validation error, return failure.
+      if (pointer == input.end()) {
+        ada_log(
+            "parse_ipv6 If c is the EOF code point, validation error, return "
+            "failure");
+        return is_valid = false;
+      }
+    }
+    // Otherwise, if c is not the EOF code point, validation error, return
+    // failure.
+    else if (pointer != input.end()) {
+      ada_log(
+          "parse_ipv6 Otherwise, if c is not the EOF code point, validation "
+          "error, return failure");
+      return is_valid = false;
+    }
+
+    // Set address[pieceIndex] to value.
+    address[piece_index] = value;
+
+    // Increase pieceIndex by 1.
+    piece_index++;
+  }
+
+  // If compress is non-null, then:
+  if (compress.has_value()) {
+    // Let swaps be pieceIndex - compress.
+    int swaps = piece_index - *compress;
+
+    // Set pieceIndex to 7.
+    piece_index = 7;
+
+    // While pieceIndex is not 0 and swaps is greater than 0,
+    // swap address[pieceIndex] with address[compress + swaps - 1], and then
+    // decrease both pieceIndex and swaps by 1.
+    while (piece_index != 0 && swaps > 0) {
+      std::swap(address[piece_index], address[*compress + swaps - 1]);
+      piece_index--;
+      swaps--;
+    }
+  }
+  // Otherwise, if compress is null and pieceIndex is not 8, validation error,
+  // return failure.
+  else if (piece_index != 8) {
+    ada_log(
+        "parse_ipv6 if compress is null and pieceIndex is not 8, validation "
+        "error, return failure");
+    return is_valid = false;
+  }
+  // TODO: Optimization opportunity: Get rid of unnecessary string creation.
+  // TODO: This is likely a bug because it goes back update_base_hostname, not
+  // what we want to do.
+  update_base_hostname(ada::serializers::ipv6(address));
+  ada_log("parse_ipv6 ", get_hostname());
+  ADA_ASSERT_TRUE(validate());
+  host_type = IPV6;
+  return true;
+}
+
+bool url_aggregator::parse_opaque_host(std::string_view input) {
+  ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer));
+  if (std::any_of(input.begin(), input.end(),
+                  ada::unicode::is_forbidden_host_code_point)) {
+    return is_valid = false;
+  }
+
+  // Return the result of running UTF-8 percent-encode on input using the C0
+  // control percent-encode set.
+  size_t idx = ada::unicode::percent_encode_index(
+      input, character_sets::C0_CONTROL_PERCENT_ENCODE);
+  if (idx == input.size()) {
+    update_base_hostname(input);
+  } else {
+    // We only create a temporary string if we need to.
+    update_base_hostname(ada::unicode::percent_encode(
+        input, character_sets::C0_CONTROL_PERCENT_ENCODE, idx));
+  }
+  ADA_ASSERT_TRUE(validate());
+  return true;
+}
+
+[[nodiscard]] std::string url_aggregator::to_diagram() const {
+  if (!is_valid) {
+    return "invalid";
+  }
+  std::string answer;
+  answer.append(buffer);
+  answer.append(" [");
+  answer.append(std::to_string(buffer.size()));
+  answer.append(" bytes]");
+  answer.append("\n");
+  // first line
+  std::string line1;
+  line1.resize(buffer.size(), ' ');
+  if (components.hash_start != url_components::omitted) {
+    line1[components.hash_start] = '|';
+  }
+  if (components.search_start != url_components::omitted) {
+    line1[components.search_start] = '|';
+  }
+  if (components.pathname_start != buffer.size()) {
+    line1[components.pathname_start] = '|';
+  }
+  if (components.host_end != buffer.size()) {
+    line1[components.host_end] = '|';
+  }
+  if (components.host_start != buffer.size()) {
+    line1[components.host_start] = '|';
+  }
+  if (components.username_end != buffer.size()) {
+    line1[components.username_end] = '|';
+  }
+  if (components.protocol_end != buffer.size()) {
+    line1[components.protocol_end] = '|';
+  }
+  answer.append(line1);
+  answer.append("\n");
+
+  std::string line2 = line1;
+  if (components.hash_start != url_components::omitted) {
+    line2[components.hash_start] = '`';
+    line1[components.hash_start] = ' ';
+
+    for (size_t i = components.hash_start + 1; i < line2.size(); i++) {
+      line2[i] = '-';
+    }
+    line2.append(" hash_start");
+    answer.append(line2);
+    answer.append("\n");
+  }
+
+  std::string line3 = line1;
+  if (components.search_start != url_components::omitted) {
+    line3[components.search_start] = '`';
+    line1[components.search_start] = ' ';
+
+    for (size_t i = components.search_start + 1; i < line3.size(); i++) {
+      line3[i] = '-';
+    }
+    line3.append(" search_start ");
+    line3.append(std::to_string(components.search_start));
+    answer.append(line3);
+    answer.append("\n");
+  }
+
+  std::string line4 = line1;
+  if (components.pathname_start != buffer.size()) {
+    line4[components.pathname_start] = '`';
+    line1[components.pathname_start] = ' ';
+    for (size_t i = components.pathname_start + 1; i < line4.size(); i++) {
+      line4[i] = '-';
+    }
+    line4.append(" pathname_start ");
+    line4.append(std::to_string(components.pathname_start));
+    answer.append(line4);
+    answer.append("\n");
+  }
+
+  std::string line5 = line1;
+  if (components.host_end != buffer.size()) {
+    line5[components.host_end] = '`';
+    line1[components.host_end] = ' ';
+
+    for (size_t i = components.host_end + 1; i < line5.size(); i++) {
+      line5[i] = '-';
+    }
+    line5.append(" host_end ");
+    line5.append(std::to_string(components.host_end));
+    answer.append(line5);
+    answer.append("\n");
+  }
+
+  std::string line6 = line1;
+  if (components.host_start != buffer.size()) {
+    line6[components.host_start] = '`';
+    line1[components.host_start] = ' ';
+
+    for (size_t i = components.host_start + 1; i < line6.size(); i++) {
+      line6[i] = '-';
+    }
+    line6.append(" host_start ");
+    line6.append(std::to_string(components.host_start));
+    answer.append(line6);
+    answer.append("\n");
+  }
+
+  std::string line7 = line1;
+  if (components.username_end != buffer.size()) {
+    line7[components.username_end] = '`';
+    line1[components.username_end] = ' ';
+
+    for (size_t i = components.username_end + 1; i < line7.size(); i++) {
+      line7[i] = '-';
+    }
+    line7.append(" username_end ");
+    line7.append(std::to_string(components.username_end));
+    answer.append(line7);
+    answer.append("\n");
+  }
+
+  std::string line8 = line1;
+  if (components.protocol_end != buffer.size()) {
+    line8[components.protocol_end] = '`';
+    line1[components.protocol_end] = ' ';
+
+    for (size_t i = components.protocol_end + 1; i < line8.size(); i++) {
+      line8[i] = '-';
+    }
+    line8.append(" protocol_end ");
+    line8.append(std::to_string(components.protocol_end));
+    answer.append(line8);
+    answer.append("\n");
+  }
+
+  if (components.hash_start == url_components::omitted) {
+    answer.append("note: hash omitted\n");
+  }
+  if (components.search_start == url_components::omitted) {
+    answer.append("note: search omitted\n");
+  }
+  if (components.protocol_end > buffer.size()) {
+    answer.append("warning: protocol_end overflows\n");
+  }
+  if (components.username_end > buffer.size()) {
+    answer.append("warning: username_end overflows\n");
+  }
+  if (components.host_start > buffer.size()) {
+    answer.append("warning: host_start overflows\n");
+  }
+  if (components.host_end > buffer.size()) {
+    answer.append("warning: host_end overflows\n");
+  }
+  if (components.pathname_start > buffer.size()) {
+    answer.append("warning: pathname_start overflows\n");
+  }
+  return answer;
+}
+
+[[nodiscard]] bool url_aggregator::validate() const noexcept {
+  if (!is_valid) {
+    return true;
+  }
+  if (!components.check_offset_consistency()) {
+    ada_log("url_aggregator::validate inconsistent components \n",
+            to_diagram());
+    return false;
+  }
+  // We have a credible components struct, but let us investivate more
+  // carefully:
+  /**
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   */
+  if (components.protocol_end == url_components::omitted) {
+    ada_log("url_aggregator::validate omitted protocol_end \n", to_diagram());
+    return false;
+  }
+  if (components.username_end == url_components::omitted) {
+    ada_log("url_aggregator::validate omitted username_end \n", to_diagram());
+    return false;
+  }
+  if (components.host_start == url_components::omitted) {
+    ada_log("url_aggregator::validate omitted host_start \n", to_diagram());
+    return false;
+  }
+  if (components.host_end == url_components::omitted) {
+    ada_log("url_aggregator::validate omitted host_end \n", to_diagram());
+    return false;
+  }
+  if (components.pathname_start == url_components::omitted) {
+    ada_log("url_aggregator::validate omitted pathname_start \n", to_diagram());
+    return false;
+  }
+
+  if (components.protocol_end > buffer.size()) {
+    ada_log("url_aggregator::validate protocol_end overflow \n", to_diagram());
+    return false;
+  }
+  if (components.username_end > buffer.size()) {
+    ada_log("url_aggregator::validate username_end overflow \n", to_diagram());
+    return false;
+  }
+  if (components.host_start > buffer.size()) {
+    ada_log("url_aggregator::validate host_start overflow \n", to_diagram());
+    return false;
+  }
+  if (components.host_end > buffer.size()) {
+    ada_log("url_aggregator::validate host_end overflow \n", to_diagram());
+    return false;
+  }
+  if (components.pathname_start > buffer.size()) {
+    ada_log("url_aggregator::validate pathname_start overflow \n",
+            to_diagram());
+    return false;
+  }
+
+  if (components.protocol_end > 0) {
+    if (buffer[components.protocol_end - 1] != ':') {
+      ada_log(
+          "url_aggregator::validate missing : at the end of the protocol \n",
+          to_diagram());
+      return false;
+    }
+  }
+
+  if (components.username_end != buffer.size() &&
+      components.username_end > components.protocol_end + 2) {
+    if (buffer[components.username_end] != ':' &&
+        buffer[components.username_end] != '@') {
+      ada_log(
+          "url_aggregator::validate missing : or @ at the end of the username "
+          "\n",
+          to_diagram());
+      return false;
+    }
+  }
+
+  if (components.host_start != buffer.size()) {
+    if (components.host_start > components.username_end) {
+      if (buffer[components.host_start] != '@') {
+        ada_log(
+            "url_aggregator::validate missing @ at the end of the password \n",
+            to_diagram());
+        return false;
+      }
+    } else if (components.host_start == components.username_end &&
+               components.host_end > components.host_start) {
+      if (components.host_start == components.protocol_end + 2) {
+        if (buffer[components.protocol_end] != '/' ||
+            buffer[components.protocol_end + 1] != '/') {
+          ada_log(
+              "url_aggregator::validate missing // between protocol and host "
+              "\n",
+              to_diagram());
+          return false;
+        }
+      } else {
+        if (components.host_start > components.protocol_end &&
+            buffer[components.host_start] != '@') {
+          ada_log(
+              "url_aggregator::validate missing @ at the end of the username "
+              "\n",
+              to_diagram());
+          return false;
+        }
+      }
+    } else {
+      if (components.host_end != components.host_start) {
+        ada_log("url_aggregator::validate expected omitted host \n",
+                to_diagram());
+        return false;
+      }
+    }
+  }
+  if (components.host_end != buffer.size() &&
+      components.pathname_start > components.host_end) {
+    if (components.pathname_start == components.host_end + 2 &&
+        buffer[components.host_end] == '/' &&
+        buffer[components.host_end + 1] == '.') {
+      if (components.pathname_start + 1 >= buffer.size() ||
+          buffer[components.pathname_start] != '/' ||
+          buffer[components.pathname_start + 1] != '/') {
+        ada_log(
+            "url_aggregator::validate expected the path to begin with // \n",
+            to_diagram());
+        return false;
+      }
+    } else if (buffer[components.host_end] != ':') {
+      ada_log("url_aggregator::validate missing : at the port \n",
+              to_diagram());
+      return false;
+    }
+  }
+  if (components.pathname_start != buffer.size() &&
+      components.pathname_start < components.search_start &&
+      components.pathname_start < components.hash_start && !has_opaque_path) {
+    if (buffer[components.pathname_start] != '/') {
+      ada_log("url_aggregator::validate missing / at the path \n",
+              to_diagram());
+      return false;
+    }
+  }
+  if (components.search_start != url_components::omitted) {
+    if (buffer[components.search_start] != '?') {
+      ada_log("url_aggregator::validate missing ? at the search \n",
+              to_diagram());
+      return false;
+    }
+  }
+  if (components.hash_start != url_components::omitted) {
+    if (buffer[components.hash_start] != '#') {
+      ada_log("url_aggregator::validate missing # at the hash \n",
+              to_diagram());
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void url_aggregator::delete_dash_dot() {
+  ada_log("url_aggregator::delete_dash_dot");
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(has_dash_dot());
+  buffer.erase(components.host_end, 2);
+  components.pathname_start -= 2;
+  if (components.search_start != url_components::omitted) {
+    components.search_start -= 2;
+  }
+  if (components.hash_start != url_components::omitted) {
+    components.hash_start -= 2;
+  }
+  ADA_ASSERT_TRUE(validate());
+  ADA_ASSERT_TRUE(!has_dash_dot());
+}
+
+inline void url_aggregator::consume_prepared_path(std::string_view input) {
+  ada_log("url_aggregator::consume_prepared_path ", input);
+  /***
+   * This is largely duplicated code from helpers::parse_prepared_path, which is
+   * unfortunate. This particular function is nearly identical, except that it
+   * is a method on url_aggregator. The idea is that the trivial path (which is
+   * very common) merely appends to the buffer. This is the same trivial path as
+   * with helpers::parse_prepared_path, except that we have the additional check
+   * for is_at_path(). Otherwise, we grab a copy of the current path and we
+   * modify it, and then insert it back into the buffer.
+   */
+  uint8_t accumulator = checkers::path_signature(input);
+  // Let us first detect a trivial case.
+  // If it is special, we check that we have no dot, no %,  no \ and no
+  // character needing percent encoding. Otherwise, we check that we have no %,
+  // no dot, and no character needing percent encoding.
+  constexpr uint8_t need_encoding = 1;
+  constexpr uint8_t backslash_char = 2;
+  constexpr uint8_t dot_char = 4;
+  constexpr uint8_t percent_char = 8;
+  bool special = type != ada::scheme::NOT_SPECIAL;
+  bool may_need_slow_file_handling = (type == ada::scheme::type::FILE &&
+                                      checkers::is_windows_drive_letter(input));
+  bool trivial_path =
+      (special ? (accumulator == 0)
+               : ((accumulator & (need_encoding | dot_char | percent_char)) ==
+                  0)) &&
+      (!may_need_slow_file_handling);
+  if (accumulator == dot_char && !may_need_slow_file_handling) {
+    // '4' means that we have at least one dot, but nothing that requires
+    // percent encoding or decoding. The only part that is not trivial is
+    // that we may have single dots and double dots path segments.
+    // If we have such segments, then we either have a path that begins
+    // with '.' (easy to check), or we have the sequence './'.
+    // Note: input cannot be empty, it must at least contain one character ('.')
+    // Note: we know that '\' is not present.
+    if (input[0] != '.') {
+      size_t slashdot = input.find("/.");
+      if (slashdot == std::string_view::npos) {  // common case
+        trivial_path = true;
+      } else {  // uncommon
+        // only three cases matter: /./, /.. or a final /
+        trivial_path =
+            !(slashdot + 2 == input.size() || input[slashdot + 2] == '.' ||
+              input[slashdot + 2] == '/');
+      }
+    }
+  }
+  if (trivial_path && is_at_path()) {
+    ada_log("parse_path trivial");
+    buffer += '/';
+    buffer += input;
+    return;
+  }
+  std::string path = std::string(get_pathname());
+  // We are going to need to look a bit at the path, but let us see if we can
+  // ignore percent encoding *and* backslashes *and* percent characters.
+  // Except for the trivial case, this is likely to capture 99% of paths out
+  // there.
+  bool fast_path =
+      (special &&
+       (accumulator & (need_encoding | backslash_char | percent_char)) == 0) &&
+      (type != ada::scheme::type::FILE);
+  if (fast_path) {
+    ada_log("parse_prepared_path fast");
+    // Here we don't need to worry about \ or percent encoding.
+    // We also do not have a file protocol. We might have dots, however,
+    // but dots must as appear as '.', and they cannot be encoded because
+    // the symbol '%' is not present.
+    size_t previous_location = 0;  // We start at 0.
+    do {
+      size_t new_location = input.find('/', previous_location);
+      // std::string_view path_view = input;
+      //  We process the last segment separately:
+      if (new_location == std::string_view::npos) {
+        std::string_view path_view = input.substr(previous_location);
+        if (path_view == "..") {  // The path ends with ..
+          // e.g., if you receive ".." with an empty path, you go to "/".
+          if (path.empty()) {
+            path = '/';
+            update_base_pathname(path);
+            return;
+          }
+          // Fast case where we have nothing to do:
+          if (path.back() == '/') {
+            update_base_pathname(path);
+            return;
+          }
+          // If you have the path "/joe/myfriend",
+          // then you delete 'myfriend'.
+          path.resize(path.rfind('/') + 1);
+          update_base_pathname(path);
+          return;
+        }
+        path += '/';
+        if (path_view != ".") {
+          path.append(path_view);
+        }
+        update_base_pathname(path);
+        return;
+      } else {
+        // This is a non-final segment.
+        std::string_view path_view =
+            input.substr(previous_location, new_location - previous_location);
+        previous_location = new_location + 1;
+        if (path_view == "..") {
+          size_t last_delimiter = path.rfind('/');
+          if (last_delimiter != std::string::npos) {
+            path.erase(last_delimiter);
+          }
+        } else if (path_view != ".") {
+          path += '/';
+          path.append(path_view);
+        }
+      }
+    } while (true);
+  } else {
+    ada_log("parse_path slow");
+    // we have reached the general case
+    bool needs_percent_encoding = (accumulator & 1);
+    std::string path_buffer_tmp;
+    do {
+      size_t location = (special && (accumulator & 2))
+                            ? input.find_first_of("/\\")
+                            : input.find('/');
+      std::string_view path_view = input;
+      if (location != std::string_view::npos) {
+        path_view.remove_suffix(path_view.size() - location);
+        input.remove_prefix(location + 1);
+      }
+      // path_buffer is either path_view or it might point at a percent encoded
+      // temporary string.
+      std::string_view path_buffer =
+          (needs_percent_encoding &&
+           ada::unicode::percent_encode<false>(
+               path_view, character_sets::PATH_PERCENT_ENCODE, path_buffer_tmp))
+              ? path_buffer_tmp
+              : path_view;
+      if (unicode::is_double_dot_path_segment(path_buffer)) {
+        if ((helpers::shorten_path(path, type) || special) &&
+            location == std::string_view::npos) {
+          path += '/';
+        }
+      } else if (unicode::is_single_dot_path_segment(path_buffer) &&
+                 (location == std::string_view::npos)) {
+        path += '/';
+      }
+      // Otherwise, if path_buffer is not a single-dot path segment, then:
+      else if (!unicode::is_single_dot_path_segment(path_buffer)) {
+        // If url's scheme is "file", url's path is empty, and path_buffer is a
+        // Windows drive letter, then replace the second code point in
+        // path_buffer with U+003A (:).
+        if (type == ada::scheme::type::FILE && path.empty() &&
+            checkers::is_windows_drive_letter(path_buffer)) {
+          path += '/';
+          path += path_buffer[0];
+          path += ':';
+          path_buffer.remove_prefix(2);
+          path.append(path_buffer);
+        } else {
+          // Append path_buffer to url's path.
+          path += '/';
+          path.append(path_buffer);
+        }
+      }
+      if (location == std::string_view::npos) {
+        update_base_pathname(path);
+        return;
+      }
+    } while (true);
+  }
+}
+}  // namespace ada
diff --git a/src/url_components.cpp b/src/url_components.cpp
new file mode 100644 (file)
index 0000000..3870b9b
--- /dev/null
@@ -0,0 +1,127 @@
+#include "ada.h"
+#include "ada/helpers.h"
+#include "ada/url_components.h"
+
+#include <numeric>
+#include <string>
+
+namespace ada {
+
+[[nodiscard]] bool url_components::check_offset_consistency() const noexcept {
+  /**
+   * https://user:pass@example.com:1234/foo/bar?baz#quux
+   *       |     |    |          | ^^^^|       |   |
+   *       |     |    |          | |   |       |   `----- hash_start
+   *       |     |    |          | |   |       `--------- search_start
+   *       |     |    |          | |   `----------------- pathname_start
+   *       |     |    |          | `--------------------- port
+   *       |     |    |          `----------------------- host_end
+   *       |     |    `---------------------------------- host_start
+   *       |     `--------------------------------------- username_end
+   *       `--------------------------------------------- protocol_end
+   */
+  // These conditions can be made more strict.
+  uint32_t index = 0;
+
+  if (protocol_end == url_components::omitted) {
+    return false;
+  }
+  if (protocol_end < index) {
+    return false;
+  }
+  index = protocol_end;
+
+  if (username_end == url_components::omitted) {
+    return false;
+  }
+  if (username_end < index) {
+    return false;
+  }
+  index = username_end;
+
+  if (host_start == url_components::omitted) {
+    return false;
+  }
+  if (host_start < index) {
+    return false;
+  }
+  index = host_start;
+
+  if (port != url_components::omitted) {
+    if (port > 0xffff) {
+      return false;
+    }
+    uint32_t port_length = helpers::fast_digit_count(port) + 1;
+    if (index + port_length < index) {
+      return false;
+    }
+    index += port_length;
+  }
+
+  if (pathname_start == url_components::omitted) {
+    return false;
+  }
+  if (pathname_start < index) {
+    return false;
+  }
+  index = pathname_start;
+
+  if (search_start != url_components::omitted) {
+    if (search_start < index) {
+      return false;
+    }
+    index = search_start;
+  }
+
+  if (hash_start != url_components::omitted) {
+    if (hash_start < index) {
+      return false;
+    }
+    index = hash_start;
+  }
+
+  return true;
+}
+
+[[nodiscard]] std::string url_components::to_string() const {
+  std::string answer;
+  auto back = std::back_insert_iterator(answer);
+  answer.append("{\n");
+
+  answer.append("\t\"protocol_end\":\"");
+  helpers::encode_json(std::to_string(protocol_end), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"username_end\":\"");
+  helpers::encode_json(std::to_string(username_end), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"host_start\":\"");
+  helpers::encode_json(std::to_string(host_start), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"host_end\":\"");
+  helpers::encode_json(std::to_string(host_end), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"port\":\"");
+  helpers::encode_json(std::to_string(port), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"pathname_start\":\"");
+  helpers::encode_json(std::to_string(pathname_start), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"search_start\":\"");
+  helpers::encode_json(std::to_string(search_start), back);
+  answer.append("\",\n");
+
+  answer.append("\t\"hash_start\":\"");
+  helpers::encode_json(std::to_string(hash_start), back);
+  answer.append("\",\n");
+
+  answer.append("\n}");
+  return answer;
+}
+
+}  // namespace ada
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a1f21f4
--- /dev/null
@@ -0,0 +1,66 @@
+add_subdirectory(wpt)
+
+set(ADA_TEST_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../)
+if(MSVC)
+  add_compile_options("/Zi" "/EHsc" "/GR")
+  add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
+  add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
+endif()
+
+include(${PROJECT_SOURCE_DIR}/cmake/add-cpp-test.cmake)
+link_libraries(ada)
+
+add_cpp_test(basic_fuzzer)
+
+if(MSVC AND BUILD_SHARED_LIBS)
+  # Copy the ada dll into the directory
+  add_custom_command(TARGET basic_fuzzer PRE_BUILD        # Adds a pre-build event
+    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake -E copy_if_different..."
+        "$<TARGET_FILE:ada>"      # <--this is in-file
+        "$<TARGET_FILE_DIR:basic_fuzzer>")                 # <--this is out-file path
+endif()
+
+if(MSVC AND BUILD_SHARED_LIBS)
+  message(STATUS "For some tests we use Google Test and it fails when building a DLL.")
+  message(STATUS "Thus the tests are disabled. Sorry.")
+else()
+  include(GoogleTest)
+  add_executable(wpt_tests wpt_tests.cpp)
+  add_executable(url_components url_components.cpp)
+  add_executable(basic_tests basic_tests.cpp)
+  add_executable(from_file_tests from_file_tests.cpp)
+  add_executable(ada_c ada_c.cpp)
+  add_executable(url_search_params url_search_params.cpp)
+
+  target_link_libraries(wpt_tests PRIVATE simdjson GTest::gtest_main)
+  target_link_libraries(url_components PRIVATE simdjson GTest::gtest_main)
+  target_link_libraries(basic_tests PRIVATE simdjson GTest::gtest_main)
+  target_link_libraries(from_file_tests PRIVATE simdjson GTest::gtest_main)
+  target_link_libraries(ada_c PRIVATE simdjson GTest::gtest_main)
+  target_link_libraries(url_search_params PRIVATE simdjson GTest::gtest_main)
+
+  gtest_discover_tests(wpt_tests)
+  gtest_discover_tests(url_components)
+  gtest_discover_tests(basic_tests)
+  gtest_discover_tests(from_file_tests)
+  gtest_discover_tests(ada_c)
+  gtest_discover_tests(url_search_params)
+
+  if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+    if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9)
+      target_link_libraries(wpt_tests PUBLIC stdc++fs)
+      target_link_libraries(url_components PUBLIC stdc++fs)
+      target_link_libraries(url_search_params PUBLIC stdc++fs)
+    endif()
+  endif()
+
+  if(MSVC OR MINGW)
+    target_compile_definitions(wpt_tests PRIVATE _CRT_SECURE_NO_WARNINGS)
+    target_compile_definitions(url_components PRIVATE _CRT_SECURE_NO_WARNINGS)
+    target_compile_definitions(basic_fuzzer PRIVATE _CRT_SECURE_NO_WARNINGS)
+    target_compile_definitions(from_file_tests PRIVATE _CRT_SECURE_NO_WARNINGS)
+    target_compile_definitions(basic_tests PRIVATE _CRT_SECURE_NO_WARNINGS)
+    target_compile_definitions(url_search_params PRIVATE _CRT_SECURE_NO_WARNINGS)
+  endif()
+
+endif()
diff --git a/tests/ada_c.cpp b/tests/ada_c.cpp
new file mode 100644 (file)
index 0000000..b13d2e0
--- /dev/null
@@ -0,0 +1,343 @@
+// gest is a C++ library so we are in C++.
+#include "gtest/gtest.h"
+extern "C" {
+#include "ada_c.h"
+}
+
+template <typename T>
+std::string convert_string(const T& input) {
+  printf("result %s \n", std::string(input.data, input.length).c_str());
+  return std::string(input.data, input.length);
+}
+
+TEST(ada_c, ada_parse) {
+  std::string_view input =
+      "https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists";
+  ada_url url = ada_parse(input.data(), input.length());
+
+  ASSERT_TRUE(ada_is_valid(url));
+
+  ada_free(url);
+
+  SUCCEED();
+}
+
+TEST(ada_c, ada_parse_with_base) {
+  std::string_view input = "/hello";
+  std::string_view base =
+      "https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists";
+  ada_url url = ada_parse_with_base(input.data(), input.length(), base.data(),
+                                    base.length());
+
+  ASSERT_TRUE(ada_is_valid(url));
+
+  ada_free(url);
+
+  SUCCEED();
+}
+
+TEST(ada_c, getters) {
+  std::string_view input =
+      "https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists";
+  ada_url url = ada_parse(input.data(), input.length());
+
+  ASSERT_TRUE(ada_is_valid(url));
+
+  ada_owned_string origin = ada_get_origin(url);
+  ASSERT_EQ(convert_string(origin), "https://www.google.com:8080");
+  ada_free_owned_string(origin);
+
+  ASSERT_EQ(convert_string(ada_get_href(url)),
+            "https://username:password@www.google.com:8080/"
+            "pathname?query=true#hash-exists");
+  ASSERT_EQ(convert_string(ada_get_username(url)), "username");
+  ASSERT_EQ(convert_string(ada_get_password(url)), "password");
+  ASSERT_EQ(convert_string(ada_get_port(url)), "8080");
+  ASSERT_EQ(convert_string(ada_get_hash(url)), "#hash-exists");
+  ASSERT_EQ(convert_string(ada_get_host(url)), "www.google.com:8080");
+  ASSERT_EQ(convert_string(ada_get_hostname(url)), "www.google.com");
+  ASSERT_EQ(convert_string(ada_get_pathname(url)), "/pathname");
+  ASSERT_EQ(convert_string(ada_get_search(url)), "?query=true");
+  ASSERT_EQ(convert_string(ada_get_protocol(url)), "https:");
+
+  ada_free(url);
+
+  SUCCEED();
+}
+
+TEST(ada_c, setters) {
+  std::string input =
+      "https://username:password@www.google.com:8080/"
+      "pathname?query=true#hash-exists";
+  ada_url url = ada_parse(input.data(), input.length());
+
+  ASSERT_TRUE(ada_is_valid(url));
+
+  ada_set_href(url, "https://www.yagiz.co", strlen("https://www.yagiz.co"));
+  ASSERT_EQ(convert_string(ada_get_href(url)), "https://www.yagiz.co/");
+
+  ada_set_username(url, "new-username", strlen("new-username"));
+  ASSERT_EQ(convert_string(ada_get_username(url)), "new-username");
+
+  ada_set_password(url, "new-password", strlen("new-password"));
+  ASSERT_EQ(convert_string(ada_get_password(url)), "new-password");
+
+  ada_set_port(url, "4242", 4);
+  ASSERT_EQ(convert_string(ada_get_port(url)), "4242");
+  ada_clear_port(url);
+  ASSERT_EQ(convert_string(ada_get_port(url)), "");
+  ASSERT_FALSE(ada_has_port(url));
+
+  ada_set_hash(url, "new-hash", strlen("new-hash"));
+  ASSERT_EQ(convert_string(ada_get_hash(url)), "#new-hash");
+  ada_clear_hash(url);
+  ASSERT_FALSE(ada_has_hash(url));
+
+  ada_set_hostname(url, "new-host", strlen("new-host"));
+  ASSERT_EQ(convert_string(ada_get_hostname(url)), "new-host");
+
+  ada_set_host(url, "changed-host:9090", strlen("changed-host:9090"));
+  ASSERT_EQ(convert_string(ada_get_host(url)), "changed-host:9090");
+
+  ada_set_pathname(url, "new-pathname", strlen("new-pathname"));
+  ASSERT_EQ(convert_string(ada_get_pathname(url)), "/new-pathname");
+
+  ada_set_search(url, "new-search", strlen("new-search"));
+  ASSERT_EQ(convert_string(ada_get_search(url)), "?new-search");
+  ada_clear_search(url);
+  ASSERT_EQ(convert_string(ada_get_search(url)), "");
+
+  ada_set_protocol(url, "wss", 3);
+  ASSERT_EQ(convert_string(ada_get_protocol(url)), "wss:");
+
+  ASSERT_EQ(ada_get_host_type(url), 0);
+
+  ada_free(url);
+
+  SUCCEED();
+}
+
+TEST(ada_c, can_parse) {
+  std::string input = "https://www.google.com";
+  std::string path = "/hello-world";
+
+  ASSERT_TRUE(ada_can_parse(input.data(), input.length()));
+  ASSERT_FALSE(ada_can_parse(path.data(), path.length()));
+  ASSERT_TRUE(ada_can_parse_with_base(path.data(), path.length(), input.data(),
+                                      input.length()));
+}
+
+TEST(ada_c, ada_url_components) {
+  std::string input = "https://www.google.com";
+  ada_url url = ada_parse(input.data(), input.length());
+  const ada_url_components* components = ada_get_components(url);
+
+  ASSERT_EQ(components->protocol_end, 6);
+  ASSERT_EQ(components->port, ada_url_omitted);
+  ASSERT_EQ(components->search_start, ada_url_omitted);
+  ASSERT_EQ(components->hash_start, ada_url_omitted);
+
+  ada_free(url);
+
+  SUCCEED();
+}
+
+TEST(ada_c, ada_copy) {
+  std::string lemire_blog = "https://lemire.me";
+  std::string anonrig_blog = "https://yagiz.co";
+  ada_url first = ada_parse(lemire_blog.data(), lemire_blog.length());
+  ada_url second = ada_copy(first);
+
+  ASSERT_TRUE(ada_set_href(second, anonrig_blog.data(), anonrig_blog.size()));
+
+  ASSERT_EQ(convert_string(ada_get_href(first)), "https://lemire.me/");
+  ASSERT_EQ(convert_string(ada_get_href(second)), "https://yagiz.co/");
+
+  ada_free(first);
+  ada_free(second);
+
+  SUCCEED();
+}
+
+TEST(ada_c, ada_idna) {
+  std::string_view ascii_input = "straße.de";
+  std::string_view unicode_input = "xn--strae-oqa.de";
+  ada_owned_string ascii =
+      ada_idna_to_ascii(ascii_input.data(), ascii_input.length());
+  ASSERT_EQ(std::string_view(ascii.data, ascii.length), unicode_input);
+
+  ada_owned_string unicode =
+      ada_idna_to_unicode(unicode_input.data(), unicode_input.length());
+  ASSERT_EQ(std::string_view(unicode.data, unicode.length), ascii_input);
+
+  ada_free_owned_string(ascii);
+  ada_free_owned_string(unicode);
+  SUCCEED();
+}
+
+TEST(ada_c, ada_clear_hash) {
+  // Make sure a hash attribute with `#` is removed.
+  std::string_view input = "https://www.google.com/hello-world?query=1#";
+  ada_url out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+
+  ada_clear_hash(out);
+  ASSERT_EQ(convert_string(ada_get_hash(out)), "");
+  ASSERT_FALSE(ada_has_hash(out));
+  ASSERT_EQ(convert_string(ada_get_href(out)),
+            "https://www.google.com/hello-world?query=1");
+
+  ada_free(out);
+  SUCCEED();
+}
+
+TEST(ada_c, ada_clear_search) {
+  // Make sure a search attribute with `?` is removed.
+  std::string_view input = "https://www.google.com/hello-world?#hash";
+  ada_url out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+
+  ada_clear_search(out);
+  ASSERT_EQ(convert_string(ada_get_search(out)), "");
+  ASSERT_FALSE(ada_has_search(out));
+  ASSERT_EQ(convert_string(ada_get_href(out)),
+            "https://www.google.com/hello-world#hash");
+
+  ada_free(out);
+  SUCCEED();
+}
+
+TEST(ada_c, ada_get_scheme_type) {
+  std::string_view input;
+  ada_url out;
+
+  input = "http://www.google.com";
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 0);
+
+  input = "notspecial://www.google.com";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 1);
+
+  input = "https://www.google.com";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 2);
+
+  input = "ws://www.google.com/ws";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 3);
+
+  input = "ftp://www.google.com/file.txt";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 4);
+
+  input = "wss://www.google.com/wss";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 5);
+
+  input = "file:///foo/bar";
+  ada_free(out);
+  out = ada_parse(input.data(), input.size());
+  ASSERT_TRUE(ada_is_valid(out));
+  ASSERT_EQ(ada_get_scheme_type(out), 6);
+
+  ada_free(out);
+  SUCCEED();
+}
+
+TEST(ada_c, ada_url_search_params) {
+  std::string_view input;
+  ada_url_search_params out;
+
+  input = "a=b&c=d&c=e&f=g";
+  out = ada_parse_search_params(input.data(), input.size());
+
+  ASSERT_EQ(ada_search_params_size(out), 4);
+
+  std::string key = "key1";
+  std::string value = "value1";
+  std::string value2 = "value2";
+  ada_search_params_append(out, key.c_str(), key.length(), value.c_str(),
+                           value.length());
+  ASSERT_EQ(ada_search_params_size(out), 5);
+
+  ada_search_params_set(out, key.c_str(), key.length(), value2.c_str(),
+                        value2.length());
+  ASSERT_EQ(ada_search_params_size(out), 5);
+
+  ASSERT_TRUE(ada_search_params_has(out, key.c_str(), key.length()));
+  ASSERT_FALSE(ada_search_params_has_value(out, key.c_str(), key.length(),
+                                           value.c_str(), value.length()));
+  ASSERT_TRUE(ada_search_params_has_value(out, key.c_str(), key.length(),
+                                          value2.c_str(), value2.length()));
+
+  ada_strings result =
+      ada_search_params_get_all(out, key.c_str(), key.length());
+  ASSERT_EQ(ada_strings_size(result), 1);
+  ada_free_strings(result);
+
+  ada_url_search_params_keys_iter keys = ada_search_params_get_keys(out);
+  ada_url_search_params_values_iter values = ada_search_params_get_values(out);
+  ada_url_search_params_entries_iter entries =
+      ada_search_params_get_entries(out);
+
+  ASSERT_TRUE(ada_search_params_keys_iter_has_next(keys));
+  ASSERT_TRUE(ada_search_params_values_iter_has_next(values));
+  ASSERT_TRUE(ada_search_params_entries_iter_has_next(entries));
+
+  ASSERT_EQ(convert_string(ada_search_params_keys_iter_next(keys)), "a");
+  ASSERT_EQ(convert_string(ada_search_params_keys_iter_next(keys)), "c");
+  ASSERT_EQ(convert_string(ada_search_params_keys_iter_next(keys)), "c");
+  ASSERT_EQ(convert_string(ada_search_params_keys_iter_next(keys)), "f");
+  ASSERT_EQ(convert_string(ada_search_params_keys_iter_next(keys)), "key1");
+  ASSERT_FALSE(ada_search_params_keys_iter_has_next(keys));
+
+  ASSERT_EQ(convert_string(ada_search_params_values_iter_next(values)), "b");
+  ASSERT_EQ(convert_string(ada_search_params_values_iter_next(values)), "d");
+  ASSERT_EQ(convert_string(ada_search_params_values_iter_next(values)), "e");
+  ASSERT_EQ(convert_string(ada_search_params_values_iter_next(values)), "g");
+  ASSERT_EQ(convert_string(ada_search_params_values_iter_next(values)),
+            "value2");
+  ASSERT_FALSE(ada_search_params_values_iter_has_next(values));
+
+  ada_string_pair pair = ada_search_params_entries_iter_next(entries);
+  ASSERT_EQ(convert_string(pair.value), "b");
+  ASSERT_EQ(convert_string(pair.key), "a");
+
+  pair = ada_search_params_entries_iter_next(entries);
+  ASSERT_EQ(convert_string(pair.value), "d");
+  ASSERT_EQ(convert_string(pair.key), "c");
+
+  while (ada_search_params_entries_iter_has_next(entries)) {
+    ada_search_params_entries_iter_next(entries);
+  }
+
+  ada_search_params_remove(out, key.c_str(), key.length());
+  ada_search_params_remove_value(out, key.c_str(), key.length(), value.c_str(),
+                                 value.length());
+
+  ada_owned_string str = ada_search_params_to_string(out);
+  ASSERT_EQ(convert_string(str), "a=b&c=d&c=e&f=g");
+
+  ada_free_search_params_keys_iter(keys);
+  ada_free_search_params_values_iter(values);
+  ada_free_search_params_entries_iter(entries);
+  ada_free_owned_string(str);
+  ada_free_search_params(out);
+
+  SUCCEED();
+}
diff --git a/tests/basic_fuzzer.cpp b/tests/basic_fuzzer.cpp
new file mode 100644 (file)
index 0000000..a64698a
--- /dev/null
@@ -0,0 +1,140 @@
+#include "ada.h"
+#include <iostream>
+#include <memory>
+
+std::string url_examples[] = {
+    "https://www.google.com/"
+    "webhp?hl=en&amp;ictx=2&amp;sa=X&amp;ved=0ahUKEwil_"
+    "oSxzJj8AhVtEFkFHTHnCGQQPQgI",
+    "https://support.google.com/websearch/"
+    "?p=ws_results_help&amp;hl=en-CA&amp;fg=1",
+    "https://en.wikipedia.org/wiki/Dog#Roles_with_humans",
+    "https://www.tiktok.com/@aguyandagolden/video/7133277734310038830",
+    "https://business.twitter.com/en/help/troubleshooting/"
+    "how-twitter-ads-work.html?ref=web-twc-ao-gbl-adsinfo&utm_source=twc&utm_"
+    "medium=web&utm_campaign=ao&utm_content=adsinfo",
+    "https://images-na.ssl-images-amazon.com/images/I/"
+    "41Gc3C8UysL.css?AUIClients/AmazonGatewayAuiAssets",
+    "https://www.reddit.com/?after=t3_zvz1ze",
+    "https://www.reddit.com/login/?dest=https%3A%2F%2Fwww.reddit.com%2F",
+    "postgresql://other:9818274x1!!@localhost:5432/"
+    "otherdb?connect_timeout=10&application_name=myapp",
+    "http://192.168.1.1",             // ipv4
+    "http://[2606:4700:4700::1111]",  // ipv6
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/"
+    "polyfills.js",
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/"
+    "css/orbit-v5-ltr.min.css",
+    "https://static.files.bbci.co.uk/orbit/737a4ee2bed596eb65afc4d2ce9af568/js/"
+    "require.min.js",
+    "https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Rg.woff2",
+    "https://nav.files.bbci.co.uk/searchbox/c8bfe8595e453f2b9483fda4074e9d15/"
+    "css/box.css",
+    "https://static.files.bbci.co.uk/cookies/d3bb303e79f041fec95388e04f84e716/"
+    "cookie-banner/cookie-library.bundle.js",
+    "https://static.files.bbci.co.uk/account/id-cta/597/style/id-cta.css",
+    "https://gn-web-assets.api.bbc.com/wwhp/"
+    "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/responsive/css/"
+    "old-ie.min.css",
+    "https://gn-web-assets.api.bbc.com/wwhp/"
+    "20220908-1153-091014d07889c842a7bdc06e00fa711c9e04f049/modules/vendor/"
+    "bower/modernizr/modernizr.js"};
+
+// This function copies your input onto a memory buffer that
+// has just the necessary size. This will entice tools to detect
+// an out-of-bound access.
+template <class result>
+ada::result<result> ada_parse(std::string_view view) {
+  std::unique_ptr<char[]> buffer(new char[view.size()]);
+  memcpy(buffer.get(), view.data(), view.size());
+  return ada::parse<result>(std::string_view(buffer.get(), view.size()));
+}
+
+template <class result>
+size_t fancy_fuzz(size_t N, size_t seed = 0) {
+  size_t counter = seed;
+  for (size_t trial = 0; trial < N; trial++) {
+    std::string copy =
+        url_examples[(seed++) % (sizeof(url_examples) / sizeof(std::string))];
+    auto url = ada::parse<result>(copy);
+    while (url) {
+      // mutate the string.
+      int k = ((321321 * counter++) % 3);
+      switch (k) {
+        case 0:
+          copy.erase((11134 * counter++) % copy.size());
+          break;
+        case 1:
+          copy.insert(copy.begin() + (211311 * counter) % copy.size(),
+                      char((counter + 1) * 777));
+          counter += 2;
+          break;
+        case 2:
+          copy[(13134 * counter++) % copy.size()] = char(counter++ * 71117);
+          break;
+        default:
+          break;
+      }
+      url = ada_parse<result>(copy);
+    }
+  }
+  return counter;
+}
+
+template <class result>
+size_t simple_fuzz(size_t N, size_t seed = 0) {
+  size_t counter = seed;
+  for (size_t trial = 0; trial < N; trial++) {
+    std::string copy =
+        url_examples[(seed++) % (sizeof(url_examples) / sizeof(std::string))];
+    auto url = ada::parse<result>(copy);
+    while (url) {
+      // mutate the string.
+      copy[(13134 * counter++) % copy.size()] = char(counter++ * 71117);
+      url = ada_parse<result>(copy);
+    }
+  }
+  return counter;
+}
+
+template <class result>
+size_t roller_fuzz(size_t N) {
+  size_t valid{};
+
+  for (std::string copy : url_examples) {
+    for (size_t index = 0; index < copy.size(); index++) {
+      char orig = copy[index];
+      for (unsigned int value = 0; value < 255; value++) {
+        copy[index] = char(value);
+        auto url = ada_parse<result>(copy);
+        if (url) {
+          valid++;
+        }
+      }
+      copy[index] = orig;
+    }
+  }
+  return valid;
+}
+
+int main() {
+#if ADA_IS_BIG_ENDIAN
+  std::cout << "You have big-endian system." << std::endl;
+#else
+  std::cout << "You have litte-endian system." << std::endl;
+#endif
+  std::cout << "Running basic fuzzer.\n";
+  std::cout << "[fancy]  Executed " << fancy_fuzz<ada::url>(100000)
+            << " mutations.\n";
+  std::cout << "[simple] Executed " << simple_fuzz<ada::url>(40000)
+            << " mutations.\n";
+  std::cout << "[roller] Executed " << roller_fuzz<ada::url>(40000)
+            << " correct cases.\n";
+  std::cout << "[fancy]  Executed " << fancy_fuzz<ada::url_aggregator>(100000)
+            << " mutations.\n";
+  std::cout << "[simple] Executed " << simple_fuzz<ada::url_aggregator>(40000)
+            << " mutations.\n";
+  std::cout << "[roller] Executed " << roller_fuzz<ada::url_aggregator>(40000)
+            << " correct cases.\n";
+  return EXIT_SUCCESS;
+}
diff --git a/tests/basic_tests.cpp b/tests/basic_tests.cpp
new file mode 100644 (file)
index 0000000..79ccbc6
--- /dev/null
@@ -0,0 +1,433 @@
+#include "ada.h"
+#include "gtest/gtest.h"
+#include <cstdlib>
+#include <iostream>
+
+using Types = testing::Types<ada::url, ada::url_aggregator>;
+template <class T>
+struct basic_tests : testing::Test {};
+TYPED_TEST_SUITE(basic_tests, Types);
+
+TYPED_TEST(basic_tests, insane_url) {
+  auto r = ada::parse<ada::url_aggregator>("e:@EEEEEEEEEE");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_protocol(), "e:");
+  ASSERT_EQ(r->get_username(), "");
+  ASSERT_EQ(r->get_password(), "");
+  ASSERT_EQ(r->get_hostname(), "");
+  ASSERT_EQ(r->get_port(), "");
+  ASSERT_EQ(r->get_pathname(), "@EEEEEEEEEE");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, bad_percent_encoding) {
+  auto r = ada::parse<TypeParam>("http://www.google.com/%X%");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_href(), "http://www.google.com/%X%");
+  r = ada::parse<TypeParam>("http://www.google%X%.com/");
+  ASSERT_FALSE(r);
+  r = ada::parse<TypeParam>("http://www.google.com/");
+  ASSERT_TRUE(r);
+  r->set_href("http://www.google.com/%X%");
+  ASSERT_EQ(r->get_href(), "http://www.google.com/%X%");
+  ASSERT_FALSE(r->set_host("www.google%X%.com"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, spaces_spaces) {
+  auto r = ada::parse<TypeParam>("http://www.google.com/%37/ /");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_href(), "http://www.google.com/%37/%20/");
+  r->set_href("http://www.google.com/  /  /+/");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_href(), "http://www.google.com/%20%20/%20%20/+/");
+  r = ada::parse<TypeParam>("http://www.google com/");
+  ASSERT_FALSE(r);
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, pluses) {
+  auto r = ada::parse<TypeParam>("http://www.google.com/%37+/");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_href(), "http://www.google.com/%37+/");
+  r = ada::parse<TypeParam>("http://www.google+com/");
+  ASSERT_TRUE(r);
+  ASSERT_EQ(r->get_href(), "http://www.google+com/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, set_host_should_return_false_sometimes) {
+  auto r = ada::parse<TypeParam>("mailto:a@b.com");
+  ASSERT_FALSE(r->set_host("something"));
+  auto r2 = ada::parse<TypeParam>("mailto:a@b.com");
+  ASSERT_FALSE(r2->set_host("something"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, empty_url_should_return_false) {
+  auto r = ada::parse<TypeParam>("");
+  ASSERT_FALSE(r);
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, set_host_should_return_true_sometimes) {
+  auto r = ada::parse<TypeParam>("https://www.google.com");
+  ASSERT_TRUE(r->set_host("something"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, set_hostname_should_return_false_sometimes) {
+  auto r = ada::parse<TypeParam>("mailto:a@b.com");
+  ASSERT_FALSE(r->set_hostname("something"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, set_hostname_should_return_true_sometimes) {
+  auto r = ada::parse<TypeParam>("https://www.google.com");
+  ASSERT_TRUE(r->set_hostname("something"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  ASSERT_TRUE(bool(url));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme2) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_username("username");
+  url->set_password("password");
+  ASSERT_EQ(url->get_href(), "https://username:password@www.google.com/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme3) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_protocol("wss");
+  ASSERT_EQ(url->get_protocol(), "wss:");
+  ASSERT_EQ(url->get_href(), "wss://www.google.com/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme4) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_host("github.com");
+  ASSERT_EQ(url->get_host(), "github.com");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme5) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_port("8080");
+  ASSERT_EQ(url->get_port(), "8080");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme6) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_pathname("/my-super-long-path");
+  ASSERT_EQ(url->get_pathname(), "/my-super-long-path");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme7) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_search("target=self");
+  ASSERT_EQ(url->get_search(), "?target=self");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, readme8) {
+  auto url = ada::parse<TypeParam>("https://www.google.com");
+  url->set_hash("is-this-the-real-life");
+  ASSERT_EQ(url->get_hash(), "#is-this-the-real-life");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, nodejs1) {
+  auto base = ada::parse<TypeParam>("http://other.com/");
+  ASSERT_TRUE(base.has_value());
+  auto url = ada::parse<TypeParam>("http://GOOgoo.com", &base.value());
+  ASSERT_TRUE(url.has_value());
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, nodejs2) {
+  auto url = ada::parse<TypeParam>("data:space    ?test");
+  ASSERT_EQ(url->get_search(), "?test");
+  url->set_search("");
+  ASSERT_EQ(url->get_search(), "");
+  ASSERT_EQ(url->get_pathname(), "space");
+  ASSERT_EQ(url->get_href(), "data:space");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, nodejs3) {
+  auto url = ada::parse<TypeParam>("data:space    ?test#test");
+  ASSERT_EQ(url->get_search(), "?test");
+  url->set_search("");
+  ASSERT_EQ(url->get_search(), "");
+  ASSERT_EQ(url->get_pathname(), "space    ");
+  ASSERT_EQ(url->get_href(), "data:space    #test");
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/46755
+TYPED_TEST(basic_tests, nodejs4) {
+  auto url = ada::parse<TypeParam>("file:///var/log/system.log");
+  url->set_href("http://0300.168.0xF0");
+  ASSERT_EQ(url->get_protocol(), "http:");
+  ASSERT_EQ(url->get_href(), "http://192.168.0.240/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, empty_url) {
+  auto url = ada::parse<TypeParam>("");
+  ASSERT_FALSE(url);
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, just_hash) {
+  auto url = ada::parse<TypeParam>("#x");
+  ASSERT_FALSE(url);
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, empty_host_dash_dash_path) {
+  auto url = ada::parse<TypeParam>("something:/.//");
+  ASSERT_TRUE(url);
+  ASSERT_FALSE(url->has_opaque_path);
+  ASSERT_EQ(url->get_href(), "something:/.//");
+  ASSERT_EQ(url->get_pathname(), "//");
+  ASSERT_EQ(url->get_hostname(), "");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, confusing_mess) {
+  auto base_url = ada::parse<TypeParam>("http://example.org/foo/bar");
+  ASSERT_TRUE(base_url);
+  auto url = ada::parse<TypeParam>("http://::@c@d:2", &*base_url);
+  ASSERT_TRUE(url);
+  ASSERT_FALSE(url->has_opaque_path);
+  ASSERT_EQ(url->get_hostname(), "d");
+  ASSERT_EQ(url->get_host(), "d:2");
+  ASSERT_EQ(url->get_pathname(), "/");
+  ASSERT_EQ(url->get_href(), "http://:%3A%40c@d:2/");
+  ASSERT_EQ(url->get_origin(), "http://d:2");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, standard_file) {
+  auto url = ada::parse<TypeParam>("file:///tmp/mock/path");
+  ASSERT_TRUE(url);
+  ASSERT_TRUE(url->has_empty_hostname());
+  ASSERT_FALSE(url->has_opaque_path);
+  ASSERT_EQ(url->get_pathname(), "/tmp/mock/path");
+  ASSERT_EQ(url->get_hostname(), "");
+  ASSERT_EQ(url->get_host(), "");
+  ASSERT_EQ(url->get_href(), "file:///tmp/mock/path");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, default_port_should_be_removed) {
+  auto url = ada::parse<TypeParam>("http://www.google.com:443");
+  ASSERT_TRUE(url);
+  url->set_protocol("https");
+  ASSERT_EQ(url->get_port(), "");
+  ASSERT_EQ(url->get_host(), "www.google.com");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, test_amazon) {
+  auto url = ada::parse<TypeParam>("HTTP://AMAZON.COM");
+  ASSERT_TRUE(url);
+  ASSERT_EQ(url->get_href(), "http://amazon.com/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, remove_username) {
+  auto url = ada::parse<TypeParam>("http://me@example.net");
+  ASSERT_TRUE(url);
+  url->set_username("");
+  ASSERT_EQ(url->get_username(), "");
+  ASSERT_EQ(url->get_href(), "http://example.net/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, remove_password) {
+  auto url = ada::parse<TypeParam>("http://user:pass@example.net");
+  ASSERT_TRUE(url);
+  url->set_password("");
+  ASSERT_EQ(url->get_password(), "");
+  ASSERT_EQ(url->get_href(), "http://user@example.net/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, remove_password_with_empty_username) {
+  auto url = ada::parse<TypeParam>("http://:pass@example.net");
+  ASSERT_TRUE(url);
+  url->set_password("");
+  ASSERT_EQ(url->get_username(), "");
+  ASSERT_EQ(url->get_password(), "");
+  ASSERT_EQ(url->get_href(), "http://example.net/");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, should_remove_dash_dot) {
+  auto url = ada::parse<TypeParam>("non-spec:/.//p");
+  ASSERT_TRUE(url);
+  ASSERT_FALSE(url->has_empty_hostname());
+  ASSERT_FALSE(url->has_hostname());
+  url->set_hostname("h");
+  ASSERT_TRUE(url->has_hostname());
+  ASSERT_FALSE(url->has_empty_hostname());
+  ASSERT_EQ(url->get_pathname(), "//p");
+  ASSERT_EQ(url->get_href(), "non-spec://h//p");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, should_remove_dash_dot_with_empty_hostname) {
+  auto url = ada::parse<TypeParam>("non-spec:/.//p");
+  ASSERT_TRUE(url);
+  ASSERT_EQ(url->get_pathname(), "//p");
+  ASSERT_FALSE(url->has_empty_hostname());
+  ASSERT_FALSE(url->has_hostname());
+  url->set_hostname("");
+  ASSERT_TRUE(url->has_hostname());
+  ASSERT_TRUE(url->has_empty_hostname());
+  ASSERT_EQ(url->get_pathname(), "//p");
+  ASSERT_EQ(url->get_href(), "non-spec:////p");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, should_add_dash_dot_on_pathname) {
+  auto url = ada::parse<TypeParam>("non-spec:/");
+  ASSERT_TRUE(url);
+  url->set_pathname("//p");
+  ASSERT_EQ(url->get_pathname(), "//p");
+  ASSERT_EQ(url->get_href(), "non-spec:/.//p");
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, should_update_password_correctly) {
+  auto url = ada::parse<TypeParam>(
+      "https://username:password@host:8000/path?query#fragment");
+  ASSERT_TRUE(url);
+  ASSERT_TRUE(url->set_password("test"));
+  ASSERT_EQ(url->get_password(), "test");
+  ASSERT_EQ(url->get_href(),
+            "https://username:test@host:8000/path?query#fragment");
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/47889
+TYPED_TEST(basic_tests, node_issue_47889) {
+  auto urlbase = ada::parse<TypeParam>("a:b");
+  ASSERT_EQ(urlbase->get_href(), "a:b");
+  ASSERT_EQ(urlbase->get_protocol(), "a:");
+  ASSERT_EQ(urlbase->get_pathname(), "b");
+  ASSERT_TRUE(urlbase->has_opaque_path);
+  ASSERT_TRUE(urlbase);
+  auto expected_url = ada::parse<TypeParam>("a:b#");
+  ASSERT_TRUE(expected_url);
+  ASSERT_TRUE(expected_url->has_opaque_path);
+  ASSERT_EQ(expected_url->get_href(), "a:b#");
+  ASSERT_EQ(expected_url->get_pathname(), "b");
+  auto url = ada::parse<TypeParam>("..#", &*urlbase);
+  ASSERT_TRUE(url);
+  ASSERT_TRUE(url->has_opaque_path);
+  ASSERT_EQ(url->get_href(), "a:b#");
+  ASSERT_EQ(url->get_pathname(), "b");
+  SUCCEED();
+}
+
+TEST(basic_tests, can_parse) {
+  ASSERT_TRUE(ada::can_parse("https://www.yagiz.co"));
+  std::string_view base = "https://yagiz.co";
+  ASSERT_TRUE(ada::can_parse("/hello", &base));
+
+  std::string_view invalid_base = "!!!!!!!1";
+  ASSERT_FALSE(ada::can_parse("/hello", &invalid_base));
+  ASSERT_FALSE(ada::can_parse("!!!"));
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, node_issue_48254) {
+  auto base_url = ada::parse<TypeParam>("localhost:80");
+  ASSERT_TRUE(base_url);
+  ASSERT_EQ(base_url->get_hostname(), "");
+  ASSERT_EQ(base_url->get_host(), "");
+  ASSERT_EQ(base_url->get_pathname(), "80");
+  ASSERT_EQ(base_url->get_href(), "localhost:80");
+  ASSERT_EQ(base_url->get_origin(), "null");
+  ASSERT_EQ(base_url->has_opaque_path, true);
+  auto url = ada::parse<TypeParam>("", &*base_url);
+  ASSERT_FALSE(url);
+  SUCCEED();
+}
+
+TYPED_TEST(basic_tests, url_host_type) {
+  ASSERT_EQ(ada::parse<TypeParam>("http://localhost:3000")->host_type,
+            ada::url_host_type::DEFAULT);
+  ASSERT_EQ(ada::parse<TypeParam>("http://0.0.0.0")->host_type,
+            ada::url_host_type::IPV4);
+  ASSERT_EQ(
+      ada::parse<TypeParam>("http://[2001:db8:3333:4444:5555:6666:7777:8888]")
+          ->host_type,
+      ada::url_host_type::IPV6);
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/49650
+TYPED_TEST(basic_tests, nodejs_49650) {
+  auto out = ada::parse<TypeParam>("http://foo");
+  ASSERT_TRUE(out);
+  ASSERT_FALSE(out->set_host("::"));
+  ASSERT_EQ(out->get_href(), "http://foo/");
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/50235
+TYPED_TEST(basic_tests, nodejs_50235) {
+  auto out = ada::parse<TypeParam>("http://test.com:5/?param=1");
+  ASSERT_TRUE(out);
+  ASSERT_TRUE(out->set_pathname("path"));
+  ASSERT_EQ(out->get_href(), "http://test.com:5/path?param=1");
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/51514
+TYPED_TEST(basic_tests, nodejs_51514) {
+  auto out = ada::parse<TypeParam>("http://1.1.1.256");
+  ASSERT_FALSE(out);
+}
+
+// https://github.com/nodejs/node/issues/51593
+TYPED_TEST(basic_tests, nodejs_51593) {
+  auto out = ada::parse<TypeParam>("http://\u200b123.123.123.123");
+  ASSERT_TRUE(out);
+  ASSERT_EQ(out->get_href(), "http://123.123.123.123/");
+  SUCCEED();
+}
+
+// https://github.com/nodejs/node/issues/51619
+TYPED_TEST(basic_tests, nodejs_51619) {
+  auto out = ada::parse<TypeParam>("https://0.0.0.0x100/");
+  ASSERT_FALSE(out);
+  SUCCEED();
+}
+
+// https://github.com/nodejs/undici/pull/2971
+TYPED_TEST(basic_tests, nodejs_undici_2971) {
+  std::string_view base =
+      "https://non-ascii-location-header.sys.workers.dev/redirect";
+  auto base_url = ada::parse<TypeParam>(base);
+  ASSERT_TRUE(base_url);
+  auto out = ada::parse<TypeParam>("/\xec\x95\x88\xeb\x85\x95", &*base_url);
+  ASSERT_TRUE(out);
+  ASSERT_EQ(
+      out->get_href(),
+      R"(https://non-ascii-location-header.sys.workers.dev/%EC%95%88%EB%85%95)");
+  SUCCEED();
+}
\ No newline at end of file
diff --git a/tests/from_file_tests.cpp b/tests/from_file_tests.cpp
new file mode 100644 (file)
index 0000000..1f10fa8
--- /dev/null
@@ -0,0 +1,18 @@
+#include "ada.h"
+#include <cstdlib>
+#include <iostream>
+#include "gtest/gtest.h"
+
+std::string long_way(std::string path) {
+  ada::result<ada::url> base = ada::parse<ada::url>("file://");
+  base->set_pathname(path);
+  return base->get_href();
+}
+
+TEST(from_file_tests, basics) {
+  for (std::string path :
+       {"", "fsfds", "C:\\\\blabala\\fdfds\\back.txt", "/home/user/txt.txt",
+        "/%2e.bar", "/foo/%2e%2", "/foo/..bar", "foo\t%91"}) {
+    ASSERT_TRUE(long_way(path) == ada::href_from_file(path));
+  }
+}
diff --git a/tests/installation/CMakeLists.txt b/tests/installation/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8e8759c
--- /dev/null
@@ -0,0 +1,24 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(test_ada_install VERSION 0.1.0 LANGUAGES CXX)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+find_package(ada REQUIRED)
+
+# You can provide your own code, this is just an example:
+file(WRITE main.cpp "
+#include \"ada.h\"
+#include <iostream>
+
+int main(int , char *[]) {
+  ada::result<ada::url_aggregator> url = ada::parse<ada::url_aggregator>(\"https://www.google.com\");
+  url->set_protocol(\"http\");
+  std::cout << url->get_protocol() << std::endl;
+  std::cout << url->get_host() << std::endl;
+  return EXIT_SUCCESS;
+}")
+
+add_executable(main main.cpp)
+target_link_libraries(main PUBLIC ada::ada)
diff --git a/tests/url_components.cpp b/tests/url_components.cpp
new file mode 100644 (file)
index 0000000..4dfe000
--- /dev/null
@@ -0,0 +1,145 @@
+#include <cstring>
+#include <filesystem>
+#include <iostream>
+#include <memory>
+#include <set>
+
+#include "simdjson.h"
+#include "gtest/gtest.h"
+#include "ada.h"
+#include "ada/url_components.h"
+
+using namespace simdjson;
+
+#ifndef WPT_DATA_DIR
+#define WPT_DATA_DIR "wpt/"
+#endif
+const char* URLTESTDATA_JSON = WPT_DATA_DIR "urltestdata.json";
+
+// This function copies your input onto a memory buffer that
+// has just the necessary size. This will entice tools to detect
+// an out-of-bound access.
+ada::result<ada::url> ada_parse(std::string_view view,
+                                const ada::url* base = nullptr) {
+  std::cout << "about to parse '" << view << "' [" << view.size() << " bytes]"
+            << std::endl;
+  std::unique_ptr<char[]> buffer(new char[view.size()]);
+  memcpy(buffer.get(), view.data(), view.size());
+  return ada::parse(std::string_view(buffer.get(), view.size()), base);
+}
+
+bool file_exists(const char* filename) {
+  namespace fs = std::filesystem;
+  std::filesystem::path f{filename};
+  if (std::filesystem::exists(filename)) {
+    std::cout << "  file found: " << filename << std::endl;
+    return true;
+  } else {
+    std::cout << "  file missing: " << filename << std::endl;
+    return false;
+  }
+}
+
+TEST(url_components, urltestdata_encoding) {
+  ondemand::parser parser;
+  size_t counter{};
+  ASSERT_TRUE(file_exists(URLTESTDATA_JSON));
+  padded_string json = padded_string::load(URLTESTDATA_JSON);
+  ondemand::document doc = parser.iterate(json);
+  for (auto element : doc.get_array()) {
+    if (element.type() == ondemand::json_type::string) {
+      std::string_view comment = element.get_string().value();
+      std::cout << comment << std::endl;
+    } else if (element.type() == ondemand::json_type::object) {
+      ondemand::object object = element.get_object();
+      std::string element_string =
+          std::string(std::string_view(object.raw_json()));
+      object.reset();
+
+      auto input_element = object["input"];
+      std::string_view input{};
+      bool allow_replacement_characters = true;
+      ASSERT_FALSE(
+          input_element.get_string(allow_replacement_characters).get(input));
+      std::cout << "input='" << input << "' [" << input.size() << " bytes]"
+                << std::endl;
+      std::string_view base;
+      ada::result<ada::url> base_url;
+      if (!object["base"].get(base)) {
+        std::cout << "base=" << base << std::endl;
+        base_url = ada_parse(base);
+        if (!base_url) {
+          bool failure = false;
+          if (!object["failure"].get(failure) && failure == true) {
+            // We are good. Failure was expected.
+            continue;  // We can't proceed any further.
+          } else {
+            ASSERT_TRUE(base_url.has_value());
+          }
+        }
+      }
+      bool failure = false;
+      ada::result<ada::url> input_url = (!object["base"].get(base))
+                                            ? ada_parse(input, &*base_url)
+                                            : ada_parse(input);
+
+      if (object["failure"].get(failure)) {
+        auto url = input_url.value();
+        auto out = url.get_components();
+        auto href = url.get_href();
+
+        ASSERT_EQ(href.substr(0, out.protocol_end), url.get_protocol());
+
+        if (!url.username.empty()) {
+          size_t username_start = href.find(url.username);
+          ASSERT_EQ(href.substr(username_start, url.username.size()),
+                    url.get_username());
+        }
+
+        if (!url.password.empty()) {
+          size_t password_start = out.username_end + 1;
+          ASSERT_EQ(href.substr(password_start, url.password.size()),
+                    url.get_password());
+        }
+
+        size_t host_start = out.host_start;
+        if (url.has_credentials()) {
+          ASSERT_EQ(url.get_href()[out.host_start], '@');
+          host_start++;
+        }
+        ASSERT_EQ(href.substr(host_start, url.get_hostname().size()),
+                  url.get_hostname());
+
+        if (url.port.has_value()) {
+          ASSERT_EQ(out.port, url.port.value());
+        } else {
+          ASSERT_EQ(out.port, ada::url_components::omitted);
+        }
+
+        if (!url.get_pathname().empty()) {
+          size_t pathname_end = std::string::npos;
+          if (out.search_start != ada::url_components::omitted) {
+            pathname_end = out.search_start;
+          } else if (out.hash_start != ada::url_components::omitted) {
+            pathname_end = out.hash_start;
+          }
+          ASSERT_EQ(href.substr(out.pathname_start,
+                                pathname_end - out.pathname_start),
+                    url.get_pathname());
+        }
+
+        if (!url.get_search().empty()) {
+          ASSERT_EQ(href.substr(out.search_start, url.get_search().size()),
+                    url.get_search());
+        }
+
+        if (!url.get_hash().empty()) {
+          ASSERT_EQ(href.substr(out.hash_start, url.get_hash().size()),
+                    url.get_hash());
+        }
+      }
+    }
+  }
+  std::cout << "Tests executed = " << counter << std::endl;
+  SUCCEED();
+}
diff --git a/tests/url_search_params.cpp b/tests/url_search_params.cpp
new file mode 100644 (file)
index 0000000..a9002ab
--- /dev/null
@@ -0,0 +1,259 @@
+#include "ada.h"
+#include "gtest/gtest.h"
+
+TEST(url_search_params, append) {
+  auto search_params = ada::url_search_params();
+  search_params.append("key", "value");
+  ASSERT_EQ(search_params.size(), 1);
+  ASSERT_TRUE(search_params.has("key"));
+  search_params.append("key", "value2");
+  ASSERT_EQ(search_params.size(), 2);
+  ASSERT_EQ(search_params.get_all("key").size(), 2);
+  SUCCEED();
+}
+
+TEST(url_search_params, to_string) {
+  auto search_params = ada::url_search_params();
+  search_params.append("key1", "value1");
+  search_params.append("key2", "value2");
+  ASSERT_EQ(search_params.size(), 2);
+  ASSERT_EQ(search_params.to_string(), "key1=value1&key2=value2");
+  SUCCEED();
+}
+
+TEST(url_search_params, with_accents) {
+  auto search_params = ada::url_search_params();
+  search_params.append("key1", "été");
+  search_params.append("key2", "Céline Dion++");
+  ASSERT_EQ(search_params.size(), 2);
+  ASSERT_EQ(search_params.to_string(),
+            "key1=%C3%A9t%C3%A9&key2=C%C3%A9line+Dion%2B%2B");
+  ASSERT_EQ(search_params.get("key1"), "été");
+  ASSERT_EQ(search_params.get("key2"), "Céline Dion++");
+  SUCCEED();
+}
+
+/**
+ * @see
+ * https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-stringifier.any.js
+ */
+TEST(url_search_params, to_string_serialize_space) {
+  auto params = ada::url_search_params();
+  params.append("a", "b c");
+  ASSERT_EQ(params.to_string(), "a=b+c");
+  ASSERT_EQ(params.get("a").value(), "b c");
+  params.remove("a");
+  params.append("a b", "c");
+  ASSERT_EQ(params.to_string(), "a+b=c");
+  params.remove("a b");
+  ASSERT_EQ(params.to_string(), "");
+  params.append("a", "");
+  ASSERT_EQ(params.to_string(), "a=");
+  params.append("", "");
+  ASSERT_EQ(params.to_string(), "a=&=");
+  params.append("", "b");
+  ASSERT_EQ(params.to_string(), "a=&=&=b");
+  SUCCEED();
+}
+
+TEST(url_search_params, to_string_serialize_plus) {
+  auto params = ada::url_search_params();
+  params.append("a", "b+c");
+  ASSERT_EQ(params.to_string(), "a=b%2Bc");
+  params.remove("a");
+  params.append("a+b", "c");
+  ASSERT_EQ(params.to_string(), "a%2Bb=c");
+  SUCCEED();
+}
+
+TEST(url_search_params, to_string_serialize_ampersand) {
+  auto params = ada::url_search_params();
+  params.append("&", "a");
+  ASSERT_EQ(params.to_string(), "%26=a");
+  params.append("b", "&");
+  ASSERT_EQ(params.to_string(), "%26=a&b=%26");
+  SUCCEED();
+}
+
+TEST(url_search_params, set) {
+  auto search_params = ada::url_search_params();
+  search_params.append("key1", "value1");
+  search_params.append("key1", "value2");
+  ASSERT_EQ(search_params.size(), 2);
+  search_params.set("key1", "hello");
+  ASSERT_EQ(search_params.size(), 1);
+  ASSERT_EQ(search_params.to_string(), "key1=hello");
+
+  // reset to initial state
+  search_params.remove("key1");
+  search_params.append("key1", "value1");
+  search_params.append("key1", "value2");
+  search_params.append("key2", "value1");
+  search_params.set("key1", "value3");
+  ASSERT_EQ(search_params.size(), 2);
+  ASSERT_EQ(search_params.to_string(), "key1=value3&key2=value1");
+  search_params.set("key1", "value4");
+  ASSERT_EQ(search_params.to_string(), "key1=value4&key2=value1");
+
+  SUCCEED();
+}
+
+TEST(url_search_params, remove) {
+  auto search_params = ada::url_search_params();
+  search_params.append("key1", "value1");
+  search_params.append("key1", "value2");
+  search_params.append("key2", "value2");
+  search_params.remove("key2");
+  ASSERT_EQ(search_params.size(), 2);
+  ASSERT_EQ(search_params.to_string(), "key1=value1&key1=value2");
+  search_params.remove("key1", "value2");
+  ASSERT_EQ(search_params.size(), 1);
+  ASSERT_EQ(search_params.to_string(), "key1=value1");
+  SUCCEED();
+}
+
+TEST(url_search_params, sort) {
+  auto search_params = ada::url_search_params();
+  search_params.append("bbb", "second");
+  search_params.append("aaa", "first");
+  search_params.append("ccc", "third");
+  ASSERT_EQ(search_params.size(), 3);
+  search_params.sort();
+  ASSERT_EQ(search_params.to_string(), "aaa=first&bbb=second&ccc=third");
+  SUCCEED();
+}
+
+TEST(url_search_params, string_constructor) {
+  auto p = ada::url_search_params("?a=b");
+  ASSERT_EQ(p.to_string(), "a=b");
+  SUCCEED();
+}
+
+TEST(url_search_params, string_constructor_with_empty_input) {
+  auto p = ada::url_search_params("");
+  ASSERT_EQ(p.to_string(), "");
+  ASSERT_EQ(p.size(), 0);
+  SUCCEED();
+}
+
+TEST(url_search_params, string_constructor_without_value) {
+  auto p = ada::url_search_params("a=b&c");
+  ASSERT_EQ(p.to_string(), "a=b&c=");
+  SUCCEED();
+}
+
+// Taken from
+// https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js
+TEST(url_search_params, string_constructor_with_edge_cases) {
+  auto p = ada::url_search_params("&a&&& &&&&&a+b=& c&m%c3%b8%c3%b8");
+  p.to_string();
+  ASSERT_TRUE(p.has("a"));
+  ASSERT_TRUE(p.has("a b"));
+  ASSERT_TRUE(p.has(" "));
+  ASSERT_TRUE(!p.has("c"));
+  ASSERT_TRUE(p.has(" c"));
+  ASSERT_TRUE(p.has("møø"));
+  SUCCEED();
+}
+
+TEST(url_search_params, has) {
+  auto search_params = ada::url_search_params("key1=value1&key2=value2");
+  ASSERT_TRUE(search_params.has("key1"));
+  ASSERT_TRUE(search_params.has("key2"));
+  ASSERT_TRUE(search_params.has("key1", "value1"));
+  ASSERT_TRUE(search_params.has("key2", "value2"));
+  ASSERT_TRUE(!search_params.has("key3"));
+  ASSERT_TRUE(!search_params.has("key1", "value2"));
+  ASSERT_TRUE(!search_params.has("key3", "value3"));
+  SUCCEED();
+}
+
+TEST(url_search_params, iterators) {
+  // JS style iterators
+  auto search_params =
+      ada::url_search_params("key1=value1&key1=value2&key2=value3");
+  auto keys = search_params.get_keys();
+  ASSERT_EQ(keys.next(), "key1");
+  ASSERT_EQ(keys.next(), "key1");
+  ASSERT_EQ(keys.next(), "key2");
+  ASSERT_FALSE(keys.next().has_value());
+
+  auto values = search_params.get_values();
+  ASSERT_EQ(values.next(), "value1");
+  ASSERT_EQ(values.next(), "value2");
+  ASSERT_EQ(values.next(), "value3");
+  ASSERT_FALSE(keys.next().has_value());
+
+  auto entries = search_params.get_entries();
+  auto next = entries.next();
+  ASSERT_EQ(next->first, "key1");
+  ASSERT_EQ(next->second, "value1");
+  next = entries.next();
+  ASSERT_EQ(next->first, "key1");
+  ASSERT_EQ(next->second, "value2");
+  next = entries.next();
+  ASSERT_EQ(next->first, "key2");
+  ASSERT_EQ(next->second, "value3");
+  // At this point we can add a new entry and the iterator will pick it up.
+  search_params.append("foo", "bar");
+  next = entries.next();
+  ASSERT_EQ(next->first, "foo");
+  ASSERT_EQ(next->second, "bar");
+
+  ASSERT_FALSE(entries.next().has_value());
+
+  // C++ conventional iterator
+  std::vector<std::pair<std::string, std::string>> expected = {
+      {"foo", "bar"},
+      {"key2", "value3"},
+      {"key1", "value2"},
+      {"key1", "value1"},
+  };
+  for (auto& entry : search_params) {
+    auto check = expected.back();
+    expected.pop_back();
+    ASSERT_EQ(check.first, entry.first);
+    ASSERT_EQ(check.second, entry.second);
+  }
+  ASSERT_EQ(expected.size(), 0);
+
+  SUCCEED();
+}
+
+// https://github.com/cloudflare/workerd/issues/1777
+TEST(url_search_params, test_to_string_encoding) {
+  auto search_params =
+      ada::url_search_params("q1=foo&q2=foo+bar&q3=foo bar&q4=foo/bar");
+  ASSERT_EQ(search_params.get("q1").value(), "foo");
+  ASSERT_EQ(search_params.get("q2").value(), "foo bar");
+  ASSERT_EQ(search_params.get("q3").value(), "foo bar");
+  ASSERT_EQ(search_params.get("q4").value(), "foo/bar");
+  ASSERT_EQ(search_params.to_string(),
+            "q1=foo&q2=foo+bar&q3=foo+bar&q4=foo%2Fbar");
+  SUCCEED();
+}
+
+// https://github.com/cloudflare/workerd/issues/1777
+TEST(url_search_params, test_character_set) {
+  auto search_params = ada::url_search_params("key=value");
+
+  // - The application/x-www-form-urlencoded percent-encode set is the component
+  // percent-encode set and U+0021 (!), U+0027 (') to U+0029 RIGHT PARENTHESIS,
+  // inclusive, and U+007E (~).
+  // - The component percent-encode set is the userinfo percent-encode set and
+  // U+0024 ($) to U+0026 (&), inclusive, U+002B (+), and U+002C (,).
+  // - The userinfo percent-encode set is the path percent-encode set and U+002F
+  // (/), U+003A (:), U+003B (;), U+003D (=), U+0040 (@), U+005B ([) to U+005E
+  // (^), inclusive, and U+007C (|).
+  std::vector<char> unique_keys = {'/', ':', ';', '=', '@', '[',  ']', '^', '|',
+                                   '$', '&', '+', ',', '!', '\'', ')', '~'};
+  for (auto& unique_key : unique_keys) {
+    auto value = "value" + std::string(1, unique_key);
+    search_params.set("key", value);
+    // Getting should return the same thing.
+    ASSERT_EQ(search_params.get("key").value(), value);
+    // Stringified version should be percent encoded.
+    ASSERT_NE(search_params.to_string(), "key=" + value);
+  }
+  SUCCEED();
+}
diff --git a/tests/wasm/CMakeLists.txt b/tests/wasm/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c57fe33
--- /dev/null
@@ -0,0 +1,13 @@
+link_libraries(ada)
+# Node
+add_executable(wasm-node wasm.cpp)
+set_target_properties(wasm-node PROPERTIES LINK_FLAGS "-Os -s ENVIRONMENT=node -s EXPORT_NAME=loadWASM -s MODULARIZE=1 --bind")
+
+configure_file(test.js.in test.js)
+
+find_program(NODEJS_BINARY NAMES node nodejs)
+if(NODEJS_BINARY)
+  add_test(NAME wasmtest
+         COMMAND "${NODEJS_BINARY}" "${CMAKE_CURRENT_BINARY_DIR}/test.js")
+endif(NODEJS_BINARY)
+
diff --git a/tests/wasm/test.js.in b/tests/wasm/test.js.in
new file mode 100644 (file)
index 0000000..9306691
--- /dev/null
@@ -0,0 +1,33 @@
+const assert = require('node:assert');
+const test = require('node:test');
+const wasm = require('${CMAKE_CURRENT_BINARY_DIR}/wasm-node');
+
+function toJS(obj) {
+  const result = {};
+  for (const key of Object.keys(obj.__proto__)) {
+       result[key] = typeof obj[key] === "object" ? toJS(obj[key]) : obj[key];
+  }
+  return result;
+}
+
+const expected = {
+  "result": "success",
+  "href": "https://google.com/?q=Yagiz#Nizipli",
+  "type": 2,
+  "components": {
+    "protocol_end": 6,
+    "username_end": 8,
+    "host_start": 8,
+    "host_end": 18,
+    "port": 4294967295,
+    "pathname_start": 18,
+    "search_start": 19,
+    "hash_start": 27
+  }
+};
+
+test('wasm', async () => {
+  const { parse } = await wasm();
+  assert.deepStrictEqual(toJS(parse('https://google.com/?q=Yagiz#Nizipli')), expected);
+});
+
diff --git a/tests/wasm/wasm.cpp b/tests/wasm/wasm.cpp
new file mode 100644 (file)
index 0000000..3df8e5a
--- /dev/null
@@ -0,0 +1,45 @@
+#include "ada.h"
+#include <emscripten/emscripten.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+struct parse_result {
+  std::string result;
+  std::string href;
+  uint32_t type;
+  ada::url_components components;
+};
+
+parse_result parse(const std::string &input) {
+  auto out = ada::parse<ada::url_aggregator>(input);
+  parse_result result;
+  if (!out.has_value()) {
+    result.result = "fail";
+  } else {
+    result.result = "success";
+    result.href = std::string(out->get_href());
+    result.type = out->type;
+    result.components = out->get_components();
+  }
+  return result;
+}
+
+EMSCRIPTEN_BINDINGS(url_components) {
+  class_<parse_result>("Result")
+      .property("result", &parse_result::result)
+      .property("href", &parse_result::href)
+      .property("type", &parse_result::type)
+      .property("components", &parse_result::components);
+  class_<ada::url_components>("URLComponents")
+      .property("protocol_end", &ada::url_components::protocol_end)
+      .property("username_end", &ada::url_components::username_end)
+      .property("host_start", &ada::url_components::host_start)
+      .property("host_end", &ada::url_components::host_end)
+      .property("port", &ada::url_components::port)
+      .property("pathname_start", &ada::url_components::pathname_start)
+      .property("search_start", &ada::url_components::search_start)
+      .property("hash_start", &ada::url_components::hash_start);
+
+  function("parse", &parse);
+}
\ No newline at end of file
diff --git a/tests/wpt/CMakeLists.txt b/tests/wpt/CMakeLists.txt
new file mode 100644 (file)
index 0000000..06011c2
--- /dev/null
@@ -0,0 +1,5 @@
+
+file(GLOB_RECURSE wpt_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.json)
+foreach(wpt_file ${wpt_files})
+  configure_file(${wpt_file} ${wpt_file} COPYONLY)
+endforeach(wpt_file)
diff --git a/tests/wpt/IdnaTestV2.json b/tests/wpt/IdnaTestV2.json
new file mode 100644 (file)
index 0000000..669d4b0
--- /dev/null
@@ -0,0 +1,9754 @@
+[
+  "THIS IS A GENERATED FILE. PLEASE DO NOT MODIFY DIRECTLY. See ../tools/IdnaTestV2-parser.py instead.",
+  "--exclude-ipv4-like: True; --exclude-std3: True; --exclude_bidi: True",
+  {
+    "input": "fass.de",
+    "output": "fass.de"
+  },
+  {
+    "input": "fa\u00df.de",
+    "output": "xn--fa-hia.de"
+  },
+  {
+    "input": "Fa\u00df.de",
+    "output": "xn--fa-hia.de"
+  },
+  {
+    "input": "xn--fa-hia.de",
+    "output": "xn--fa-hia.de"
+  },
+  {
+    "input": "\u00e0.\u05d0\u0308",
+    "output": "xn--0ca.xn--ssa73l"
+  },
+  {
+    "input": "a\u0300.\u05d0\u0308",
+    "output": "xn--0ca.xn--ssa73l"
+  },
+  {
+    "input": "A\u0300.\u05d0\u0308",
+    "output": "xn--0ca.xn--ssa73l"
+  },
+  {
+    "input": "\u00c0.\u05d0\u0308",
+    "output": "xn--0ca.xn--ssa73l"
+  },
+  {
+    "input": "xn--0ca.xn--ssa73l",
+    "output": "xn--0ca.xn--ssa73l"
+  },
+  {
+    "input": "\u00e0\u0308.\u05d0",
+    "output": "xn--0ca81i.xn--4db"
+  },
+  {
+    "input": "a\u0300\u0308.\u05d0",
+    "output": "xn--0ca81i.xn--4db"
+  },
+  {
+    "input": "A\u0300\u0308.\u05d0",
+    "output": "xn--0ca81i.xn--4db"
+  },
+  {
+    "input": "\u00c0\u0308.\u05d0",
+    "output": "xn--0ca81i.xn--4db"
+  },
+  {
+    "input": "xn--0ca81i.xn--4db",
+    "output": "xn--0ca81i.xn--4db"
+  },
+  {
+    "comment": "C1",
+    "input": "a\u200cb",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "A\u200cB",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "A\u200cb",
+    "output": null
+  },
+  {
+    "input": "ab",
+    "output": "ab"
+  },
+  {
+    "comment": "C1",
+    "input": "xn--ab-j1t",
+    "output": null
+  },
+  {
+    "input": "a\u094d\u200cb",
+    "output": "xn--ab-fsf604u"
+  },
+  {
+    "input": "A\u094d\u200cB",
+    "output": "xn--ab-fsf604u"
+  },
+  {
+    "input": "A\u094d\u200cb",
+    "output": "xn--ab-fsf604u"
+  },
+  {
+    "input": "xn--ab-fsf",
+    "output": "xn--ab-fsf"
+  },
+  {
+    "input": "a\u094db",
+    "output": "xn--ab-fsf"
+  },
+  {
+    "input": "A\u094dB",
+    "output": "xn--ab-fsf"
+  },
+  {
+    "input": "A\u094db",
+    "output": "xn--ab-fsf"
+  },
+  {
+    "input": "xn--ab-fsf604u",
+    "output": "xn--ab-fsf604u"
+  },
+  {
+    "comment": "C2",
+    "input": "a\u200db",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "A\u200dB",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "A\u200db",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "xn--ab-m1t",
+    "output": null
+  },
+  {
+    "input": "a\u094d\u200db",
+    "output": "xn--ab-fsf014u"
+  },
+  {
+    "input": "A\u094d\u200dB",
+    "output": "xn--ab-fsf014u"
+  },
+  {
+    "input": "A\u094d\u200db",
+    "output": "xn--ab-fsf014u"
+  },
+  {
+    "input": "xn--ab-fsf014u",
+    "output": "xn--ab-fsf014u"
+  },
+  {
+    "input": "\u00a1",
+    "output": "xn--7a"
+  },
+  {
+    "input": "xn--7a",
+    "output": "xn--7a"
+  },
+  {
+    "input": "\u19da",
+    "output": "xn--pkf"
+  },
+  {
+    "input": "xn--pkf",
+    "output": "xn--pkf"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\u3002",
+    "output": "."
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".",
+    "output": "."
+  },
+  {
+    "input": "\uab60",
+    "output": "xn--3y9a"
+  },
+  {
+    "input": "xn--3y9a",
+    "output": "xn--3y9a"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1234567890\u00e41234567890123456789012345678901234567890123456",
+    "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1234567890a\u03081234567890123456789012345678901234567890123456",
+    "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1234567890A\u03081234567890123456789012345678901234567890123456",
+    "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1234567890\u00c41234567890123456789012345678901234567890123456",
+    "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "xn--12345678901234567890123456789012345678901234567890123456-fxe",
+    "output": "xn--12345678901234567890123456789012345678901234567890123456-fxe"
+  },
+  {
+    "input": "www.eXample.cOm",
+    "output": "www.example.com"
+  },
+  {
+    "input": "B\u00fccher.de",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "Bu\u0308cher.de",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "bu\u0308cher.de",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "b\u00fccher.de",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "B\u00dcCHER.DE",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "BU\u0308CHER.DE",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "xn--bcher-kva.de",
+    "output": "xn--bcher-kva.de"
+  },
+  {
+    "input": "\u00d6BB",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "O\u0308BB",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "o\u0308bb",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "\u00f6bb",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "\u00d6bb",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "O\u0308bb",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "xn--bb-eka",
+    "output": "xn--bb-eka"
+  },
+  {
+    "input": "\u03b2\u03cc\u03bb\u03bf\u03c2.com",
+    "output": "xn--nxasmm1c.com"
+  },
+  {
+    "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c2.com",
+    "output": "xn--nxasmm1c.com"
+  },
+  {
+    "input": "\u0392\u039f\u0301\u039b\u039f\u03a3.COM",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u0392\u038c\u039b\u039f\u03a3.COM",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u03b2\u03cc\u03bb\u03bf\u03c3.com",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c3.com",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c3.com",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u0392\u03cc\u03bb\u03bf\u03c3.com",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "xn--nxasmq6b.com",
+    "output": "xn--nxasmq6b.com"
+  },
+  {
+    "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c2.com",
+    "output": "xn--nxasmm1c.com"
+  },
+  {
+    "input": "\u0392\u03cc\u03bb\u03bf\u03c2.com",
+    "output": "xn--nxasmm1c.com"
+  },
+  {
+    "input": "xn--nxasmm1c.com",
+    "output": "xn--nxasmm1c.com"
+  },
+  {
+    "input": "xn--nxasmm1c",
+    "output": "xn--nxasmm1c"
+  },
+  {
+    "input": "\u03b2\u03cc\u03bb\u03bf\u03c2",
+    "output": "xn--nxasmm1c"
+  },
+  {
+    "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c2",
+    "output": "xn--nxasmm1c"
+  },
+  {
+    "input": "\u0392\u039f\u0301\u039b\u039f\u03a3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u0392\u038c\u039b\u039f\u03a3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u03b2\u03cc\u03bb\u03bf\u03c3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u03b2\u03bf\u0301\u03bb\u03bf\u03c3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u0392\u03cc\u03bb\u03bf\u03c3",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "xn--nxasmq6b",
+    "output": "xn--nxasmq6b"
+  },
+  {
+    "input": "\u0392\u03cc\u03bb\u03bf\u03c2",
+    "output": "xn--nxasmm1c"
+  },
+  {
+    "input": "\u0392\u03bf\u0301\u03bb\u03bf\u03c2",
+    "output": "xn--nxasmm1c"
+  },
+  {
+    "input": "www.\u0dc1\u0dca\u200d\u0dbb\u0dd3.com",
+    "output": "www.xn--10cl1a0b660p.com"
+  },
+  {
+    "input": "WWW.\u0dc1\u0dca\u200d\u0dbb\u0dd3.COM",
+    "output": "www.xn--10cl1a0b660p.com"
+  },
+  {
+    "input": "Www.\u0dc1\u0dca\u200d\u0dbb\u0dd3.com",
+    "output": "www.xn--10cl1a0b660p.com"
+  },
+  {
+    "input": "www.xn--10cl1a0b.com",
+    "output": "www.xn--10cl1a0b.com"
+  },
+  {
+    "input": "www.\u0dc1\u0dca\u0dbb\u0dd3.com",
+    "output": "www.xn--10cl1a0b.com"
+  },
+  {
+    "input": "WWW.\u0dc1\u0dca\u0dbb\u0dd3.COM",
+    "output": "www.xn--10cl1a0b.com"
+  },
+  {
+    "input": "Www.\u0dc1\u0dca\u0dbb\u0dd3.com",
+    "output": "www.xn--10cl1a0b.com"
+  },
+  {
+    "input": "www.xn--10cl1a0b660p.com",
+    "output": "www.xn--10cl1a0b660p.com"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc",
+    "output": "xn--mgba3gch31f060k"
+  },
+  {
+    "input": "xn--mgba3gch31f",
+    "output": "xn--mgba3gch31f"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u0627\u06cc",
+    "output": "xn--mgba3gch31f"
+  },
+  {
+    "input": "xn--mgba3gch31f060k",
+    "output": "xn--mgba3gch31f060k"
+  },
+  {
+    "input": "xn--mgba3gch31f060k.com",
+    "output": "xn--mgba3gch31f060k.com"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc.com",
+    "output": "xn--mgba3gch31f060k.com"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u200c\u0627\u06cc.COM",
+    "output": "xn--mgba3gch31f060k.com"
+  },
+  {
+    "input": "xn--mgba3gch31f.com",
+    "output": "xn--mgba3gch31f.com"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u0627\u06cc.com",
+    "output": "xn--mgba3gch31f.com"
+  },
+  {
+    "input": "\u0646\u0627\u0645\u0647\u0627\u06cc.COM",
+    "output": "xn--mgba3gch31f.com"
+  },
+  {
+    "input": "a.b\uff0ec\u3002d\uff61",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "a.b.c\u3002d\u3002",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "A.B.C\u3002D\u3002",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "A.b.c\u3002D\u3002",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "a.b.c.d.",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "A.B\uff0eC\u3002D\uff61",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "A.b\uff0ec\u3002D\uff61",
+    "output": "a.b.c.d."
+  },
+  {
+    "input": "U\u0308.xn--tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00dc.xn--tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00fc.xn--tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "u\u0308.xn--tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "U\u0308.XN--TDA",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00dc.XN--TDA",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00dc.xn--Tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "U\u0308.xn--Tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "xn--tda.xn--tda",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00fc.\u00fc",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "u\u0308.u\u0308",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "U\u0308.U\u0308",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00dc.\u00dc",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "\u00dc.\u00fc",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "input": "U\u0308.u\u0308",
+    "output": "xn--tda.xn--tda"
+  },
+  {
+    "comment": "V1",
+    "input": "xn--u-ccb",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "a\u2488com",
+    "output": null
+  },
+  {
+    "input": "a1.com",
+    "output": "a1.com"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "A\u2488COM",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "A\u2488Com",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--acom-0w1b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--a-ecp.ru",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "xn--0.pt",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--a.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "xn--a-\u00c4.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "xn--a-A\u0308.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "xn--a-a\u0308.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "xn--a-\u00e4.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "XN--A-\u00c4.PT",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "XN--A-A\u0308.PT",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "Xn--A-A\u0308.pt",
+    "output": null
+  },
+  {
+    "comment": "P4",
+    "input": "Xn--A-\u00c4.pt",
+    "output": null
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "xn--xn--a--gua.pt",
+    "output": "xn--xn--a--gua.pt"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002\uff2a\uff30",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002JP",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002jp",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002Jp",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "xn--wgv71a119e.jp",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e.jp",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e.JP",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e.Jp",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002\uff4a\uff50",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u65e5\u672c\u8a9e\u3002\uff2a\uff50",
+    "output": "xn--wgv71a119e.jp"
+  },
+  {
+    "input": "\u2615",
+    "output": "xn--53h"
+  },
+  {
+    "input": "xn--53h",
+    "output": "xn--53h"
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.a\u00df\u200c\u200db\u200c\u200dc\u00df\u00df\u00df\u00dfd\u03c2\u03c3\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfe\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfx\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfy\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u0302\u00dfz",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.ASS\u200c\u200dB\u200c\u200dCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSS\u0302SSZ",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.ASS\u200c\u200dB\u200c\u200dCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSS\u015cSSZ",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.Ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.Ass\u200c\u200db\u200c\u200dcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.ASSBCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSSS\u0302SSZ",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.ASSBCSSSSSSSSD\u03a3\u03a3SSSSSSSSSSSSSSSSESSSSSSSSSSSSSSSSSSSSXSSSSSSSSSSSSSSSSSSSSYSSSSSSSSSSSSSSS\u015cSSZ",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.Assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssss\u015dssz",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "1.Assbcssssssssd\u03c3\u03c3ssssssssssssssssessssssssssssssssssssxssssssssssssssssssssyssssssssssssssss\u0302ssz",
+    "output": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa"
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.xn--assbcssssssssdssssssssssssssssessssssssssssssssssssxssssssssssssssssssssysssssssssssssssssz-pxq1419aa69989dba9gc",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.A\u00df\u200c\u200db\u200c\u200dc\u00df\u00df\u00df\u00dfd\u03c2\u03c3\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfe\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfx\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00dfy\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u00df\u0302\u00dfz",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; A4_2 (ignored)",
+    "input": "1.xn--abcdexyz-qyacaaabaaaaaaabaaaaaaaaabaaaaaaaaabaaaaaaaa010ze2isb1140zba8cc",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200cx\u200dn\u200c-\u200d-b\u00df",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200cX\u200dN\u200c-\u200d-BSS",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200cx\u200dn\u200c-\u200d-bss",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200cX\u200dn\u200c-\u200d-Bss",
+    "output": null
+  },
+  {
+    "input": "xn--bss",
+    "output": "xn--bss"
+  },
+  {
+    "input": "\u5919",
+    "output": "xn--bss"
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--xn--bss-7z6ccid",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200cX\u200dn\u200c-\u200d-B\u00df",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--xn--b-pqa5796ccahd",
+    "output": null
+  },
+  {
+    "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00\u017f\u2064\ud835\udd30\udb40\uddef\ufb04",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "x\u034fN\u200b-\u00ad-\u180cB\ufe00s\u2064s\udb40\uddefffl",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "x\u034fn\u200b-\u00ad-\u180cb\ufe00s\u2064s\udb40\uddefffl",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "X\u034fN\u200b-\u00ad-\u180cB\ufe00S\u2064S\udb40\uddefFFL",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "X\u034fn\u200b-\u00ad-\u180cB\ufe00s\u2064s\udb40\uddefffl",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "xn--bssffl",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "\u5921\u591e\u591c\u5919",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00S\u2064\ud835\udd30\udb40\uddefFFL",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "x\u034fN\u200b-\u00ad-\u180cB\ufe00S\u2064s\udb40\uddefFFL",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "\u02e3\u034f\u2115\u200b\ufe63\u00ad\uff0d\u180c\u212c\ufe00s\u2064\ud835\udd30\udb40\uddefffl",
+    "output": "xn--bssffl"
+  },
+  {
+    "input": "\u00e41234567890123456789012345678901234567890123456789012345",
+    "output": "xn--1234567890123456789012345678901234567890123456789012345-9te"
+  },
+  {
+    "input": "a\u03081234567890123456789012345678901234567890123456789012345",
+    "output": "xn--1234567890123456789012345678901234567890123456789012345-9te"
+  },
+  {
+    "input": "A\u03081234567890123456789012345678901234567890123456789012345",
+    "output": "xn--1234567890123456789012345678901234567890123456789012345-9te"
+  },
+  {
+    "input": "\u00c41234567890123456789012345678901234567890123456789012345",
+    "output": "xn--1234567890123456789012345678901234567890123456789012345-9te"
+  },
+  {
+    "input": "xn--1234567890123456789012345678901234567890123456789012345-9te",
+    "output": "xn--1234567890123456789012345678901234567890123456789012345-9te"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "a.b..-q--a-.e",
+    "output": "a.b..-q--a-.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "a.b..-q--\u00e4-.e",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "a.b..-q--a\u0308-.e",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "A.B..-Q--A\u0308-.E",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "A.B..-Q--\u00c4-.E",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "A.b..-Q--\u00c4-.E",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "A.b..-Q--A\u0308-.E",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored); A4_2 (ignored)",
+    "input": "a.b..xn---q----jra.e",
+    "output": "a.b..xn---q----jra.e"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "a..c",
+    "output": "a..c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a.-b.",
+    "output": "a.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a.b-.c",
+    "output": "a.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a.-.c",
+    "output": "a.-.c"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "a.bc--de.f",
+    "output": "a.bc--de.f"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\u00e4.\u00ad.c",
+    "output": "xn--4ca..c"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "a\u0308.\u00ad.c",
+    "output": "xn--4ca..c"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "A\u0308.\u00ad.C",
+    "output": "xn--4ca..c"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\u00c4.\u00ad.C",
+    "output": "xn--4ca..c"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "xn--4ca..c",
+    "output": "xn--4ca..c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00e4.-b.",
+    "output": "xn--4ca.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a\u0308.-b.",
+    "output": "xn--4ca.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "A\u0308.-B.",
+    "output": "xn--4ca.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00c4.-B.",
+    "output": "xn--4ca.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--4ca.-b.",
+    "output": "xn--4ca.-b."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00e4.b-.c",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a\u0308.b-.c",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "A\u0308.B-.C",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00c4.B-.C",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00c4.b-.C",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "A\u0308.b-.C",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--4ca.b-.c",
+    "output": "xn--4ca.b-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00e4.-.c",
+    "output": "xn--4ca.-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "a\u0308.-.c",
+    "output": "xn--4ca.-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "A\u0308.-.C",
+    "output": "xn--4ca.-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u00c4.-.C",
+    "output": "xn--4ca.-.c"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--4ca.-.c",
+    "output": "xn--4ca.-.c"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "\u00e4.bc--de.f",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "a\u0308.bc--de.f",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "A\u0308.BC--DE.F",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "\u00c4.BC--DE.F",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "\u00c4.bc--De.f",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "A\u0308.bc--De.f",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V2 (ignored)",
+    "input": "xn--4ca.bc--de.f",
+    "output": "xn--4ca.bc--de.f"
+  },
+  {
+    "comment": "V5",
+    "input": "a.b.\u0308c.d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "A.B.\u0308C.D",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "A.b.\u0308c.d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "a.b.xn--c-bcb.d",
+    "output": null
+  },
+  {
+    "input": "A0",
+    "output": "a0"
+  },
+  {
+    "input": "0A",
+    "output": "0a"
+  },
+  {
+    "input": "\u05d0\u05c7",
+    "output": "xn--vdbr"
+  },
+  {
+    "input": "xn--vdbr",
+    "output": "xn--vdbr"
+  },
+  {
+    "input": "\u05d09\u05c7",
+    "output": "xn--9-ihcz"
+  },
+  {
+    "input": "xn--9-ihcz",
+    "output": "xn--9-ihcz"
+  },
+  {
+    "input": "\u05d0\u05ea",
+    "output": "xn--4db6c"
+  },
+  {
+    "input": "xn--4db6c",
+    "output": "xn--4db6c"
+  },
+  {
+    "input": "\u05d0\u05f3\u05ea",
+    "output": "xn--4db6c0a"
+  },
+  {
+    "input": "xn--4db6c0a",
+    "output": "xn--4db6c0a"
+  },
+  {
+    "input": "\u05d07\u05ea",
+    "output": "xn--7-zhc3f"
+  },
+  {
+    "input": "xn--7-zhc3f",
+    "output": "xn--7-zhc3f"
+  },
+  {
+    "input": "\u05d0\u0667\u05ea",
+    "output": "xn--4db6c6t"
+  },
+  {
+    "input": "xn--4db6c6t",
+    "output": "xn--4db6c6t"
+  },
+  {
+    "input": "\u0bb9\u0bcd\u200d",
+    "output": "xn--dmc4b194h"
+  },
+  {
+    "input": "xn--dmc4b",
+    "output": "xn--dmc4b"
+  },
+  {
+    "input": "\u0bb9\u0bcd",
+    "output": "xn--dmc4b"
+  },
+  {
+    "input": "xn--dmc4b194h",
+    "output": "xn--dmc4b194h"
+  },
+  {
+    "comment": "C2",
+    "input": "\u0bb9\u200d",
+    "output": null
+  },
+  {
+    "input": "xn--dmc",
+    "output": "xn--dmc"
+  },
+  {
+    "input": "\u0bb9",
+    "output": "xn--dmc"
+  },
+  {
+    "comment": "C2",
+    "input": "xn--dmc225h",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "",
+    "output": ""
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1ug",
+    "output": null
+  },
+  {
+    "input": "\u0bb9\u0bcd\u200c",
+    "output": "xn--dmc4by94h"
+  },
+  {
+    "input": "xn--dmc4by94h",
+    "output": "xn--dmc4by94h"
+  },
+  {
+    "comment": "C1",
+    "input": "\u0bb9\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--dmc025h",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--0ug",
+    "output": null
+  },
+  {
+    "input": "\u0644\u0670\u200c\u06ed\u06ef",
+    "output": "xn--ghb2gxqia7523a"
+  },
+  {
+    "input": "xn--ghb2gxqia",
+    "output": "xn--ghb2gxqia"
+  },
+  {
+    "input": "\u0644\u0670\u06ed\u06ef",
+    "output": "xn--ghb2gxqia"
+  },
+  {
+    "input": "xn--ghb2gxqia7523a",
+    "output": "xn--ghb2gxqia7523a"
+  },
+  {
+    "input": "\u0644\u0670\u200c\u06ef",
+    "output": "xn--ghb2g3qq34f"
+  },
+  {
+    "input": "xn--ghb2g3q",
+    "output": "xn--ghb2g3q"
+  },
+  {
+    "input": "\u0644\u0670\u06ef",
+    "output": "xn--ghb2g3q"
+  },
+  {
+    "input": "xn--ghb2g3qq34f",
+    "output": "xn--ghb2g3qq34f"
+  },
+  {
+    "input": "\u0644\u200c\u06ed\u06ef",
+    "output": "xn--ghb25aga828w"
+  },
+  {
+    "input": "xn--ghb25aga",
+    "output": "xn--ghb25aga"
+  },
+  {
+    "input": "\u0644\u06ed\u06ef",
+    "output": "xn--ghb25aga"
+  },
+  {
+    "input": "xn--ghb25aga828w",
+    "output": "xn--ghb25aga828w"
+  },
+  {
+    "input": "\u0644\u200c\u06ef",
+    "output": "xn--ghb65a953d"
+  },
+  {
+    "input": "xn--ghb65a",
+    "output": "xn--ghb65a"
+  },
+  {
+    "input": "\u0644\u06ef",
+    "output": "xn--ghb65a"
+  },
+  {
+    "input": "xn--ghb65a953d",
+    "output": "xn--ghb65a953d"
+  },
+  {
+    "input": "xn--ghb2gxq",
+    "output": "xn--ghb2gxq"
+  },
+  {
+    "input": "\u0644\u0670\u06ed",
+    "output": "xn--ghb2gxq"
+  },
+  {
+    "comment": "C1",
+    "input": "\u06ef\u200c\u06ef",
+    "output": null
+  },
+  {
+    "input": "xn--cmba",
+    "output": "xn--cmba"
+  },
+  {
+    "input": "\u06ef\u06ef",
+    "output": "xn--cmba"
+  },
+  {
+    "comment": "C1",
+    "input": "xn--cmba004q",
+    "output": null
+  },
+  {
+    "input": "xn--ghb",
+    "output": "xn--ghb"
+  },
+  {
+    "input": "\u0644",
+    "output": "xn--ghb"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "a\u3002\u3002b",
+    "output": "a..b"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "A\u3002\u3002B",
+    "output": "a..b"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "a..b",
+    "output": "a..b"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "..xn--skb",
+    "output": "..xn--skb"
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u2495\u221d\u065f\uda0e\udd26\uff0e-\udb40\udd2f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "14.\u221d\u065f\uda0e\udd26.-\udb40\udd2f",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "14.xn--7hb713l3v90n.-",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--7hb713lfwbi1311b.-",
+    "output": null
+  },
+  {
+    "input": "\ua863.\u07cf",
+    "output": "xn--8c9a.xn--qsb"
+  },
+  {
+    "input": "xn--8c9a.xn--qsb",
+    "output": "xn--8c9a.xn--qsb"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud97d\udf9c\uff0e\ud803\udfc7\u0fa2\u077d\u0600",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud97d\udf9c\uff0e\ud803\udfc7\u0fa1\u0fb7\u077d\u0600",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud97d\udf9c.\ud803\udfc7\u0fa1\u0fb7\u077d\u0600",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--gw68a.xn--ifb57ev2psc6027m",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud84f\udcd4\u0303.\ud805\udcc2",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--nsa95820a.xn--wz1d",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\ud9d4\udfad.\u10b2\ud804\uddc0",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\ud9d4\udfad.\u2d12\ud804\uddc0",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--bn95b.xn--9kj2034e",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug15083f.xn--9kj2034e",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--bn95b.xn--qnd6272k",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug15083f.xn--qnd6272k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u7e71\ud805\uddbf\u200d.\uff18\ufe12",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--gl0as212a.xn--8-o89h",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--1ug6928ac48e.xn--8-o89h",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "\udb40\uddbe\uff0e\ud838\udc08",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "\udb40\uddbe.\ud838\udc08",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": ".xn--ph4h",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u00df\u06eb\u3002\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "SS\u06eb\u3002\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "ss\u06eb\u3002\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "Ss\u06eb\u3002\u200d",
+    "output": null
+  },
+  {
+    "input": "xn--ss-59d.",
+    "output": "xn--ss-59d."
+  },
+  {
+    "input": "ss\u06eb.",
+    "output": "xn--ss-59d."
+  },
+  {
+    "input": "SS\u06eb.",
+    "output": "xn--ss-59d."
+  },
+  {
+    "input": "Ss\u06eb.",
+    "output": "xn--ss-59d."
+  },
+  {
+    "comment": "C2",
+    "input": "xn--ss-59d.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "xn--zca012a.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\udb41\udc35\u200c\u2488\uff0e\udb40\udf87",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; A4_2 (ignored)",
+    "input": "\udb41\udc35\u200c1..\udb40\udf87",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": "xn--1-bs31m..xn--tv36e",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; A4_2 (ignored)",
+    "input": "xn--1-rgn37671n..xn--tv36e",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tshz2001k.xn--tv36e",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug88o47900b.xn--tv36e",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb3c\ude23\u065f\uaab2\u00df\u3002\udaf1\udce7",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb3c\ude23\u065f\uaab2SS\u3002\udaf1\udce7",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb3c\ude23\u065f\uaab2ss\u3002\udaf1\udce7",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb3c\ude23\u065f\uaab2Ss\u3002\udaf1\udce7",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ss-3xd2839nncy1m.xn--bb79d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--zca92z0t7n5w96j.xn--bb79d",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u0774\u200c\ud83a\udd3f\u3002\ud8b5\ude10\u425c\u200d\ud9be\udd3c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u0774\u200c\ud83a\udd1d\u3002\ud8b5\ude10\u425c\u200d\ud9be\udd3c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4pb2977v.xn--z0nt555ukbnv",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V6",
+    "input": "xn--4pb607jjt73a.xn--1ug236ke314donv1a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u3164\u094d\u10a0\u17d0.\u180b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1160\u094d\u10a0\u17d0.\u180b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1160\u094d\u2d00\u17d0.\u180b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--n3b742bkqf4ty.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--n3b468aoqa89r.",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u3164\u094d\u2d00\u17d0.\u180b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--n3b445e53po6d.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--n3b468azngju2a.",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u2763\u200d\uff0e\u09cd\ud807\udc3d\u0612\ua929",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u2763\u200d.\u09cd\ud807\udc3d\u0612\ua929",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--pei.xn--0fb32q3w7q2g4d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--1ugy10a.xn--0fb32q3w7q2g4d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u0349\u3002\ud85e\udc6b",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--nua.xn--bc6k",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc3f\udb40\udd66\uff0e\u1160",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc3f\udb40\udd66.\u1160",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ok3d.xn--psd",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u850f\uff61\ud807\udc3a",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u850f\u3002\ud807\udc3a",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--uy1a.xn--jk3d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--8g1d12120a.xn--5l6h",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud804\udee7\ua9c02\uff61\u39c9\uda09\udd84",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud804\udee7\ua9c02\u3002\u39c9\uda09\udd84",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--2-5z4eu89y.xn--97l02706d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03c2\ud8ab\udc40\uff61\uffa0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03c2\ud8ab\udc40\u3002\u1160",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03a3\ud8ab\udc40\u3002\u1160",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03c3\ud8ab\udc40\u3002\u1160",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa192qmp03d.xn--psd",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3xa392qmp03d.xn--psd",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03a3\ud8ab\udc40\uff61\uffa0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2938\u03c3\ud8ab\udc40\uff61\uffa0",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa192qmp03d.xn--cl7c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3xa392qmp03d.xn--cl7c",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u200d\udb7d\udc56\udb40\udc50\uff0e\u05bd\ud826\udfb0\ua85d\ud800\udee1",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u200d\udb7d\udc56\udb40\udc50.\u05bd\ud826\udfb0\ua85d\ud800\udee1",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--b726ey18m.xn--ldb8734fg0qcyzzg",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--1ug66101lt8me.xn--ldb8734fg0qcyzzg",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u3002\udbcc\ude35\u03c2\ud8c2\udc07\u3002\ud802\udf88",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u3002\udbcc\ude35\u03a3\ud8c2\udc07\u3002\ud802\udf88",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u3002\udbcc\ude35\u03c3\ud8c2\udc07\u3002\ud802\udf88",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--4xa68573c7n64d.xn--f29c",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--3xa88573c7n64d.xn--f29c",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd37.\ud802\udf90\ud83a\udc81\ud803\ude60\u0624",
+    "output": "xn--ve6h.xn--jgb1694kz0b2176a"
+  },
+  {
+    "input": "\ud83a\udd37.\ud802\udf90\ud83a\udc81\ud803\ude60\u0648\u0654",
+    "output": "xn--ve6h.xn--jgb1694kz0b2176a"
+  },
+  {
+    "input": "\ud83a\udd15.\ud802\udf90\ud83a\udc81\ud803\ude60\u0648\u0654",
+    "output": "xn--ve6h.xn--jgb1694kz0b2176a"
+  },
+  {
+    "input": "\ud83a\udd15.\ud802\udf90\ud83a\udc81\ud803\ude60\u0624",
+    "output": "xn--ve6h.xn--jgb1694kz0b2176a"
+  },
+  {
+    "input": "xn--ve6h.xn--jgb1694kz0b2176a",
+    "output": "xn--ve6h.xn--jgb1694kz0b2176a"
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "-\udb40\ude56\ua867\uff0e\udb40\ude82\ud8dc\udd83\ud83c\udd09",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----hg4ei0361g.xn--207ht163h7m94c",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "\u200c\uff61\u0354",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "\u200c\u3002\u0354",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": ".xn--yua",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "xn--0ug.xn--yua",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd25\udb40\udd6e\uff0e\u1844\u10ae",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd25\udb40\udd6e.\u1844\u10ae",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd25\udb40\udd6e.\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd03\udb40\udd6e.\u1844\u10ae",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd03\udb40\udd6e.\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "input": "xn--de6h.xn--37e857h",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "input": "\ud83a\udd25.\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd03.\u1844\u10ae",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd03.\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--de6h.xn--mnd799a",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd25\udb40\udd6e\uff0e\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd03\udb40\udd6e\uff0e\u1844\u10ae",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd03\udb40\udd6e\uff0e\u1844\u2d0e",
+    "output": "xn--de6h.xn--37e857h"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83a\udd25.\u1844\u10ae",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0fa4\ud986\udd2f\uff0e\ud835\udfed\u10bb",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0fa4\ud986\udd2f.1\u10bb",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0fa4\ud986\udd2f.1\u2d1b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--0fd40533g.xn--1-tws",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--0fd40533g.xn--1-q1g",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0fa4\ud986\udd2f\uff0e\ud835\udfed\u2d1b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c2\ud9d5\udf0c\uff18.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c2\ud9d5\udf0c8.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03a3\ud9d5\udf0c8.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c3\ud9d5\udf0c8.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--8-zmb14974n.xn--su6h",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--8-xmb44974n.xn--su6h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03a3\ud9d5\udf0c\uff18.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c3\ud9d5\udf0c\uff18.\ud83a\udf64",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u200c\uae03.\u69b6-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u200c\u1100\u1173\u11b2.\u69b6-",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--ej0b.xn----d87b",
+    "output": "xn--ej0b.xn----d87b"
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn--0ug3307c.xn----d87b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ub253\u6cd3\ud833\udd7d.\u09cd\u200d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1102\u1170\u11be\u6cd3\ud833\udd7d.\u09cd\u200d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--lwwp69lqs7m.xn--b7b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--lwwp69lqs7m.xn--b7b605i",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u10b1\u115f\uff0e\ud804\udd34\u2132",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u10b1\u115f.\ud804\udd34\u2132",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u2d11\u115f.\ud804\udd34\u214e",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u10b1\u115f.\ud804\udd34\u214e",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--pnd26a55x.xn--73g3065g",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--osd925cvyn.xn--73g3065g",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--pnd26a55x.xn--f3g7465g",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u2d11\u115f\uff0e\ud804\udd34\u214e",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bf3\u10b1\u115f\uff0e\ud804\udd34\u214e",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u00c5\ub444-\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "A\u030a\u1103\u116d\u11b7-\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u00c5\ub444-.\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "A\u030a\u1103\u116d\u11b7-.\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "a\u030a\u1103\u116d\u11b7-.\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u00e5\ub444-.\u200c",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----1fa1788k.",
+    "output": "xn----1fa1788k."
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn----1fa1788k.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "a\u030a\u1103\u116d\u11b7-\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u00e5\ub444-\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V5; V6",
+    "input": "\ub8f1\u200d\ud880\udf68\u200c\u3002\ud836\ude16\ufe12",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V5; V6",
+    "input": "\u1105\u116e\u11b0\u200d\ud880\udf68\u200c\u3002\ud836\ude16\ufe12",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V5",
+    "input": "\ub8f1\u200d\ud880\udf68\u200c\u3002\ud836\ude16\u3002",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V5",
+    "input": "\u1105\u116e\u11b0\u200d\ud880\udf68\u200c\u3002\ud836\ude16\u3002",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--ct2b0738h.xn--772h.",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V5",
+    "input": "xn--0ugb3358ili2v.xn--772h.",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ct2b0738h.xn--y86cl899a",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V5; V6",
+    "input": "xn--0ugb3358ili2v.xn--y86cl899a",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud83c\udd04\uff0e\u1cdc\u2488\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud83c\udd04\uff0e\u1cdc\u2488SS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud83c\udd04\uff0e\u1cdc\u2488ss",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud83c\udd04\uff0e\u1cdc\u2488Ss",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--x07h.xn--ss-k1r094b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--x07h.xn--zca344lmif",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\u1bf3.-\u900b\ud98e\uddad\udb25\ude6e",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--1zf.xn----483d46987byr50b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u3164\u200d\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u1160\u200d\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u1160\u200d\u03a3",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u1160\u200d\u03c3",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--9ob.xn--4xa380e",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9ob.xn--4xa380ebol",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9ob.xn--3xa580ebol",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u3164\u200d\u03a3",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u0756\u3002\u3164\u200d\u03c3",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--9ob.xn--4xa574u",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9ob.xn--4xa795lq2l",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9ob.xn--3xa995lq2l",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u1846\u10a3\uff61\udb3a\udca7\u0315\u200d\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u1846\u10a3\u3002\udb3a\udca7\u0315\u200d\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u1846\u2d03\u3002\udb3a\udca7\u0315\u200d\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--57e237h.xn--5sa98523p",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--57e237h.xn--5sa649la993427a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--bnd320b.xn--5sa98523p",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--bnd320b.xn--5sa649la993427a",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u1846\u2d03\uff61\udb3a\udca7\u0315\u200d\u200d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud838\udc28\uff61\u1b44\uda45\udee8\ud838\udf87",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud838\udc28\u3002\u1b44\uda45\udee8\ud838\udf87",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--mi4h.xn--1uf6843smg20c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u189b\udb60\udd5f\u00df.\u1327",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u189b\udb60\udd5fSS.\u1327",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u189b\udb60\udd5fss.\u1327",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u189b\udb60\udd5fSs.\u1327",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ss-7dp66033t.xn--p5d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--zca562jc642x.xn--p5d",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2b92\u200c.\ud909\ude97\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--b9i.xn--5p9y",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ugx66b.xn--0ugz2871c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u00df\uff61\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u00df\u3002\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "input": "\u00df\u3002\ud800\udef3\u2d0c\u0fb8",
+    "output": "xn--zca.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "SS\u3002\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "input": "ss\u3002\ud800\udef3\u2d0c\u0fb8",
+    "output": "ss.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "Ss\u3002\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "ss.xn--lgd10cu829c",
+    "output": null
+  },
+  {
+    "input": "ss.xn--lgd921mvv0m",
+    "output": "ss.xn--lgd921mvv0m"
+  },
+  {
+    "input": "ss.\ud800\udef3\u2d0c\u0fb8",
+    "output": "ss.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "SS.\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "Ss.\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "input": "xn--zca.xn--lgd921mvv0m",
+    "output": "xn--zca.xn--lgd921mvv0m"
+  },
+  {
+    "input": "\u00df.\ud800\udef3\u2d0c\u0fb8",
+    "output": "xn--zca.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--zca.xn--lgd10cu829c",
+    "output": null
+  },
+  {
+    "input": "\u00df\uff61\ud800\udef3\u2d0c\u0fb8",
+    "output": "xn--zca.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "SS\uff61\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "input": "ss\uff61\ud800\udef3\u2d0c\u0fb8",
+    "output": "ss.xn--lgd921mvv0m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "Ss\uff61\ud800\udef3\u10ac\u0fb8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1a5a\ud82e\udd9d\u0c4d\u3002\ud829\udf6c\ud835\udff5",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1a5a\ud82e\udd9d\u0c4d\u3002\ud829\udf6c9",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--lqc703ebm93a.xn--9-000p",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\u1856\uff61\u031f\ud91d\udee8\u0b82-",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\u1856\u3002\u031f\ud91d\udee8\u0b82-",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--m8e.xn----mdb555dkk71m",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83c\udd07\u4f10\ufe12.\ud831\ude5a\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--woqs083bel0g.xn--0f9ao925c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\udb40\udda0\uff0e\ud99d\udc34\udaf1\udfc8",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\udb40\udda0.\ud99d\udc34\udaf1\udfc8",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--rx21bhv12i",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "-.\u1886\udb47\udca3-",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "-.xn----pbkx6497q",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0\uff0e-\ud835\udffb\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0.-5\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0.-5SS",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0.-5ss",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--t960e.-5ss",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--t960e.xn---5-hia",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0\uff0e-\ud835\udffbSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0\uff0e-\ud835\udffbss",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0\uff0e-\ud835\udffbSs",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udafd\udcb0.-5Ss",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\ud802\ude3f.\ud83e\udd12\u10c5\uda06\udfb6",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\ud802\ude3f.\ud83e\udd12\u2d25\uda06\udfb6",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--0s9c.xn--tljz038l0gz4b",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug9533g.xn--tljz038l0gz4b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--0s9c.xn--9nd3211w0gz4b",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug9533g.xn--9nd3211w0gz4b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud894\udec5\u3002\u00df\ud873\udd69\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud894\udec5\u3002SS\ud873\udd69\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud894\udec5\u3002ss\ud873\udd69\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud894\udec5\u3002Ss\ud873\udd69\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ey1p.xn--ss-eq36b",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--ey1p.xn--ss-n1tx0508a",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--ey1p.xn--zca870nz438b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u248b\uff61\u2488\u200d\uda8f\udd22",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; A4_2 (ignored)",
+    "input": "4.\u30021.\u200d\uda8f\udd22",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": "4..1.xn--sf51d",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; A4_2 (ignored)",
+    "input": "4..1.xn--1ug64613i",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--wsh.xn--tsh07994h",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--wsh.xn--1ug58o74922a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10b3\ud805\udf2b\u200d\uda1e\udf53\uff0e\u06a7\ud807\udc36",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10b3\ud805\udf2b\u200d\uda1e\udf53.\u06a7\ud807\udc36",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d13\ud805\udf2b\u200d\uda1e\udf53.\u06a7\ud807\udc36",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--blj6306ey091d.xn--9jb4223l",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--1ugy52cym7p7xu5e.xn--9jb4223l",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--rnd8945ky009c.xn--9jb4223l",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--rnd479ep20q7x12e.xn--9jb4223l",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d13\ud805\udf2b\u200d\uda1e\udf53\uff0e\u06a7\ud807\udc36",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud802\ude3f.\ud83c\udd06\u2014",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--0s9c.xn--8ug8324p",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\uda10\udeb1\ud8c6\uddae\u06f8\u3002\udb43\udfad-",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--lmb18944c0g2z.xn----2k81m",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83d\udf85\udb43\udce1\udb30\udf59.\ud989\uddb7",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ie9hi1349bqdlb.xn--oj69a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u20e7\ud97e\udc4e-\uda6e\udcdd.4\u10a4\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u20e7\ud97e\udc4e-\uda6e\udcdd.4\u2d04\u200c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----9snu5320fi76w.xn--4-ivs",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn----9snu5320fi76w.xn--4-sgn589c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----9snu5320fi76w.xn--4-f0g",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn----9snu5320fi76w.xn--4-f0g649i",
+    "output": null
+  },
+  {
+    "input": "\u16ad\uff61\ud834\udf20\u00df\ud81a\udef1",
+    "output": "xn--hwe.xn--zca4946pblnc"
+  },
+  {
+    "input": "\u16ad\u3002\ud834\udf20\u00df\ud81a\udef1",
+    "output": "xn--hwe.xn--zca4946pblnc"
+  },
+  {
+    "input": "\u16ad\u3002\ud834\udf20SS\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad\u3002\ud834\udf20ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad\u3002\ud834\udf20Ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "xn--hwe.xn--ss-ci1ub261a",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad.\ud834\udf20ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad.\ud834\udf20SS\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad.\ud834\udf20Ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "xn--hwe.xn--zca4946pblnc",
+    "output": "xn--hwe.xn--zca4946pblnc"
+  },
+  {
+    "input": "\u16ad.\ud834\udf20\u00df\ud81a\udef1",
+    "output": "xn--hwe.xn--zca4946pblnc"
+  },
+  {
+    "input": "\u16ad\uff61\ud834\udf20SS\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad\uff61\ud834\udf20ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "input": "\u16ad\uff61\ud834\udf20Ss\ud81a\udef1",
+    "output": "xn--hwe.xn--ss-ci1ub261a"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3a\u00c9\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3aE\u0301\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734.\ud802\ude3a\u00c9\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734.\ud802\ude3aE\u0301\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734.\ud802\ude3ae\u0301\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734.\ud802\ude3a\u00e9\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--c0e34564d.xn--9ca207st53lg3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3ae\u0301\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud8fc\udc47\u1734\uff0e\ud802\ude3a\u00e9\u2b13\ud804\udd34",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "xn--09e4694e..xn--ye6h",
+    "output": "xn--09e4694e..xn--ye6h"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c3\uff0e\u0653\u18a4",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c3.\u0653\u18a4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u2d23.\u0653\u18a4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--rlj.xn--vhb294g",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--7nd.xn--vhb294g",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u2d23\uff0e\u0653\u18a4",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813\uff0e\uc2c9\ud9d0\uddbb\u10c4\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813\uff0e\u1109\u1174\u11b0\ud9d0\uddbb\u10c4\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813.\uc2c9\ud9d0\uddbb\u10c4\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813.\u1109\u1174\u11b0\ud9d0\uddbb\u10c4\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813.\u1109\u1174\u11b0\ud9d0\uddbb\u2d24\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813.\uc2c9\ud9d0\uddbb\u2d24\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--oub.xn--sljz109bpe25dviva",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--oub.xn--8nd9522gpe69cviva",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813\uff0e\u1109\u1174\u11b0\ud9d0\uddbb\u2d24\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb40\udd08\u0813\uff0e\uc2c9\ud9d0\uddbb\u2d24\ud9ca\udc50",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\ud804\udc45\u3002-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--210d.-",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\ua866\u1851\u200d\u2488\u3002\ud800\udee3-",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored); A4_2 (ignored)",
+    "input": "\ua866\u1851\u200d1.\u3002\ud800\udee3-",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored); A4_2 (ignored)",
+    "input": "xn--1-o7j0610f..xn----381i",
+    "output": "xn--1-o7j0610f..xn----381i"
+  },
+  {
+    "comment": "C2; V3 (ignored); A4_2 (ignored)",
+    "input": "xn--1-o7j663bdl7m..xn----381i",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--h8e863drj7h.xn----381i",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn--h8e470bl0d838o.xn----381i",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u2488\u4c39\u200d-\u3002\uc6c8",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u2488\u4c39\u200d-\u3002\u110b\u116e\u11bf",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "1.\u4c39\u200d-\u3002\uc6c8",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "1.\u4c39\u200d-\u3002\u110b\u116e\u11bf",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "1.xn----zw5a.xn--kp5b",
+    "output": "1.xn----zw5a.xn--kp5b"
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "1.xn----tgnz80r.xn--kp5b",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----dcp160o.xn--kp5b",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----tgnx5rjr6c.xn--kp5b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u3066\u3002\u200c\udb43\udcfd\u07f3",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--m9j.xn--rtb10784p",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--m9j.xn--rtb154j9l73w",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c2\uff61\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c2\u3002\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03a3\u3002\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c3\u3002\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--4xa.xn--3lb1944f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--3xa.xn--3lb1944f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03a3\uff61\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c3\uff61\ua9c0\u06e7",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u10a2\u10b5",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u2d02\u2d15",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0bcd\udb56\udec5\ud9f0\ude51.\u10a2\u2d15",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--xmc83135idcxza.xn--9md086l",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--xmc83135idcxza.xn--tkjwb",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--xmc83135idcxza.xn--9md2b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u1c32\ud83c\udd08\u2f9b\u05a6\uff0e\u200d\uda7e\udd64\u07fd",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--xcb756i493fwi5o.xn--1tb13454l",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--xcb756i493fwi5o.xn--1tb334j1197q",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1897\uff61\u04c0\ud934\udd3b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1897\u3002\u04c0\ud934\udd3b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1897\u3002\u04cf\ud934\udd3b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hbf.xn--s5a83117e",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hbf.xn--d5a86117e",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1897\uff61\u04cf\ud934\udd3b",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\ud800\udef7\ud81b\udf91\u3002\udb40\uddac",
+    "output": "xn----991iq40y."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----991iq40y.",
+    "output": "xn----991iq40y."
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\uff61\ud835\udfea\u10bc",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\u30028\u10bc",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\u30028\u2d1c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--7m3d291b.xn--8-vws",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--7m3d291b.xn--8-s1g",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud807\udc98\udb40\udd12\ud80d\udc61\uff61\ud835\udfea\u2d1c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bab\uff61\ud83c\udc89\udb40\udc70",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1bab\u3002\ud83c\udc89\udb40\udc70",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--zxf.xn--fx7ho0250c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\udb71\udeb6\udba0\uded6\uda1a\ude70-\u3002\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----7i12hu122k9ire.",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; V3 (ignored)",
+    "input": "xn----7i12hu122k9ire.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ufe12\uff0e\ufe2f\ud805\udc42",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ufe12\uff0e\ud805\udc42\ufe2f",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "\u3002.\ud805\udc42\ufe2f",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "..xn--s96cu30b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--y86c.xn--s96cu30b",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\ua92c\u3002\u200d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--zi9a.",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--zi9a.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\udb58\ude04\u3002-",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--xm38e.-",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0330\uff0e\udb81\udf31\u8680",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0330.\udb81\udf31\u8680",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--xta.xn--e91aw9417e",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud83e\udc9f\ud83c\udd08\u200d\ua84e\uff61\u0f84",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--nc9aq743ds0e.xn--3ed",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--1ug4874cfd0kbmg.xn--3ed",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ua854\u3002\u1039\u1887",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--tc9a.xn--9jd663b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\ud880\udd67\ud94e\ude60-\uff0e\uabed-\u609c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\ud880\udd67\ud94e\ude60-.\uabed-\u609c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----7m53aj640l.xn----8f4br83t",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u1849\ud899\udce7\u2b1e\u189c.-\u200d\ud83a\udcd1\u202e",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--87e0ol04cdl39e.xn----qinu247r",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn--87e0ol04cdl39e.xn----ugn5e3763s",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd53\uff0e\u0718",
+    "output": "xn--of6h.xn--inb"
+  },
+  {
+    "input": "\ud83a\udd53.\u0718",
+    "output": "xn--of6h.xn--inb"
+  },
+  {
+    "input": "xn--of6h.xn--inb",
+    "output": "xn--of6h.xn--inb"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\udb40\udd3d-\uff0e-\u0dca",
+    "output": "-.xn----ptf"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\udb40\udd3d-.-\u0dca",
+    "output": "-.xn----ptf"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.xn----ptf",
+    "output": "-.xn----ptf"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba\ud800\udef8\udb40\udd04\u3002\ud835\udfdd\ud7f6\u103a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba\ud800\udef8\udb40\udd04\u30025\ud7f6\u103a",
+    "output": null
+  },
+  {
+    "input": "\u2d1a\ud800\udef8\udb40\udd04\u30025\ud7f6\u103a",
+    "output": "xn--ilj2659d.xn--5-dug9054m"
+  },
+  {
+    "input": "xn--ilj2659d.xn--5-dug9054m",
+    "output": "xn--ilj2659d.xn--5-dug9054m"
+  },
+  {
+    "input": "\u2d1a\ud800\udef8.5\ud7f6\u103a",
+    "output": "xn--ilj2659d.xn--5-dug9054m"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba\ud800\udef8.5\ud7f6\u103a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ynd2415j.xn--5-dug9054m",
+    "output": null
+  },
+  {
+    "input": "\u2d1a\ud800\udef8\udb40\udd04\u3002\ud835\udfdd\ud7f6\u103a",
+    "output": "xn--ilj2659d.xn--5-dug9054m"
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u200d-\u1839\ufe6a.\u1de1\u1922",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u200d-\u1839%.\u1de1\u1922",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "xn---%-u4o.xn--gff52t",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "xn---%-u4oy48b.xn--gff52t",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----c6jx047j.xn--gff52t",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn----c6j614b1z4v.xn--gff52t",
+    "output": null
+  },
+  {
+    "input": "\u0723\u05a3\uff61\u332a",
+    "output": "xn--ucb18e.xn--eck4c5a"
+  },
+  {
+    "input": "\u0723\u05a3\u3002\u30cf\u30a4\u30c4",
+    "output": "xn--ucb18e.xn--eck4c5a"
+  },
+  {
+    "input": "xn--ucb18e.xn--eck4c5a",
+    "output": "xn--ucb18e.xn--eck4c5a"
+  },
+  {
+    "input": "\u0723\u05a3.\u30cf\u30a4\u30c4",
+    "output": "xn--ucb18e.xn--eck4c5a"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud84e\ude6b\uff0e\ud9f1\udc72",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud84e\ude6b.\ud9f1\udc72",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--td3j.xn--4628b",
+    "output": null
+  },
+  {
+    "input": "xn--skb",
+    "output": "xn--skb"
+  },
+  {
+    "input": "\u06b9",
+    "output": "xn--skb"
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0c4d\ud836\ude3e\u05a9\ud835\udfed\u3002-\ud805\udf28",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0c4d\ud836\ude3e\u05a91\u3002-\ud805\udf28",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--1-rfc312cdp45c.xn----nq0j",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\uda4f\udfc8\u3002\ub64f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\uda4f\udfc8\u3002\u1104\u116b\u11ae",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ph26c.xn--281b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud916\ude1a\udb40\udd0c\udb07\udf40\u1840.\u08b6",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--z7e98100evc01b.xn--czb",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\uff61\ud8d4\udc5b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u3002\ud8d4\udc5b",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--6x4u",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug.xn--6x4u",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "\ud805\uddbf\ud836\ude14.\u185f\ud805\uddbf\u1b42\u200c",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--461dw464a.xn--v8e29loy65a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "xn--461dw464a.xn--v8e29ldzfo952a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ud02c-?\ud99b\udcd2.\u200c\u0ac5\udb67\ude24\u06f4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.\u200c\u0ac5\udb67\ude24\u06f4",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "xn---?-6g4k75207c.xn--hmb76q74166b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "xn---?-6g4k75207c.xn--hmb76q48y18505a",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud02c-?\ud99b\udcd2.xn--hmb76q74166b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.xn--hmb76q74166b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.XN--HMB76Q74166B",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud02c-?\ud99b\udcd2.XN--HMB76Q74166B",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud02c-?\ud99b\udcd2.Xn--Hmb76q74166b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.Xn--Hmb76q74166b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ud02c-?\ud99b\udcd2.xn--hmb76q48y18505a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.xn--hmb76q48y18505a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.XN--HMB76Q48Y18505A",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ud02c-?\ud99b\udcd2.XN--HMB76Q48Y18505A",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ud02c-?\ud99b\udcd2.Xn--Hmb76q48y18505a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u110f\u1170\u11bb-?\ud99b\udcd2.Xn--Hmb76q48y18505a",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u252e\udb40\uddd0\uff0e\u0c00\u0c4d\u1734\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u252e\udb40\uddd0.\u0c00\u0c4d\u1734\u200d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--kxh.xn--eoc8m432a",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--1ug04r.xn--eoc8m432a40i",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udaa5\udeaa\uff61\ud83c\udd02",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--n433d.xn--v07h",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud804\udf68\u520d.\ud83d\udee6",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--rbry728b.xn--y88h",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb40\udf0f3\uff61\u1bf1\ud835\udfd2",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb40\udf0f3\u3002\u1bf14",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--3-ib31m.xn--4-pql",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u034a\uff0e\ud802\ude0e",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u034a.\ud802\ude0e",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--oua.xn--mr9c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ua846\u3002\u2183\u0fb5\ub1ae-",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ua846\u3002\u2183\u0fb5\u1102\u116a\u11c1-",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\ua846\u3002\u2184\u0fb5\u1102\u116a\u11c1-",
+    "output": "xn--fc9a.xn----qmg097k469k"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\ua846\u3002\u2184\u0fb5\ub1ae-",
+    "output": "xn--fc9a.xn----qmg097k469k"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--fc9a.xn----qmg097k469k",
+    "output": "xn--fc9a.xn----qmg097k469k"
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--fc9a.xn----qmg787k869k",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud805\udc42\uff61\u200d\udb55\udf80\ud83d\udf95\uda54\udc54",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud805\udc42\u3002\u200d\udb55\udf80\ud83d\udf95\uda54\udc54",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--8v1d.xn--ye9h41035a2qqs",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--8v1d.xn--1ug1386plvx1cd8vya",
+    "output": null
+  },
+  {
+    "input": "\u00df\u09c1\u1ded\u3002\u06208\u2085",
+    "output": "xn--zca266bwrr.xn--85-psd"
+  },
+  {
+    "input": "\u00df\u09c1\u1ded\u3002\u062085",
+    "output": "xn--zca266bwrr.xn--85-psd"
+  },
+  {
+    "input": "SS\u09c1\u1ded\u3002\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "ss\u09c1\u1ded\u3002\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "Ss\u09c1\u1ded\u3002\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "xn--ss-e2f077r.xn--85-psd",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "ss\u09c1\u1ded.\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "SS\u09c1\u1ded.\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "Ss\u09c1\u1ded.\u062085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "xn--zca266bwrr.xn--85-psd",
+    "output": "xn--zca266bwrr.xn--85-psd"
+  },
+  {
+    "input": "\u00df\u09c1\u1ded.\u062085",
+    "output": "xn--zca266bwrr.xn--85-psd"
+  },
+  {
+    "input": "SS\u09c1\u1ded\u3002\u06208\u2085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "ss\u09c1\u1ded\u3002\u06208\u2085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "Ss\u09c1\u1ded\u3002\u06208\u2085",
+    "output": "xn--ss-e2f077r.xn--85-psd"
+  },
+  {
+    "input": "\ufe0d\u0a9b\u3002\u5d68",
+    "output": "xn--6dc.xn--tot"
+  },
+  {
+    "input": "xn--6dc.xn--tot",
+    "output": "xn--6dc.xn--tot"
+  },
+  {
+    "input": "\u0a9b.\u5d68",
+    "output": "xn--6dc.xn--tot"
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "-\u200c\u2499\ud802\udee5\uff61\ud836\ude35",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V3 (ignored)",
+    "input": "-\u200c18.\ud802\udee5\u3002\ud836\ude35",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-18.xn--rx9c.xn--382h",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V3 (ignored)",
+    "input": "xn---18-9m0a.xn--rx9c.xn--382h",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----ddps939g.xn--382h",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6; V3 (ignored)",
+    "input": "xn----sgn18r3191a.xn--382h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ufe05\ufe12\u3002\ud858\udc3e\u1ce0",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\ufe05\u3002\u3002\ud858\udc3e\u1ce0",
+    "output": "..xn--t6f5138v"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "..xn--t6f5138v",
+    "output": "..xn--t6f5138v"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--y86c.xn--t6f5138v",
+    "output": null
+  },
+  {
+    "input": "xn--t6f5138v",
+    "output": "xn--t6f5138v"
+  },
+  {
+    "input": "\ud858\udc3e\u1ce0",
+    "output": "xn--t6f5138v"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\uda7b\udd5b\u0613.\u10b5",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\uda7b\udd5b\u0613.\u2d15",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--1fb94204l.xn--dlj",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--1fb94204l.xn--tnd",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\udb40\udd37\uff61\uda09\udc41",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\udb40\udd37\u3002\uda09\udc41",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--w720c",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug.xn--w720c",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2488\u0dd6\u7105.\udb1e\udc59\u200d\ua85f",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "1.\u0dd6\u7105.\udb1e\udc59\u200d\ua85f",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "1.xn--t1c6981c.xn--4c9a21133d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "1.xn--t1c6981c.xn--1ugz184c9lw7i",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--t1c337io97c.xn--4c9a21133d",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--t1c337io97c.xn--1ugz184c9lw7i",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud804\uddc0\u258d.\u205e\u1830",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--9zh3057f.xn--j7e103b",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-3.\u200d\u30cc\u1895",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-3.xn--fbf115j",
+    "output": "-3.xn--fbf115j"
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-3.xn--fbf739aq5o",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03c2",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03a3",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud802\ude3f\udb40\udd8c\u9e2e\ud805\udeb6.\u03c3",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--l76a726rt2h.xn--4xa",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--l76a726rt2h.xn--3xa",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03c2-\u3002\u200c\ud835\udfed-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03c2-\u3002\u200c1-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03a3-\u3002\u200c1-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03c3-\u3002\u200c1-",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----zmb.1-",
+    "output": "xn----zmb.1-"
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn----zmb.xn--1--i1t",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn----xmb.xn--1--i1t",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03a3-\u3002\u200c\ud835\udfed-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u03c3-\u3002\u200c\ud835\udfed-",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1734-\u0ce2\uff0e\udb40\udd29\u10a4",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1734-\u0ce2.\udb40\udd29\u10a4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u1734-\u0ce2.\udb40\udd29\u2d04",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn----ggf830f.xn--vkj",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----ggf830f.xn--cnd",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u1734-\u0ce2\uff0e\udb40\udd29\u2d04",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u200d\u3002\ud838\udc18\u2488\ua84d\u64c9",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u3002\ud838\udc181.\ua84d\u64c9",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": ".xn--1-1p4r.xn--s7uv61m",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--1ug.xn--1-1p4r.xn--s7uv61m",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; A4_2 (ignored)",
+    "input": ".xn--tsh026uql4bew9p",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--1ug.xn--tsh026uql4bew9p",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2ad0\uff61\u10c0-\udacd\udc22",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2ad0\u3002\u10c0-\udacd\udc22",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2ad0\u3002\u2d20-\udacd\udc22",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--r3i.xn----2wst7439i",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--r3i.xn----z1g58579u",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2ad0\uff61\u2d20-\udacd\udc22",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud805\udc42\u25ca\uff0e\u299f\u2220",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud805\udc42\u25ca.\u299f\u2220",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--01h3338f.xn--79g270a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud5c1\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba\ud835\udfdc",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1112\u1164\u11bc\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba\ud835\udfdc",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud5c1\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba4",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1112\u1164\u11bc\udb21\udd99\u0e3a\udb28\udf5a\u3002\u06ba4",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--o4c1723h8g85gt4ya.xn--4-dvc",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua953.\u033d\ud804\udcbd\u998b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--3j9a.xn--bua0708eqzrd",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\udae2\udedd\uda69\udef8\u200d\uff61\u4716",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\udae2\udedd\uda69\udef8\u200d\u3002\u4716",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--g138cxw05a.xn--k0o",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug30527h9mxi.xn--k0o",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u186f\u2689\u59f6\ud83c\udd09\uff0e\u06f7\u200d\ud83c\udfaa\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--c9e433epi4b3j20a.xn--kmb6733w",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--c9e433epi4b3j20a.xn--kmb859ja94998b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "\u135f\u1848\u200c\uff0e\ufe12-\ud81b\udf90-",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V3 (ignored); A4_2 (ignored)",
+    "input": "\u135f\u1848\u200c.\u3002-\ud81b\udf90-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored); A4_2 (ignored)",
+    "input": "xn--b7d82w..xn-----pe4u",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V3 (ignored); A4_2 (ignored)",
+    "input": "xn--b7d82wo4h..xn-----pe4u",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--b7d82w.xn-----c82nz547a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6; V3 (ignored)",
+    "input": "xn--b7d82wo4h.xn-----c82nz547a",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\ud836\ude5c\u3002-\u0b4d\u10ab",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\ud836\ude5c\u3002-\u0b4d\u2d0b",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--792h.xn----bse820x",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--792h.xn----bse632b",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\ud835\udff5\u9681\u2bee\uff0e\u180d\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "9\u9681\u2bee.\u180d\u200c",
+    "output": null
+  },
+  {
+    "input": "xn--9-mfs8024b.",
+    "output": "xn--9-mfs8024b."
+  },
+  {
+    "input": "9\u9681\u2bee.",
+    "output": "xn--9-mfs8024b."
+  },
+  {
+    "comment": "C1",
+    "input": "xn--9-mfs8024b.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u1bac\u10ac\u200c\u0325\u3002\ud835\udff8",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "\u1bac\u2d0c\u200c\u0325\u3002\ud835\udff8",
+    "output": null
+  },
+  {
+    "input": "xn--2ib43l.xn--te6h",
+    "output": "xn--2ib43l.xn--te6h"
+  },
+  {
+    "input": "\u067d\u0943.\ud83a\udd35",
+    "output": "xn--2ib43l.xn--te6h"
+  },
+  {
+    "input": "\u067d\u0943.\ud83a\udd13",
+    "output": "xn--2ib43l.xn--te6h"
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u3002\uffa0\u0f84\u0f96",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u3002\u1160\u0f84\u0f96",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--3ed0b20h",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug.xn--3ed0b20h",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--3ed0by082k",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug.xn--3ed0by082k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ua9d0\u04c0\u1baa\u08f6\uff0e\ub235",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ua9d0\u04c0\u1baa\u08f6\uff0e\u1102\u116f\u11bc",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ua9d0\u04c0\u1baa\u08f6.\ub235",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ua9d0\u04c0\u1baa\u08f6.\u1102\u116f\u11bc",
+    "output": null
+  },
+  {
+    "input": "\ua9d0\u04cf\u1baa\u08f6.\u1102\u116f\u11bc",
+    "output": "xn--s5a04sn4u297k.xn--2e1b"
+  },
+  {
+    "input": "\ua9d0\u04cf\u1baa\u08f6.\ub235",
+    "output": "xn--s5a04sn4u297k.xn--2e1b"
+  },
+  {
+    "input": "xn--s5a04sn4u297k.xn--2e1b",
+    "output": "xn--s5a04sn4u297k.xn--2e1b"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--d5a07sn4u297k.xn--2e1b",
+    "output": null
+  },
+  {
+    "input": "\ua9d0\u04cf\u1baa\u08f6\uff0e\u1102\u116f\u11bc",
+    "output": "xn--s5a04sn4u297k.xn--2e1b"
+  },
+  {
+    "input": "\ua9d0\u04cf\u1baa\u08f6\uff0e\ub235",
+    "output": "xn--s5a04sn4u297k.xn--2e1b"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua8ea\uff61\ud818\udd3f\ud804\uddbe\udb40\uddd7",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua8ea\u3002\ud818\udd3f\ud804\uddbe\udb40\uddd7",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--3g9a.xn--ud1dz07k",
+    "output": null
+  },
+  {
+    "input": "xn--9hb7344k.",
+    "output": "xn--9hb7344k."
+  },
+  {
+    "input": "\ud802\udec7\u0661.",
+    "output": "xn--9hb7344k."
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c5.\ud804\udd33\u32b8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c5.\ud804\udd3343",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u2d25.\ud804\udd3343",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--tlj.xn--43-274o",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--9nd.xn--43-274o",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u2d25.\ud804\udd33\u32b8",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91e\udea8\udb40\udd09\uffa0\u0fb7.\ud9a1\udfb0\ua953",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91e\udea8\udb40\udd09\u1160\u0fb7.\ud9a1\udfb0\ua953",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--kgd36f9z57y.xn--3j9au7544a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--kgd7493jee34a.xn--3j9au7544a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "\u0618.\u06f3\u200c\ua953",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--6fb.xn--gmb0524f",
+    "output": null
+  },
+  {
+    "comment": "C1; V5",
+    "input": "xn--6fb.xn--gmb469jjf1h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u184c\uff0e\ufe12\u1891",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\u184c.\u3002\u1891",
+    "output": "xn--c8e..xn--bbf"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "xn--c8e..xn--bbf",
+    "output": "xn--c8e..xn--bbf"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--c8e.xn--bbf9168i",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83b\uddcf\u3002\u1822\uda0d\ude06",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hd7h.xn--46e66060j",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u40b9\udbb9\udd85\ud800\udee6\uff0e\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u40b9\udbb9\udd85\ud800\udee6.\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0on3543c5981i.",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--0on3543c5981i.xn--1ug",
+    "output": null
+  },
+  {
+    "input": "\u07e5.\u06b5",
+    "output": "xn--dtb.xn--okb"
+  },
+  {
+    "input": "xn--dtb.xn--okb",
+    "output": "xn--dtb.xn--okb"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--3e6h",
+    "output": ".xn--3e6h"
+  },
+  {
+    "input": "xn--3e6h",
+    "output": "xn--3e6h"
+  },
+  {
+    "input": "\ud83a\udd3f",
+    "output": "xn--3e6h"
+  },
+  {
+    "input": "\ud83a\udd1d",
+    "output": "xn--3e6h"
+  },
+  {
+    "comment": "C1; V5; V3 (ignored)",
+    "input": "\u103a\u200d\u200c\u3002-\u200c",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--bkd.-",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V3 (ignored)",
+    "input": "xn--bkd412fca.xn----sgn",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ufe12\uff61\u1b44\u1849",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "\u3002\u3002\u1b44\u1849",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "..xn--87e93m",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--y86c.xn--87e93m",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "-\u1bab\ufe12\u200d.\ud90b\udd88\ud957\ude53",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "-\u1bab\u3002\u200d.\ud90b\udd88\ud957\ude53",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored); A4_2 (ignored)",
+    "input": "xn----qml..xn--x50zy803a",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----qml.xn--1ug.xn--x50zy803a",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----qml1407i.xn--x50zy803a",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----qmlv7tw180a.xn--x50zy803a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u06b9\uff0e\u1873\u115f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u06b9.\u1873\u115f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--skb.xn--osd737a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u3a1b\ud823\udc4e.\ufe12\ud835\udfd5\u0d01",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "\u3a1b\ud823\udc4e.\u30027\u0d01",
+    "output": "xn--mbm8237g..xn--7-7hf"
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "xn--mbm8237g..xn--7-7hf",
+    "output": "xn--mbm8237g..xn--7-7hf"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--mbm8237g.xn--7-7hf1526p",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u00df\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u10b6",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u00df\u200c\uaaf6\u18a5.\u22b6\u10c1\u10b6",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u00df\u200c\uaaf6\u18a5.\u22b6\u2d21\u2d16",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "SS\u200c\uaaf6\u18a5.\u22b6\u10c1\u10b6",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "ss\u200c\uaaf6\u18a5.\u22b6\u2d21\u2d16",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "Ss\u200c\uaaf6\u18a5.\u22b6\u10c1\u2d16",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ss-4epx629f.xn--5nd703gyrh",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--ss-4ep585bkm5p.xn--5nd703gyrh",
+    "output": null
+  },
+  {
+    "input": "xn--ss-4epx629f.xn--ifh802b6a",
+    "output": "xn--ss-4epx629f.xn--ifh802b6a"
+  },
+  {
+    "input": "ss\uaaf6\u18a5.\u22b6\u2d21\u2d16",
+    "output": "xn--ss-4epx629f.xn--ifh802b6a"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "SS\uaaf6\u18a5.\u22b6\u10c1\u10b6",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "Ss\uaaf6\u18a5.\u22b6\u10c1\u2d16",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ss-4epx629f.xn--undv409k",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--ss-4ep585bkm5p.xn--ifh802b6a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--ss-4ep585bkm5p.xn--undv409k",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--zca682johfi89m.xn--ifh802b6a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--zca682johfi89m.xn--undv409k",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u00df\u200c\uaaf6\u18a5\uff0e\u22b6\u2d21\u2d16",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "SS\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u10b6",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "ss\u200c\uaaf6\u18a5\uff0e\u22b6\u2d21\u2d16",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "Ss\u200c\uaaf6\u18a5\uff0e\u22b6\u10c1\u2d16",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u3002\u03c2\udb40\udc49",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u3002\u03a3\udb40\udc49",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u3002\u03c3\udb40\udc49",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--4xa24344p",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug.xn--4xa24344p",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug.xn--3xa44344p",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u2492\uda61\ude19\uda8f\udce0\ud805\udcc0.-\udb3a\udc4a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "11.\uda61\ude19\uda8f\udce0\ud805\udcc0.-\udb3a\udc4a",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "11.xn--uz1d59632bxujd.xn----x310m",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--3shy698frsu9dt1me.xn----x310m",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-\uff61\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-\u3002\u200d",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.",
+    "output": "-."
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u126c\uda12\udc3c\ud8c5\uddf6\uff61\ud802\ude2c\ud835\udfe0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u126c\uda12\udc3c\ud8c5\uddf6\u3002\ud802\ude2c8",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--d0d41273c887z.xn--8-ob5i",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u03c2\u200d-.\u10c3\ud859\udfd9",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "\u03c2\u200d-.\u2d23\ud859\udfd9",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u03a3\u200d-.\u10c3\ud859\udfd9",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "\u03c3\u200d-.\u2d23\ud859\udfd9",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----zmb.xn--rlj2573p",
+    "output": "xn----zmb.xn--rlj2573p"
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "xn----zmb048s.xn--rlj2573p",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----zmb.xn--7nd64871a",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----zmb048s.xn--7nd64871a",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "xn----xmb348s.xn--rlj2573p",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----xmb348s.xn--7nd64871a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udad6\udf3d.\u8814",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--g747d.xn--xl2a",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u08e6\u200d\uff0e\ubf3d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u08e6\u200d\uff0e\u1108\u1168\u11c0",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u08e6\u200d.\ubf3d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u08e6\u200d.\u1108\u1168\u11c0",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--p0b.xn--e43b",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--p0b869i.xn--e43b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud8f6\ude3d\uff0e\ud8ef\ude15",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud8f6\ude3d.\ud8ef\ude15",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--pr3x.xn--rv7w",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea7\u2084\u10ab\ud8cb\ude6b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea74\u10ab\ud8cb\ude6b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea74\u2d0b\ud8cb\ude6b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--039c42bq865a.xn--4-wvs27840bnrzm",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--039c42bq865a.xn--4-t0g49302fnrzm",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\udfc0\ud803\ude09\ud83a\uddcf\u3002\ud949\udea7\u2084\u2d0b\ud8cb\ude6b",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud835\udfd3\u3002\u06d7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "5\u3002\u06d7",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "5.xn--nlb",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\udaab\ude29.\u2f95",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\udaab\ude29.\u8c37",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--i183d.xn--6g3a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug26167i.xn--6g3a",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6; V3 (ignored)",
+    "input": "\ufe12\udafb\udc07\u200d.-\u073c\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6; V3 (ignored); A4_2 (ignored)",
+    "input": "\u3002\udafb\udc07\u200d.-\u073c\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored); A4_2 (ignored)",
+    "input": ".xn--hh50e.xn----t2c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V6; V3 (ignored); A4_2 (ignored)",
+    "input": ".xn--1ug05310k.xn----t2c071q",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--y86c71305c.xn----t2c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V6; V3 (ignored)",
+    "input": "xn--1ug1658ftw26f.xn----t2c071q",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\uff0e\ud835\udfd7",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u10ad\ud8be\udccd\ua868\u05ae\u3002\u10be\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2d0d\ud8be\udccd\ua868\u05ae\u3002\u2d1e\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--5cb172r175fug38a.xn--mlj",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--5cb172r175fug38a.xn--0uga051h",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--5cb347co96jug15a.xn--2nd",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--5cb347co96jug15a.xn--2nd059ea",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud800\udef0\u3002\udb05\udcf1",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--k97c.xn--q031e",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u10ab\ud89b\udff8\uade4\uff0e\uda40\udd7c\ud835\udfe2\ud72a\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u10ab\ud89b\udff8\u1100\u1172\u11af\uff0e\uda40\udd7c\ud835\udfe2\u1112\u1171\u11b9\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u10ab\ud89b\udff8\uade4.\uda40\udd7c0\ud72a\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u10ab\ud89b\udff8\u1100\u1172\u11af.\uda40\udd7c0\u1112\u1171\u11b9\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u2d0b\ud89b\udff8\u1100\u1172\u11af.\uda40\udd7c0\u1112\u1171\u11b9\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u2d0b\ud89b\udff8\uade4.\uda40\udd7c0\ud72a\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--i0b436pkl2g2h42a.xn--0-8le8997mulr5f",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--i0b601b6r7l2hs0a.xn--0-8le8997mulr5f",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u2d0b\ud89b\udff8\u1100\u1172\u11af\uff0e\uda40\udd7c\ud835\udfe2\u1112\u1171\u11b9\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u08df\u2d0b\ud89b\udff8\uade4\uff0e\uda40\udd7c\ud835\udfe2\ud72a\u0ae3",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u0784\uff0e\ud83a\udc5d\u0601",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u0784.\ud83a\udc5d\u0601",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--lqb.xn--jfb1808v",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u0acd\u2083.8\ua8c4\u200d\ud83c\udce4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u0acd3.8\ua8c4\u200d\ud83c\udce4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--3-yke.xn--8-sl4et308f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--3-yke.xn--8-ugnv982dbkwm",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u9c4a\u3002\u200c",
+    "output": null
+  },
+  {
+    "input": "xn--rt6a.",
+    "output": "xn--rt6a."
+  },
+  {
+    "input": "\u9c4a.",
+    "output": "xn--rt6a."
+  },
+  {
+    "comment": "C1",
+    "input": "xn--rt6a.xn--0ug",
+    "output": null
+  },
+  {
+    "input": "xn--4-0bd15808a.",
+    "output": "xn--4-0bd15808a."
+  },
+  {
+    "input": "\ud83a\udd3a\u07cc4.",
+    "output": "xn--4-0bd15808a."
+  },
+  {
+    "input": "\ud83a\udd18\u07cc4.",
+    "output": "xn--4-0bd15808a."
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\uff61\u43db",
+    "output": "-.xn--xco"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\u3002\u43db",
+    "output": "-.xn--xco"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.xn--xco",
+    "output": "-.xn--xco"
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u200c\ud908\udce0\uff0e\u200d",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u200c\ud908\udce0.\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--dj8y.",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V6",
+    "input": "xn--0ugz7551c.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud804\uddc0.\udb42\ude31",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--wd1d.xn--k946e",
+    "output": null
+  },
+  {
+    "input": "\ud83a\udd2a.\u03c2",
+    "output": "xn--ie6h.xn--3xa"
+  },
+  {
+    "input": "\ud83a\udd08.\u03a3",
+    "output": "xn--ie6h.xn--4xa"
+  },
+  {
+    "input": "\ud83a\udd2a.\u03c3",
+    "output": "xn--ie6h.xn--4xa"
+  },
+  {
+    "input": "\ud83a\udd08.\u03c3",
+    "output": "xn--ie6h.xn--4xa"
+  },
+  {
+    "input": "xn--ie6h.xn--4xa",
+    "output": "xn--ie6h.xn--4xa"
+  },
+  {
+    "input": "\ud83a\udd08.\u03c2",
+    "output": "xn--ie6h.xn--3xa"
+  },
+  {
+    "input": "xn--ie6h.xn--3xa",
+    "output": "xn--ie6h.xn--3xa"
+  },
+  {
+    "input": "\ud83a\udd2a.\u03a3",
+    "output": "xn--ie6h.xn--4xa"
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u10ba\uff61\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u10ba\u3002\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\u2d1a\u3002\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u10ba\u3002\u03a3",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\u2d1a\u3002\u03c3",
+    "output": null
+  },
+  {
+    "input": "xn--ilj.xn--4xa",
+    "output": "xn--ilj.xn--4xa"
+  },
+  {
+    "input": "\u2d1a.\u03c3",
+    "output": "xn--ilj.xn--4xa"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba.\u03a3",
+    "output": null
+  },
+  {
+    "input": "\u2d1a.\u03c2",
+    "output": "xn--ilj.xn--3xa"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba.\u03c2",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ynd.xn--4xa",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ynd.xn--3xa",
+    "output": null
+  },
+  {
+    "input": "xn--ilj.xn--3xa",
+    "output": "xn--ilj.xn--3xa"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10ba.\u03c3",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--0ug262c.xn--4xa",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--ynd759e.xn--4xa",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--0ug262c.xn--3xa",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--ynd759e.xn--3xa",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\u2d1a\uff61\u03c2",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u10ba\uff61\u03a3",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\u2d1a\uff61\u03c3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u2f95\u3002\u200c\u0310\ua953\ua84e",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u2f95\u3002\u200c\ua953\u0310\ua84e",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u8c37\u3002\u200c\ua953\u0310\ua84e",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--6g3a.xn--0sa8175flwa",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--1ug0273b.xn--0sa359l6n7g13a",
+    "output": null
+  },
+  {
+    "input": "\u6dfd\u3002\u183e",
+    "output": "xn--34w.xn--x7e"
+  },
+  {
+    "input": "xn--34w.xn--x7e",
+    "output": "xn--34w.xn--x7e"
+  },
+  {
+    "input": "\u6dfd.\u183e",
+    "output": "xn--34w.xn--x7e"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uda72\ude29\u10b3\u2753\uff61\ud804\udd28",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uda72\ude29\u10b3\u2753\u3002\ud804\udd28",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uda72\ude29\u2d13\u2753\u3002\ud804\udd28",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--8di78qvw32y.xn--k80d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--rnd896i0j14q.xn--k80d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uda72\ude29\u2d13\u2753\uff61\ud804\udd28",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u17ff\uff61\ud83a\udf33",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u17ff\u3002\ud83a\udf33",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--45e.xn--et6h",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u0652\u200d\uff61\u0ccd\ud805\udeb3",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u0652\u200d\u3002\u0ccd\ud805\udeb3",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--uhb.xn--8tc4527k",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--uhb882k.xn--8tc4527k",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u00df\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6e\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u00df\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6e\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "SS\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6eSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "ss\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6ess",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "Ss\ud880\udc3b\ud8da\udf17\u3002\ud836\ude68\ud83d\udd6eSs",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ss-jl59biy67d.xn--ss-4d11aw87d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--zca20040bgrkh.xn--zca3653v86qa",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "SS\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6eSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "ss\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6ess",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "Ss\ud880\udc3b\ud8da\udf17\uff61\ud836\ude68\ud83d\udd6eSs",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u3002\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--1ug.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb41\udc58\uff0e\udb40\udd2e",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb41\udc58.\udb40\udd2e",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--s136e.",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua9b7\udb37\udd59\uba79\u3002\u249b\udb42\ude07",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua9b7\udb37\udd59\u1106\u1167\u11b0\u3002\u249b\udb42\ude07",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua9b7\udb37\udd59\uba79\u300220.\udb42\ude07",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua9b7\udb37\udd59\u1106\u1167\u11b0\u300220.\udb42\ude07",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ym9av13acp85w.20.xn--d846e",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ym9av13acp85w.xn--dth22121k",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\uff61\ufe12",
+    "output": null
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": "\u200c\u3002\u3002",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": "..",
+    "output": ".."
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": "xn--0ug..",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--y86c",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug.xn--y86c",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-\ud835\udff9.\u00df-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-3.\u00df-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-3.SS-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-3.ss-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-3.Ss-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "V2 (ignored); V3 (ignored)",
+    "input": "xn---3-p9o.ss--",
+    "output": "xn---3-p9o.ss--"
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn---3-p9o.xn--ss---276a",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn---3-p9o.xn-----fia9303a",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-\ud835\udff9.SS-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-\ud835\udff9.ss-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u1872-\ud835\udff9.Ss-\u200c-",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb27\udd9c\u1898\u3002\u1a7f\u2ea2",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ibf35138o.xn--fpfz94g",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\uda1c\udda7\ud835\udfef\u3002\u2488\u1a76\ud835\udfda\uda41\ude0c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uda1c\udda73\u30021.\u1a762\uda41\ude0c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--3-rj42h.1.xn--2-13k96240l",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3-rj42h.xn--2-13k746cq465x",
+    "output": null
+  },
+  {
+    "input": "\ua860\uff0e\u06f2",
+    "output": "xn--5c9a.xn--fmb"
+  },
+  {
+    "input": "\ua860.\u06f2",
+    "output": "xn--5c9a.xn--fmb"
+  },
+  {
+    "input": "xn--5c9a.xn--fmb",
+    "output": "xn--5c9a.xn--fmb"
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ua67d\u200c\ud87e\uddf5\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ua67d\u200c\u9723\ud83c\udd06\uff61\u200c\ud804\udc42\u1b01",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--2q5a751a653w.xn--4sf0725i",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--0ug4208b2vjuk63a.xn--4sf36u6u4w",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u514e\uff61\u183c\udb43\udd1c\ud805\udeb6\ud807\udc3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u514e\u3002\u183c\udb43\udd1c\ud805\udeb6\ud807\udc3f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--b5q.xn--v7e6041kqqd4m251b",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\ud835\udfd9\uff61\u200d\ud835\udff8\u200d\u2077",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "1\u3002\u200d2\u200d7",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "1.xn--27-l1tb",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u1868-\uff61\udb43\udecb\ud835\udff7",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u1868-\u3002\udb43\udecb1",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----z8j.xn--1-5671m",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u10bc\ud9e3\udded\u0f80\u2f87\u3002\u10af\u2640\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u10bc\ud9e3\udded\u0f80\u821b\u3002\u10af\u2640\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2d1c\ud9e3\udded\u0f80\u821b\u3002\u2d0f\u2640\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--zed372mdj2do3v4h.xn--e5h11w",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--zed372mdj2do3v4h.xn--0uga678bgyh",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--zed54dz10wo343g.xn--nnd651i",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--zed54dz10wo343g.xn--nnd089ea464d",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2d1c\ud9e3\udded\u0f80\u2f87\u3002\u2d0f\u2640\u200c\u200c",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\ud804\udc46\ud835\udff0.\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\ud804\udc464.\u200d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--4-xu7i.",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--4-xu7i.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u10be\u7640\uff61\ud805\ude3f\u200d\u200c\ubdbc",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u10be\u7640\uff61\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u10be\u7640\u3002\ud805\ude3f\u200d\u200c\ubdbc",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u10be\u7640\u3002\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u2d1e\u7640\u3002\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u2d1e\u7640\u3002\ud805\ude3f\u200d\u200c\ubdbc",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--mlju35u7qx2f.xn--et3bn23n",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--mlju35u7qx2f.xn--0ugb6122js83c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--2nd6803c7q37d.xn--et3bn23n",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--2nd6803c7q37d.xn--0ugb6122js83c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u2d1e\u7640\uff61\ud805\ude3f\u200d\u200c\u1107\u1170\u11ab",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud97b\udd18\u2d1e\u7640\uff61\ud805\ude3f\u200d\u200c\ubdbc",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "-\ud804\ude36\u248f\uff0e\u248e\ud881\udee2\udb40\udfad",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored); A4_2 (ignored)",
+    "input": "-\ud804\ude368..7.\ud881\udee2\udb40\udfad",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored); A4_2 (ignored)",
+    "input": "xn---8-bv5o..7.xn--c35nf1622b",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----scp6252h.xn--zshy411yzpx2d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u0ecb\u200d\uff0e\u9381\udb43\udc11",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u0ecb\u200d.\u9381\udb43\udc11",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--t8c.xn--iz4a43209d",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--t8c059f.xn--iz4a43209d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud9e5\udef4.-\u1862\u0592\ud836\ude20",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn--ep37b.xn----hec165lho83b",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b\uff0e\u1baa\u03c2\u10a6\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b.\u1baa\u03c2\u10a6\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b.\u1baa\u03c2\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b.\u1baa\u03a3\u10a6\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b.\u1baa\u03c3\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b.\u1baa\u03a3\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--nu4s.xn--4xa153j7im",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--nu4s.xn--4xa153jk8cs1q",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--nu4s.xn--4xa217dxri",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--nu4s.xn--4xa217dxriome",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--nu4s.xn--3xa353jk8cs1q",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--nu4s.xn--3xa417dxriome",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b\uff0e\u1baa\u03c2\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b\uff0e\u1baa\u03a3\u10a6\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b\uff0e\u1baa\u03c3\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ud8bc\udc2b\uff0e\u1baa\u03a3\u2d06\u200d",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u2488\u200c\uaaec\ufe12\uff0e\u0acd",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; A4_2 (ignored)",
+    "input": "1.\u200c\uaaec\u3002.\u0acd",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "1.xn--sv9a..xn--mfc",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; A4_2 (ignored)",
+    "input": "1.xn--0ug7185c..xn--mfc",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--tsh0720cse8b.xn--mfc",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--0ug78o720myr1c.xn--mfc",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u00df\u200d.\u1bf2\ud8d3\udfbc",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "SS\u200d.\u1bf2\ud8d3\udfbc",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "ss\u200d.\u1bf2\ud8d3\udfbc",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "Ss\u200d.\u1bf2\ud8d3\udfbc",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "ss.xn--0zf22107b",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--ss-n1t.xn--0zf22107b",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn--zca870n.xn--0zf22107b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83d\udd7c\uff0e\uffa0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud83d\udd7c.\u1160",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--my8h.xn--psd",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--my8h.xn--cl7c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u7215\uda8d\ude51\uff0e\ud835\udff0\u6c17",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u7215\uda8d\ude51.4\u6c17",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--1zxq3199c.xn--4-678b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V2 (ignored); V3 (ignored)",
+    "input": "\udb39\udf43\u3002\uda04\udd83\ud8e6\udc97--",
+    "output": null
+  },
+  {
+    "comment": "V6; V2 (ignored); V3 (ignored)",
+    "input": "xn--2y75e.xn-----1l15eer88n",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u8530\u3002\udb40\udc79\u08dd-\ud804\ude35",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--sz1a.xn----mrd9984r3dl0i",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c2\u10c5\u3002\u075a",
+    "output": null
+  },
+  {
+    "input": "\u03c2\u2d25\u3002\u075a",
+    "output": "xn--3xa403s.xn--epb"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03a3\u10c5\u3002\u075a",
+    "output": null
+  },
+  {
+    "input": "\u03c3\u2d25\u3002\u075a",
+    "output": "xn--4xa203s.xn--epb"
+  },
+  {
+    "input": "\u03a3\u2d25\u3002\u075a",
+    "output": "xn--4xa203s.xn--epb"
+  },
+  {
+    "input": "xn--4xa203s.xn--epb",
+    "output": "xn--4xa203s.xn--epb"
+  },
+  {
+    "input": "\u03c3\u2d25.\u075a",
+    "output": "xn--4xa203s.xn--epb"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03a3\u10c5.\u075a",
+    "output": null
+  },
+  {
+    "input": "\u03a3\u2d25.\u075a",
+    "output": "xn--4xa203s.xn--epb"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa477d.xn--epb",
+    "output": null
+  },
+  {
+    "input": "xn--3xa403s.xn--epb",
+    "output": "xn--3xa403s.xn--epb"
+  },
+  {
+    "input": "\u03c2\u2d25.\u075a",
+    "output": "xn--3xa403s.xn--epb"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3xa677d.xn--epb",
+    "output": null
+  },
+  {
+    "input": "xn--vkb.xn--08e172a",
+    "output": "xn--vkb.xn--08e172a"
+  },
+  {
+    "input": "\u06bc.\u1e8f\u1864",
+    "output": "xn--vkb.xn--08e172a"
+  },
+  {
+    "input": "\u06bc.y\u0307\u1864",
+    "output": "xn--vkb.xn--08e172a"
+  },
+  {
+    "input": "\u06bc.Y\u0307\u1864",
+    "output": "xn--vkb.xn--08e172a"
+  },
+  {
+    "input": "\u06bc.\u1e8e\u1864",
+    "output": "xn--vkb.xn--08e172a"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--pt9c.xn--hnd666l",
+    "output": null
+  },
+  {
+    "input": "xn--pt9c.xn--0kjya",
+    "output": "xn--pt9c.xn--0kjya"
+  },
+  {
+    "input": "\ud802\ude57.\u2d09\u2d15",
+    "output": "xn--pt9c.xn--0kjya"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\ude57.\u10a9\u10b5",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud802\ude57.\u10a9\u2d15",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--pt9c.xn--hndy",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u200c\u200c\u3124\uff0e\u032e\udb16\ude11\u09c2",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\u200c\u200c\u3124.\u032e\udb16\ude11\u09c2",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--1fk.xn--vta284a9o563a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--0uga242k.xn--vta284a9o563a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10b4\ud836\ude28\u2083\udb40\udc66\uff0e\ud835\udff3\ud804\udcb9\u0b82",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10b4\ud836\ude283\udb40\udc66.7\ud804\udcb9\u0b82",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d14\ud836\ude283\udb40\udc66.7\ud804\udcb9\u0b82",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3-ews6985n35s3g.xn--7-cve6271r",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3-b1g83426a35t0g.xn--7-cve6271r",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d14\ud836\ude28\u2083\udb40\udc66\uff0e\ud835\udff3\ud804\udcb9\u0b82",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u43c8\u200c\u3002\u200c\u2488\ud986\udc95",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u43c8\u200c\u3002\u200c1.\ud986\udc95",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--eco.1.xn--ms39a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug491l.xn--1-rgn.xn--ms39a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--eco.xn--tsh21126d",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug491l.xn--0ug88oot66q",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uff11\uaaf6\u00df\ud807\udca5\uff61\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "1\uaaf6\u00df\ud807\udca5\u3002\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "1\uaaf6SS\ud807\udca5\u3002\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "1\uaaf6ss\ud807\udca5\u3002\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--1ss-ir6ln166b.xn--weg",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--1-qfa2471kdb0d.xn--weg",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uff11\uaaf6SS\ud807\udca5\uff61\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uff11\uaaf6ss\ud807\udca5\uff61\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "1\uaaf6Ss\ud807\udca5\u3002\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uff11\uaaf6Ss\ud807\udca5\uff61\u1dd8",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3j78f.xn--mkb20b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud88a\udd31\u249b\u2fb3\uff0e\ua866\u2488",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud88a\udd3120.\u97f3.\ua8661.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--20-9802c.xn--0w5a.xn--1-eg4e.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--dth6033bzbvx.xn--tsh9439b",
+    "output": null
+  },
+  {
+    "input": "xn--ge6h.xn--oc9a",
+    "output": "xn--ge6h.xn--oc9a"
+  },
+  {
+    "input": "\ud83a\udd28.\ua84f",
+    "output": "xn--ge6h.xn--oc9a"
+  },
+  {
+    "input": "\ud83a\udd06.\ua84f",
+    "output": "xn--ge6h.xn--oc9a"
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\u200c.\u00df\u10a9-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u200c.\u00df\u2d09-",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\u200c.SS\u10a9-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u200c.ss\u2d09-",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "\u200c.Ss\u2d09-",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored); A4_2 (ignored)",
+    "input": ".xn--ss--bi1b",
+    "output": ".xn--ss--bi1b"
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn--0ug.xn--ss--bi1b",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored); A4_2 (ignored)",
+    "input": ".xn--ss--4rn",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; V3 (ignored)",
+    "input": "xn--0ug.xn--ss--4rn",
+    "output": null
+  },
+  {
+    "comment": "C1; V3 (ignored)",
+    "input": "xn--0ug.xn----pfa2305a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; V3 (ignored)",
+    "input": "xn--0ug.xn----pfa042j",
+    "output": null
+  },
+  {
+    "input": "\u9f59--\ud835\udff0.\u00df",
+    "output": "xn----4-p16k.xn--zca"
+  },
+  {
+    "input": "\u9f59--4.\u00df",
+    "output": "xn----4-p16k.xn--zca"
+  },
+  {
+    "input": "\u9f59--4.SS",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "\u9f59--4.ss",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "\u9f59--4.Ss",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "xn----4-p16k.ss",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "xn----4-p16k.xn--zca",
+    "output": "xn----4-p16k.xn--zca"
+  },
+  {
+    "input": "\u9f59--\ud835\udff0.SS",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "\u9f59--\ud835\udff0.ss",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "input": "\u9f59--\ud835\udff0.Ss",
+    "output": "xn----4-p16k.ss"
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\udacf\udc99\udb40\uded8\uff61?-\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\udacf\udc99\udb40\uded8\u3002?-\u200d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "xn--ct86d8w51a.?-",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "xn--ct86d8w51a.xn--?--n1t",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "xn--ct86d8w51a.?-\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "XN--CT86D8W51A.?-\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "Xn--Ct86d8w51a.?-\u200d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud836\ude9e\u10b0\uff61\ucaa1",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud836\ude9e\u10b0\uff61\u110d\u1168\u11a8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud836\ude9e\u10b0\u3002\ucaa1",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud836\ude9e\u10b0\u3002\u110d\u1168\u11a8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud836\ude9e\u2d10\u3002\u110d\u1168\u11a8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud836\ude9e\u2d10\u3002\ucaa1",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--7kj1858k.xn--pi6b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ond3755u.xn--pi6b",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud836\ude9e\u2d10\uff61\u110d\u1168\u11a8",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud836\ude9e\u2d10\uff61\ucaa1",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u1845\uff10\u200c\uff61\u23a2\udb52\ude04",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u18450\u200c\u3002\u23a2\udb52\ude04",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0-z6j.xn--8lh28773l",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0-z6jy93b.xn--8lh28773l",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200d\u00df",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a9\ua369\u17d3.\u200d\u00df",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a9\ua369\u17d3.\u200dSS",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a9\ua369\u17d3.\u200dss",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--9-i0j5967eg3qz.ss",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9-i0j5967eg3qz.xn--ss-l1t",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--9-i0j5967eg3qz.xn--zca770n",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dSS",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dss",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a9\ua369\u17d3.\u200dSs",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\ud88a\udf9a\uff19\ua369\u17d3\uff0e\u200dSs",
+    "output": null
+  },
+  {
+    "input": "\ua5f7\ud804\udd80.\u075d\ud802\ude52",
+    "output": "xn--ju8a625r.xn--hpb0073k"
+  },
+  {
+    "input": "xn--ju8a625r.xn--hpb0073k",
+    "output": "xn--ju8a625r.xn--hpb0073k"
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u10af\udb40\udd4b-\uff0e\u200d\u10a9",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "\u10af\udb40\udd4b-.\u200d\u10a9",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "\u2d0f\udb40\udd4b-.\u200d\u2d09",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----3vs.xn--0kj",
+    "output": "xn----3vs.xn--0kj"
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "xn----3vs.xn--1ug532c",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----00g.xn--hnd",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----00g.xn--hnd399e",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "\u2d0f\udb40\udd4b-\uff0e\u200d\u2d09",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u1714\u3002\udb40\udda3-\ud804\udeea",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--fze.xn----ly8i",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-\uff0e\uda60\udfdc\u05bd\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-.\uda60\udfdc\u05bd\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-.\uda60\udfdc\u05bdSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-.\uda60\udfdc\u05bdss",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-.\uda60\udfdc\u05bdSs",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----pw5e.xn--ss-7jd10716y",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----pw5e.xn--zca50wfv060a",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-\uff0e\uda60\udfdc\u05bdSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-\uff0e\uda60\udfdc\u05bdss",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\uabe8-\uff0e\uda60\udfdc\u05bdSs",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud835\udfe5\u266e\ud805\udf2b\u08ed\uff0e\u17d2\ud805\udf2b8\udb40\udd8f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "3\u266e\ud805\udf2b\u08ed.\u17d2\ud805\udf2b8\udb40\udd8f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--3-ksd277tlo7s.xn--8-f0jx021l",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "-\uff61\uda14\udf00\u200d\u2761",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "-\u3002\uda14\udf00\u200d\u2761",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "-.xn--nei54421f",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "-.xn--1ug800aq795s",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ud835\udfd3\u2631\ud835\udfd0\uda57\udc35\uff61\ud836\udeae\ud902\udc73",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "5\u26312\uda57\udc35\u3002\ud836\udeae\ud902\udc73",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--52-dwx47758j.xn--kd3hk431k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "-.-\u251c\uda1a\udda3",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "-.xn----ukp70432h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c2\uff0e\ufdc1\ud83d\udf9b\u2488",
+    "output": null
+  },
+  {
+    "input": "\u03c2.\u0641\u0645\u064a\ud83d\udf9b1.",
+    "output": "xn--3xa.xn--1-gocmu97674d."
+  },
+  {
+    "input": "\u03a3.\u0641\u0645\u064a\ud83d\udf9b1.",
+    "output": "xn--4xa.xn--1-gocmu97674d."
+  },
+  {
+    "input": "\u03c3.\u0641\u0645\u064a\ud83d\udf9b1.",
+    "output": "xn--4xa.xn--1-gocmu97674d."
+  },
+  {
+    "input": "xn--4xa.xn--1-gocmu97674d.",
+    "output": "xn--4xa.xn--1-gocmu97674d."
+  },
+  {
+    "input": "xn--3xa.xn--1-gocmu97674d.",
+    "output": "xn--3xa.xn--1-gocmu97674d."
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03a3\uff0e\ufdc1\ud83d\udf9b\u2488",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u03c3\uff0e\ufdc1\ud83d\udf9b\u2488",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa.xn--dhbip2802atb20c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3xa.xn--dhbip2802atb20c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "9\udb40\udde5\uff0e\udb6b\udd34\u1893",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "9\udb40\udde5.\udb6b\udd34\u1893",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "9.xn--dbf91222q",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ufe12\u10b6\u0366\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; A4_2 (ignored)",
+    "input": "\u3002\u10b6\u0366.\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": "\u3002\u2d16\u0366.\u200c",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--hva754s.",
+    "output": ".xn--hva754s."
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": ".xn--hva754s.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--hva929d.",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; A4_2 (ignored)",
+    "input": ".xn--hva929d.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ufe12\u2d16\u0366\uff0e\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hva754sy94k.",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--hva754sy94k.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hva929dl29p.",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--hva929dl29p.xn--0ug",
+    "output": null
+  },
+  {
+    "input": "xn--hva754s.",
+    "output": "xn--hva754s."
+  },
+  {
+    "input": "\u2d16\u0366.",
+    "output": "xn--hva754s."
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10b6\u0366.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hva929d.",
+    "output": null
+  },
+  {
+    "input": "xn--hzb.xn--ukj4430l",
+    "output": "xn--hzb.xn--ukj4430l"
+  },
+  {
+    "input": "\u08bb.\u2d03\ud838\udc12",
+    "output": "xn--hzb.xn--ukj4430l"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u08bb.\u10a3\ud838\udc12",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--hzb.xn--bnd2938u",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u200d\u200c\u3002\uff12\u4af7\udb42\uddf7",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; P1; V6",
+    "input": "\u200d\u200c\u30022\u4af7\udb42\uddf7",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--2-me5ay1273i",
+    "output": null
+  },
+  {
+    "comment": "C1; C2; V6",
+    "input": "xn--0ugb.xn--2-me5ay1273i",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "-\ud838\udc24\udb32\udc10\u3002\ud9e2\udf16",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----rq4re4997d.xn--l707b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\udb8d\udec2\ufe12\u200c\u37c0\uff0e\u0624\u2488",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\udb8d\udec2\ufe12\u200c\u37c0\uff0e\u0648\u0654\u2488",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--z272f.xn--etl.xn--1-smc.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--etlt457ccrq7h.xn--jgb476m",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug754gxl4ldlt0k.xn--jgb476m",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u07fc\ud803\ude06.\ud80d\udd8f\ufe12\ud8ea\ude29\u10b0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u07fc\ud803\ude06.\ud80d\udd8f\u3002\ud8ea\ude29\u10b0",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u07fc\ud803\ude06.\ud80d\udd8f\u3002\ud8ea\ude29\u2d10",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0tb8725k.xn--tu8d.xn--7kj73887a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0tb8725k.xn--tu8d.xn--ond97931d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u07fc\ud803\ude06.\ud80d\udd8f\ufe12\ud8ea\ude29\u2d10",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0tb8725k.xn--7kj9008dt18a7py9c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--0tb8725k.xn--ond3562jt18a7py9c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c5\u26ad\udb41\uddab\u22c3\uff61\ud804\udf3c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10c5\u26ad\udb41\uddab\u22c3\u3002\ud804\udf3c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2d25\u26ad\udb41\uddab\u22c3\u3002\ud804\udf3c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--vfh16m67gx1162b.xn--ro1d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--9nd623g4zc5z060c.xn--ro1d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2d25\u26ad\udb41\uddab\u22c3\uff61\ud804\udf3c",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\udb40\udd93\u26cf-\u3002\ua852",
+    "output": "xn----o9p.xn--rc9a"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn----o9p.xn--rc9a",
+    "output": "xn----o9p.xn--rc9a"
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u650c\uabed\u3002\u1896-\u10b8",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u650c\uabed\u3002\u1896-\u2d18",
+    "output": null
+  },
+  {
+    "input": "xn--p9ut19m.xn----mck373i",
+    "output": "xn--p9ut19m.xn----mck373i"
+  },
+  {
+    "input": "\u650c\uabed.\u1896-\u2d18",
+    "output": "xn--p9ut19m.xn----mck373i"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u650c\uabed.\u1896-\u10b8",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--p9ut19m.xn----k1g451d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1ug592ykp6b.xn----mck373i",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug592ykp6b.xn----k1g451d",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\ua5a8\uff0e\u2497\uff13\ud212\u06f3",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\ua5a8\uff0e\u2497\uff13\u1110\u116d\u11a9\u06f3",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\ua5a8.16.3\ud212\u06f3",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c\ua5a8.16.3\u1110\u116d\u11a9\u06f3",
+    "output": null
+  },
+  {
+    "input": "xn--9r8a.16.xn--3-nyc0117m",
+    "output": "xn--9r8a.16.xn--3-nyc0117m"
+  },
+  {
+    "input": "\ua5a8.16.3\ud212\u06f3",
+    "output": "xn--9r8a.16.xn--3-nyc0117m"
+  },
+  {
+    "input": "\ua5a8.16.3\u1110\u116d\u11a9\u06f3",
+    "output": "xn--9r8a.16.xn--3-nyc0117m"
+  },
+  {
+    "comment": "C1",
+    "input": "xn--0ug2473c.16.xn--3-nyc0117m",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--9r8a.xn--3-nyc678tu07m",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug2473c.xn--3-nyc678tu07m",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\ud835\udfcf\ud836\ude19\u2e16.\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "1\ud836\ude19\u2e16.\u200d",
+    "output": null
+  },
+  {
+    "input": "xn--1-5bt6845n.",
+    "output": "xn--1-5bt6845n."
+  },
+  {
+    "input": "1\ud836\ude19\u2e16.",
+    "output": "xn--1-5bt6845n."
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1-5bt6845n.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "F\udb40\udd5f\uff61\ud9fd\uddc5\u265a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "F\udb40\udd5f\u3002\ud9fd\uddc5\u265a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "f\udb40\udd5f\u3002\ud9fd\uddc5\u265a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "f.xn--45hz6953f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "f\udb40\udd5f\uff61\ud9fd\uddc5\u265a",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0b4d\ud804\udd34\u1de9\u3002\ud835\udfee\u10b8\ud838\udc28\ud8ce\udd47",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0b4d\ud804\udd34\u1de9\u30022\u10b8\ud838\udc28\ud8ce\udd47",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0b4d\ud804\udd34\u1de9\u30022\u2d18\ud838\udc28\ud8ce\udd47",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--9ic246gs21p.xn--2-nws2918ndrjr",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--9ic246gs21p.xn--2-k1g43076adrwq",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u0b4d\ud804\udd34\u1de9\u3002\ud835\udfee\u2d18\ud838\udc28\ud8ce\udd47",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\uda0e\udc2d\u200c\u200c\u2488\u3002\u52c9\ud804\udc45",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; A4_2 (ignored)",
+    "input": "\uda0e\udc2d\u200c\u200c1.\u3002\u52c9\ud804\udc45",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": "xn--1-yi00h..xn--4grs325b",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; A4_2 (ignored)",
+    "input": "xn--1-rgna61159u..xn--4grs325b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tsh11906f.xn--4grs325b",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0uga855aez302a.xn--4grs325b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1843.\u73bf\ud96c\ude1c\udb15\udf90",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--27e.xn--7cy81125a0yq4a",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u20da\uff0e\ud805\ude3f-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u20da.\ud805\ude3f-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--w0g.xn----bd0j",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u1082-\u200d\ua8ea\uff0e\ua84a\u200d\ud9b3\ude33",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\u1082-\u200d\ua8ea.\ua84a\u200d\ud9b3\ude33",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----gyg3618i.xn--jc9ao4185a",
+    "output": null
+  },
+  {
+    "comment": "C2; V5; V6",
+    "input": "xn----gyg250jio7k.xn--1ug8774cri56d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud804\ude35\u5eca.\ud802\udc0d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--xytw701b.xn--yc9c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10be\ud899\udec0\ud82d\uddfb\uff0e\u1897\ub9ab",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10be\ud899\udec0\ud82d\uddfb\uff0e\u1897\u1105\u1174\u11c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10be\ud899\udec0\ud82d\uddfb.\u1897\ub9ab",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10be\ud899\udec0\ud82d\uddfb.\u1897\u1105\u1174\u11c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d1e\ud899\udec0\ud82d\uddfb.\u1897\u1105\u1174\u11c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d1e\ud899\udec0\ud82d\uddfb.\u1897\ub9ab",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--mlj0486jgl2j.xn--hbf6853f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--2nd8876sgl2j.xn--hbf6853f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d1e\ud899\udec0\ud82d\uddfb\uff0e\u1897\u1105\u1174\u11c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d1e\ud899\udec0\ud82d\uddfb\uff0e\u1897\ub9ab",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u00df\u200d\u103a\uff61\u2488",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "SS\u200d\u103a\uff61\u2488",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "ss\u200d\u103a\uff61\u2488",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "Ss\u200d\u103a\uff61\u2488",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ss-f4j.xn--tsh",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--ss-f4j585j.xn--tsh",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--zca679eh2l.xn--tsh",
+    "output": null
+  },
+  {
+    "input": "\u06cc\ud802\ude3f\uff0e\u00df\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--zca216edt0r"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f.\u00df\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--zca216edt0r"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f.SS\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f.ss\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "xn--clb2593k.xn--ss-toj6092t",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "xn--clb2593k.xn--zca216edt0r",
+    "output": "xn--clb2593k.xn--zca216edt0r"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f\uff0eSS\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f\uff0ess\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f.Ss\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "input": "\u06cc\ud802\ude3f\uff0eSs\u0f84\ud804\udf6c",
+    "output": "xn--clb2593k.xn--ss-toj6092t"
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0f9f\uff0e-\u082a",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0f9f.-\u082a",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--vfd.xn----fhd",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1d6c\udb40\udda0\uff0e\ud552\u2492\u2488\udbe0\udd26",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1d6c\udb40\udda0\uff0e\u1111\u1175\u11bd\u2492\u2488\udbe0\udd26",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1d6c\udb40\udda0.\ud55211.1.\udbe0\udd26",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1d6c\udb40\udda0.\u1111\u1175\u11bd11.1.\udbe0\udd26",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tbg.xn--11-5o7k.1.xn--k469f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tbg.xn--tsht7586kyts9l",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2488\u270c\uda3e\udf1f\uff0e\ud835\udfe1\ud943\udc63",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "1.\u270c\uda3e\udf1f.9\ud943\udc63",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "1.xn--7bi44996f.xn--9-o706d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tsh24g49550b.xn--9-o706d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c2\uff0e\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c2.\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03a3.\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c3.\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--4xa.xn--0f9ars",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--3xa.xn--0f9ars",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03a3\uff0e\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\u03c3\uff0e\ua9c0\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2786\ud99e\uddd5\u1ed7\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2786\ud99e\uddd5o\u0302\u0303\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u2786\ud99e\uddd5\u1ed71..\uda06\udf12\ud945\ude2e\u085b9",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u2786\ud99e\uddd5o\u0302\u03031..\uda06\udf12\ud945\ude2e\u085b9",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u2786\ud99e\uddd5O\u0302\u03031..\uda06\udf12\ud945\ude2e\u085b9",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u2786\ud99e\uddd5\u1ed61..\uda06\udf12\ud945\ude2e\u085b9",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": "xn--1-3xm292b6044r..xn--9-6jd87310jtcqs",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2786\ud99e\uddd5O\u0302\u0303\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2786\ud99e\uddd5\u1ed6\u2488\uff0e\uda06\udf12\ud945\ude2e\u085b\ud835\udfeb",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6lg26tvvc6v99z.xn--9-6jd87310jtcqs",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--ye6h",
+    "output": ".xn--ye6h"
+  },
+  {
+    "input": "xn--ye6h",
+    "output": "xn--ye6h"
+  },
+  {
+    "input": "\ud83a\udd3a",
+    "output": "xn--ye6h"
+  },
+  {
+    "input": "\ud83a\udd18",
+    "output": "xn--ye6h"
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "\u073c\u200c-\u3002\ud80d\udc3e\u00df",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "\u073c\u200c-\u3002\ud80d\udc3eSS",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "\u073c\u200c-\u3002\ud80d\udc3ess",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "\u073c\u200c-\u3002\ud80d\udc3eSs",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----s2c.xn--ss-066q",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6; V3 (ignored)",
+    "input": "xn----s2c071q.xn--ss-066q",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6; V3 (ignored)",
+    "input": "xn----s2c071q.xn--zca7848m",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "-\uda9d\udf6c\u135e\ud805\udf27.\u1deb-\ufe12",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "-\uda9d\udf6c\u135e\ud805\udf27.\u1deb-\u3002",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----b5h1837n2ok9f.xn----mkm.",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----b5h1837n2ok9f.xn----mkmw278h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ufe12.\uda2a\udc21\u1a59",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u3002.\uda2a\udc21\u1a59",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": "..xn--cof61594i",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--y86c.xn--cof61594i",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\ud807\udc3a.-\uda05\udfcf",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--jk3d.xn----iz68g",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb43\udee9\uff0e\u8d4f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb43\udee9.\u8d4f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--2856e.xn--6o3a",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u10ad\uff0e\ud8f4\udde6\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u10ad.\ud8f4\udde6\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2d0d.\ud8f4\udde6\u200c",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4kj.xn--p01x",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--4kj.xn--0ug56448b",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--lnd.xn--p01x",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--lnd.xn--0ug56448b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u2d0d\uff0e\ud8f4\udde6\u200c",
+    "output": null
+  },
+  {
+    "input": "\ud835\udfdb\uff0e\uf9f8",
+    "output": "3.xn--6vz"
+  },
+  {
+    "input": "\ud835\udfdb\uff0e\u7b20",
+    "output": "3.xn--6vz"
+  },
+  {
+    "input": "3.\u7b20",
+    "output": "3.xn--6vz"
+  },
+  {
+    "input": "3.xn--6vz",
+    "output": "3.xn--6vz"
+  },
+  {
+    "comment": "C2; P1; V6; V3 (ignored)",
+    "input": "-\u200d.\u10be\ud800\udef7",
+    "output": null
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "-\u200d.\u2d1e\ud800\udef7",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.xn--mlj8559d",
+    "output": "-.xn--mlj8559d"
+  },
+  {
+    "comment": "C2; V3 (ignored)",
+    "input": "xn----ugn.xn--mlj8559d",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "-.xn--2nd2315j",
+    "output": null
+  },
+  {
+    "comment": "C2; V6; V3 (ignored)",
+    "input": "xn----ugn.xn--2nd2315j",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c2\u00df\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c2\u00df\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3SS\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c3ss\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3ss\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--ss-ubc826a.xn--xmc",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--ss-ubc826ab34b.xn--xmc",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3\u00df\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c3\u00df\u0731.\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--zca39lk1di19a.xn--xmc",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--zca19ln1di19a.xn--xmc",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3SS\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c3ss\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3ss\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03a3\u00df\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u200d\u03c3\u00df\u0731\uff0e\u0bcd",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c2\uff0e\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c2.\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3.\u03a3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c3.\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c3.\u03c3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3.\u03c3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa502av8297a.xn--4xa6055k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3.\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa502av8297a.xn--3xa8055k",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3xa702av8297a.xn--3xa8055k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03a3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c3\uff0e\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03c3\uff0e\u03c3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03c3\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb5c\udef5\u09cd\u03a3\uff0e\u03c2\ud802\ude3f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud94e\udd12\uff61\ub967",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud94e\udd12\uff61\u1105\u1172\u11b6",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud94e\udd12\u3002\ub967",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud94e\udd12\u3002\u1105\u1172\u11b6",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--s264a.xn--pw2b",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1846\ud805\udcdd\uff0e\ud83b\udd46",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u1846\ud805\udcdd.\ud83b\udd46",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--57e0440k.xn--k86h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udbef\udfe6\uff61\u183d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udbef\udfe6\u3002\u183d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--j890g.xn--w7e",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u5b03\ud834\udf4c\uff0e\u200d\u0b44",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u5b03\ud834\udf4c.\u200d\u0b44",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--b6s0078f.xn--0ic",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "xn--b6s0078f.xn--0ic557h",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c.\ud93d\udee4",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--q823a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug.xn--q823a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udaa9\uded5\u10a3\u4805\uff0e\ud803\ude11",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udaa9\uded5\u10a3\u4805.\ud803\ude11",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udaa9\uded5\u2d03\u4805.\ud803\ude11",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--ukju77frl47r.xn--yl0d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--bnd074zr557n.xn--yl0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udaa9\uded5\u2d03\u4805\uff0e\ud803\ude11",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "-\uff61\ufe12",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored); A4_2 (ignored)",
+    "input": "-\u3002\u3002",
+    "output": "-.."
+  },
+  {
+    "comment": "V3 (ignored); A4_2 (ignored)",
+    "input": "-..",
+    "output": "-.."
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "-.xn--y86c",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d.F",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d.f",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".f",
+    "output": ".f"
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1ug.f",
+    "output": null
+  },
+  {
+    "input": "f",
+    "output": "f"
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\uff61\u00df",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\u3002\u00df",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\u3002SS",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\u3002ss",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\u3002Ss",
+    "output": null
+  },
+  {
+    "input": "xn--9bm.ss",
+    "output": "xn--9bm.ss"
+  },
+  {
+    "input": "\u3a32.ss",
+    "output": "xn--9bm.ss"
+  },
+  {
+    "input": "\u3a32.SS",
+    "output": "xn--9bm.ss"
+  },
+  {
+    "input": "\u3a32.Ss",
+    "output": "xn--9bm.ss"
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1ug914h.ss",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "xn--1ug914h.xn--zca",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\uff61SS",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\uff61ss",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\u200d\u3a32\uff61Ss",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\uff0e\udbc3\ude28",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d.\udbc3\ude28",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--h327f",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1ug.xn--h327f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--98e.xn--om9c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\uaaf6\u188f\u0e3a\uff12.\ud800\udee2\u0745\u0f9f\ufe12",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uaaf6\u188f\u0e3a2.\ud800\udee2\u0745\u0f9f\u3002",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--2-2zf840fk16m.xn--sob093b2m7s.",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--2-2zf840fk16m.xn--sob093bj62sz9d",
+    "output": null
+  },
+  {
+    "input": "\ud835\udfce\u3002\u752f",
+    "output": "0.xn--qny"
+  },
+  {
+    "input": "0\u3002\u752f",
+    "output": "0.xn--qny"
+  },
+  {
+    "input": "0.xn--qny",
+    "output": "0.xn--qny"
+  },
+  {
+    "input": "0.\u752f",
+    "output": "0.xn--qny"
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\u2f86\uff0e\uaaf6",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\u820c.\uaaf6",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn----ef8c.xn--2v9a",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\uff61\u1898",
+    "output": "-.xn--ibf"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\u3002\u1898",
+    "output": "-.xn--ibf"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.xn--ibf",
+    "output": "-.xn--ibf"
+  },
+  {
+    "comment": "C1",
+    "input": "\u74bc\ud836\ude2d\uff61\u200c\udb40\udddf",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u74bc\ud836\ude2d\u3002\u200c\udb40\udddf",
+    "output": null
+  },
+  {
+    "input": "xn--gky8837e.",
+    "output": "xn--gky8837e."
+  },
+  {
+    "input": "\u74bc\ud836\ude2d.",
+    "output": "xn--gky8837e."
+  },
+  {
+    "comment": "C1",
+    "input": "xn--gky8837e.xn--0ug",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u200c.\u200c",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "xn--0ug.xn--0ug",
+    "output": null
+  },
+  {
+    "input": "xn--157b.xn--gnb",
+    "output": "xn--157b.xn--gnb"
+  },
+  {
+    "input": "\ud29b.\u0716",
+    "output": "xn--157b.xn--gnb"
+  },
+  {
+    "input": "\u1110\u1171\u11c2.\u0716",
+    "output": "xn--157b.xn--gnb"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10b7\uff0e\u05c2\ud804\udd34\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10b7\uff0e\ud804\udd34\u05c2\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u10b7.\ud804\udd34\u05c2\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2d17.\ud804\udd34\u05c2\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--flj.xn--qdb0605f14ycrms3c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--vnd.xn--qdb0605f14ycrms3c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2d17\uff0e\ud804\udd34\u05c2\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2d17\uff0e\u05c2\ud804\udd34\ua9b7\ud920\udce8",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u2488\u916b\ufe12\u3002\u08d6",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "1.\u916b\u3002\u3002\u08d6",
+    "output": null
+  },
+  {
+    "comment": "V5; A4_2 (ignored)",
+    "input": "1.xn--8j4a..xn--8zb",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--tsh4490bfe8c.xn--8zb",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--co6h.xn--1-h1g429s",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--co6h.xn--1-kwssa",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--co6h.xn--1-h1gs",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua806\u3002\ud8ad\ude8f\u0fb0\u2495",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\ua806\u3002\ud8ad\ude8f\u0fb014.",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--l98a.xn--14-jsj57880f.",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--l98a.xn--dgd218hhp28d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "\ud835\udfe04\udb40\uddd7\ud834\ude3b\uff0e\u200d\ud800\udef5\u26e7\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2",
+    "input": "84\udb40\uddd7\ud834\ude3b.\u200d\ud800\udef5\u26e7\u200d",
+    "output": null
+  },
+  {
+    "input": "xn--84-s850a.xn--59h6326e",
+    "output": "xn--84-s850a.xn--59h6326e"
+  },
+  {
+    "input": "84\ud834\ude3b.\ud800\udef5\u26e7",
+    "output": "xn--84-s850a.xn--59h6326e"
+  },
+  {
+    "comment": "C2",
+    "input": "xn--84-s850a.xn--1uga573cfq1w",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ud975\udf0e\u2488\uff61\u200c\ud835\udfe4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; A4_2 (ignored)",
+    "input": "\ud975\udf0e1.\u3002\u200c2",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; A4_2 (ignored)",
+    "input": "xn--1-ex54e..xn--2-rgn",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--tsh94183d.xn--2-rgn",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\uff61\u00df\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\u3002\u00df\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\u3002SS\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\u3002ss\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\u3002Ss\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--ss-bh7o",
+    "output": ".xn--ss-bh7o"
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--0ugb.xn--ss-bh7o",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "xn--0ugb.xn--zca0732l",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\uff61SS\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\uff61ss\ud805\udcc3",
+    "output": null
+  },
+  {
+    "comment": "C1; C2",
+    "input": "\u200d\u200c\udb40\uddaa\uff61Ss\ud805\udcc3",
+    "output": null
+  },
+  {
+    "input": "xn--ss-bh7o",
+    "output": "xn--ss-bh7o"
+  },
+  {
+    "input": "ss\ud805\udcc3",
+    "output": "xn--ss-bh7o"
+  },
+  {
+    "input": "SS\ud805\udcc3",
+    "output": "xn--ss-bh7o"
+  },
+  {
+    "input": "Ss\ud805\udcc3",
+    "output": "xn--ss-bh7o"
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\ufe12\u200c\u30f6\u44a9.\ua86a",
+    "output": null
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": "\u3002\u200c\u30f6\u44a9.\ua86a",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--qekw60d.xn--gd9a",
+    "output": ".xn--qekw60d.xn--gd9a"
+  },
+  {
+    "comment": "C1; A4_2 (ignored)",
+    "input": ".xn--0ug287dj0o.xn--gd9a",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--qekw60dns9k.xn--gd9a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug287dj0or48o.xn--gd9a",
+    "output": null
+  },
+  {
+    "input": "xn--qekw60d.xn--gd9a",
+    "output": "xn--qekw60d.xn--gd9a"
+  },
+  {
+    "input": "\u30f6\u44a9.\ua86a",
+    "output": "xn--qekw60d.xn--gd9a"
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\u2488\ud852\udf8d.\udb49\udccb\u1a60",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c1.\ud852\udf8d.\udb49\udccb\u1a60",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "1.xn--4x6j.xn--jof45148n",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--1-rgn.xn--4x6j.xn--jof45148n",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--tshw462r.xn--jof45148n",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ug88o7471d.xn--jof45148n",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud834\udd75\uff61\ud835\udfeb\ud838\udc08\u4b3a\u2488",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud834\udd75\u30029\ud838\udc08\u4b3a1.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3f1h.xn--91-030c1650n.",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--3f1h.xn--9-ecp936non25a",
+    "output": null
+  },
+  {
+    "input": "xn--8c1a.xn--2ib8jn539l",
+    "output": "xn--8c1a.xn--2ib8jn539l"
+  },
+  {
+    "input": "\u821b.\u067d\ud83a\udd34\u06bb",
+    "output": "xn--8c1a.xn--2ib8jn539l"
+  },
+  {
+    "input": "\u821b.\u067d\ud83a\udd12\u06bb",
+    "output": "xn--8c1a.xn--2ib8jn539l"
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\udb40\udd710\uff61\u17cf\u1dfd\ud187\uc2ed",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\udb40\udd710\uff61\u17cf\u1dfd\u1110\u1168\u11aa\u1109\u1175\u11b8",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\udb40\udd710\u3002\u17cf\u1dfd\ud187\uc2ed",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-\udb40\udd710\u3002\u17cf\u1dfd\u1110\u1168\u11aa\u1109\u1175\u11b8",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "-0.xn--r4e872ah77nghm",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u10b5\u10e0\uff61\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u10b5\u10e0\u3002\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u2d1f\u2d15\u10e0\u3002\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u10b5\u1ca0\u3002\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--tndt4hvw.xn--9ic",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--1od7wz74eeb.xn--9ic",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u2d1f\u2d15\u10e0\uff61\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u10b5\u1ca0\uff61\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u2d15\u10e0\u3002\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--3nd0etsm92g.xn--9ic",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u115f\u10bf\u2d15\u10e0\uff61\u0b4d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--l96h.xn--03e93aq365d",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\ud835\udfdb\ud834\uddaa\ua8c4\uff61\ua8ea-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\ud835\udfdb\ua8c4\ud834\uddaa\uff61\ua8ea-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "3\ua8c4\ud834\uddaa\u3002\ua8ea-",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn--3-sl4eu679e.xn----xn4e",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1139\uff61\u0eca\uda42\udfe4\udb40\udd1e",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1139\u3002\u0eca\uda42\udfe4\udb40\udd1e",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--lrd.xn--s8c05302k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10a6\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10a6\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d06\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd2f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--xkjw3965g.xn--ne6h",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--end82983m.xn--ne6h",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d06\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd2f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d06\udaae\udca9.\udb40\udda1\ufe09\ud83a\udd0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u2d06\udaae\udca9\uff0e\udb40\udda1\ufe09\ud83a\udd0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udee8.\ud9d5\udfe2\ud835\udfe8\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udee8.\ud9d5\udfe26\ua8c4",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--mi60a.xn--6-sl4es8023c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud800\udef8\udb79\ude0b\u10c2.\u10a1",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud800\udef8\udb79\ude0b\u2d22.\u2d01",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud800\udef8\udb79\ude0b\u10c2.\u2d01",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6nd5215jr2u0h.xn--skj",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--qlj1559dr224h.xn--skj",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6nd5215jr2u0h.xn--8md",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03c2",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03a3",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua8064\uda65\udf86\u3002\ud88a\ude67\udb41\udcb9\u03c3",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4-w93ej7463a9io5a.xn--4xa31142bk3f0d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4-w93ej7463a9io5a.xn--3xa51142bk3f0d",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03a3",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud91d\udc7f\ua806\u2084\uda65\udf86\uff61\ud88a\ude67\udb41\udcb9\u03c3",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud8ba\udcac\u3002\u0729\u3002\ucbd95",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\ud8ba\udcac\u3002\u0729\u3002\u110d\u1173\u11ac5",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--t92s.xn--znb.xn--5-y88f",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u17ca.\u200d\ud835\udfee\ud804\udc3f",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "\u17ca.\u200d2\ud804\udc3f",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--m4e.xn--2-ku7i",
+    "output": null
+  },
+  {
+    "comment": "C2; V5",
+    "input": "xn--m4e.xn--2-tgnv469h",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uaaf6\u3002\u5b36\u00df\u847d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uaaf6\u3002\u5b36SS\u847d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uaaf6\u3002\u5b36ss\u847d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\uaaf6\u3002\u5b36Ss\u847d",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--2v9a.xn--ss-q40dp97m",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--2v9a.xn--zca7637b14za",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03c2\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03c2\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03a3\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03c3\ud805\udc3d\ud896\udc88\ud805\udf2b\u3002\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--4xa2260lk3b8z15g.xn--tw9ct349a",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--4xa2260lk3b8z15g.xn--0ug4653g2xzf",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--3xa4260lk3b8z15g.xn--0ug4653g2xzf",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03a3\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u03c3\ud805\udc3d\ud896\udc88\ud805\udf2b\uff61\ud83a\udf29\u200c\ud802\udec4",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2ea2\ud9df\ude85\ud835\udfe4\uff61\u200d\ud83d\udeb7",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2ea2\ud9df\ude852\u3002\u200d\ud83d\udeb7",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--2-4jtr4282f.xn--m78h",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--2-4jtr4282f.xn--1ugz946p",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud836\ude25\u3002\u2adf\ud804\ude3e",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--n82h.xn--63iw010f",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6; V3 (ignored)",
+    "input": "-\u1897\u200c\ud83c\udd04.\ud805\udf22",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----pck1820x.xn--9h2d",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6; V3 (ignored)",
+    "input": "xn----pck312bx563c.xn--9h2d",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\u17b4.\ucb87-",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "\u17b4.\u110d\u1170\u11ae-",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--z3e.xn----938f",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6",
+    "input": "\u200c\ud805\udcc2\u3002\u2488-\udbc2\ude9b",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\u200c\ud805\udcc2\u30021.-\udbc2\ude9b",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn--wz1d.1.xn----rg03o",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; V3 (ignored)",
+    "input": "xn--0ugy057g.1.xn----rg03o",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--wz1d.xn----dcp29674o",
+    "output": null
+  },
+  {
+    "comment": "C1; V6",
+    "input": "xn--0ugy057g.xn----dcp29674o",
+    "output": null
+  },
+  {
+    "comment": "A4_2 (ignored)",
+    "input": ".xn--hcb32bni",
+    "output": ".xn--hcb32bni"
+  },
+  {
+    "input": "xn--hcb32bni",
+    "output": "xn--hcb32bni"
+  },
+  {
+    "input": "\u06bd\u0663\u0596",
+    "output": "xn--hcb32bni"
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0f94\ua84b-\uff0e-\ud81a\udf34",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "\u0f94\ua84b-.-\ud81a\udf34",
+    "output": null
+  },
+  {
+    "comment": "V5; V3 (ignored)",
+    "input": "xn----ukg9938i.xn----4u5m",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\ud9bd\udcb3-\u22e2\u200c\uff0e\u6807-",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\ud9bd\udcb3-\u2291\u0338\u200c\uff0e\u6807-",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\ud9bd\udcb3-\u22e2\u200c.\u6807-",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V6; V3 (ignored)",
+    "input": "\ud9bd\udcb3-\u2291\u0338\u200c.\u6807-",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----9mo67451g.xn----qj7b",
+    "output": null
+  },
+  {
+    "comment": "C1; V6; V3 (ignored)",
+    "input": "xn----sgn90kn5663a.xn----qj7b",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6; V3 (ignored)",
+    "input": "-\ud914\ude74.\u06e0\u189a-",
+    "output": null
+  },
+  {
+    "comment": "V5; V6; V3 (ignored)",
+    "input": "xn----qi38c.xn----jxc827k",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; A4_2 (ignored)",
+    "input": "\u3002\u0635\u0649\u0e37\u0644\u0627\u3002\u5c93\u1bf2\udb43\udf83\u1842",
+    "output": null
+  },
+  {
+    "comment": "V6; A4_2 (ignored)",
+    "input": ".xn--mgb1a7bt462h.xn--17e10qe61f9r71s",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u188c\uff0e-\u085a",
+    "output": "xn--59e.xn----5jd"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "\u188c.-\u085a",
+    "output": "xn--59e.xn----5jd"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "xn--59e.xn----5jd",
+    "output": "xn--59e.xn----5jd"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0e\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2.\u00df",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2.SS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2.ss",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2.Ss",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----9tg11172akr8b.ss",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn----9tg11172akr8b.xn--zca",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0eSS",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0ess",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u1039-\ud82a\udfad\ud83d\udfa2\uff0eSs",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\u9523\u3002\u0a4d\udb41\ude3b\udb41\ude86",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--gc5a.xn--ybc83044ppga",
+    "output": null
+  },
+  {
+    "input": "xn--8gb2338k.xn--lhb0154f",
+    "output": "xn--8gb2338k.xn--lhb0154f"
+  },
+  {
+    "input": "\u063d\ud804\ude3e.\u0649\ua92b",
+    "output": "xn--8gb2338k.xn--lhb0154f"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10c1\u10b16\u0318\u3002\u00df\u1b03",
+    "output": null
+  },
+  {
+    "input": "\u2d21\u2d116\u0318\u3002\u00df\u1b03",
+    "output": "xn--6-8cb7433a2ba.xn--zca894k"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10c1\u10b16\u0318\u3002SS\u1b03",
+    "output": null
+  },
+  {
+    "input": "\u2d21\u2d116\u0318\u3002ss\u1b03",
+    "output": "xn--6-8cb7433a2ba.xn--ss-2vq"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10c1\u2d116\u0318\u3002Ss\u1b03",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6-8cb306hms1a.xn--ss-2vq",
+    "output": null
+  },
+  {
+    "input": "xn--6-8cb7433a2ba.xn--ss-2vq",
+    "output": "xn--6-8cb7433a2ba.xn--ss-2vq"
+  },
+  {
+    "input": "\u2d21\u2d116\u0318.ss\u1b03",
+    "output": "xn--6-8cb7433a2ba.xn--ss-2vq"
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10c1\u10b16\u0318.SS\u1b03",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\u10c1\u2d116\u0318.Ss\u1b03",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6-8cb555h2b.xn--ss-2vq",
+    "output": null
+  },
+  {
+    "input": "xn--6-8cb7433a2ba.xn--zca894k",
+    "output": "xn--6-8cb7433a2ba.xn--zca894k"
+  },
+  {
+    "input": "\u2d21\u2d116\u0318.\u00df\u1b03",
+    "output": "xn--6-8cb7433a2ba.xn--zca894k"
+  },
+  {
+    "comment": "V6",
+    "input": "xn--6-8cb555h2b.xn--zca894k",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb40\udd0f\ud81a\udf34\udb43\udcbd\uff61\uffa0",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb40\udd0f\ud81a\udf34\udb43\udcbd\u3002\u1160",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--619ep9154c.xn--psd",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--619ep9154c.xn--cl7c",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb42\udf54.\ud800\udef1\u2082",
+    "output": null
+  },
+  {
+    "comment": "P1; V6",
+    "input": "\udb42\udf54.\ud800\udef12",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--vi56e.xn--2-w91i",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2dbf.\u00df\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2dbf.SS\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2dbf.ss\u200d",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u2dbf.Ss\u200d",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--7pj.ss",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--7pj.xn--ss-n1t",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--7pj.xn--zca870n",
+    "output": null
+  },
+  {
+    "comment": "C1",
+    "input": "\u6889\u3002\u200c",
+    "output": null
+  },
+  {
+    "input": "xn--7zv.",
+    "output": "xn--7zv."
+  },
+  {
+    "input": "\u6889.",
+    "output": "xn--7zv."
+  },
+  {
+    "comment": "C1",
+    "input": "xn--7zv.xn--0ug",
+    "output": null
+  },
+  {
+    "input": "xn--iwb.ss",
+    "output": "xn--iwb.ss"
+  },
+  {
+    "input": "\u0853.ss",
+    "output": "xn--iwb.ss"
+  },
+  {
+    "input": "\u0853.SS",
+    "output": "xn--iwb.ss"
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u40da\u87e5-\u3002-\ud9b5\udc98\u2488",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\u40da\u87e5-\u3002-\ud9b5\udc981.",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----n50a258u.xn---1-up07j.",
+    "output": null
+  },
+  {
+    "comment": "V6; V3 (ignored)",
+    "input": "xn----n50a258u.xn----ecp33805f",
+    "output": null
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\uff61\u2e90",
+    "output": "-.xn--6vj"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-\u3002\u2e90",
+    "output": "-.xn--6vj"
+  },
+  {
+    "comment": "V3 (ignored)",
+    "input": "-.xn--6vj",
+    "output": "-.xn--6vj"
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb43\udc29\ud807\udcac\uff0e\u065c",
+    "output": null
+  },
+  {
+    "comment": "P1; V5; V6",
+    "input": "\udb43\udc29\ud807\udcac.\u065c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--sn3d59267c.xn--4hb",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud800\udf7a.\ud928\uddc3\u200c",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--ie8c.xn--2g51a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--ie8c.xn--0ug03366c",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u200d\u8954\u3002\u10bc5\ua86e\ud995\udf4f",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V6",
+    "input": "\u200d\u200d\u8954\u3002\u2d1c5\ua86e\ud995\udf4f",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--2u2a.xn--5-uws5848bpf44e",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1uga7691f.xn--5-uws5848bpf44e",
+    "output": null
+  },
+  {
+    "comment": "V6",
+    "input": "xn--2u2a.xn--5-r1g7167ipfw8d",
+    "output": null
+  },
+  {
+    "comment": "C2; V6",
+    "input": "xn--1uga7691f.xn--5-r1g7167ipfw8d",
+    "output": null
+  },
+  {
+    "input": "xn--ix9c26l.xn--q0s",
+    "output": "xn--ix9c26l.xn--q0s"
+  },
+  {
+    "input": "\ud802\udedc\ud804\udf3c.\u5a40",
+    "output": "xn--ix9c26l.xn--q0s"
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6\u00df\uff0e\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8\u00df.\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8\u00df.\udb40\udd10-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8SS.\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.\udb40\udd10-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.xn---?-gfk",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.xn---?-261a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "xn--8-qfa.xn---?-261a",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "xn--8-qfa.xn---?-gfk",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6\u00df\uff0e\udb40\udd10-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6SS\uff0e\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6ss\uff0e\udb40\udd10-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6ss\uff0e\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8ss.-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8SS.-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "xn--8-qfa.-?\u2d0f",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "XN--8-QFA.-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "Xn--8-Qfa.-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "xn--8-qfa.-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "\ud835\udfd6Ss\uff0e\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "P1; V6; V3 (ignored)",
+    "input": "8Ss.\udb40\udd10-?\u10af",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ua9b9\u200d\ud077\ud8af\udda1\uff61\u2082",
+    "output": null
+  },
+  {
+    "comment": "C2; P1; V5; V6",
+    "input": "\ua9b9\u200d\u110f\u1173\u11b2\ud8af\udda1\uff61\u2082",
+    "output": null
+  },
+  {
+    "input": "\ud802\udec0\uff0e\u0689\ud804\udf00",
+    "output": "xn--pw9c.xn--fjb8658k"
+  },
+  {
+    "input": "\ud802\udec0.\u0689\ud804\udf00",
+    "output": "xn--pw9c.xn--fjb8658k"
+  },
+  {
+    "input": "xn--pw9c.xn--fjb8658k",
+    "output": "xn--pw9c.xn--fjb8658k"
+  },
+  {
+    "comment": "C2",
+    "input": "\ud800\udef7\u3002\u200d",
+    "output": null
+  },
+  {
+    "input": "xn--r97c.",
+    "output": "xn--r97c."
+  },
+  {
+    "input": "\ud800\udef7.",
+    "output": "xn--r97c."
+  },
+  {
+    "comment": "C2",
+    "input": "xn--r97c.xn--1ug",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "\ud807\udc33\ud804\ude2f\u3002\u296a",
+    "output": null
+  },
+  {
+    "comment": "V5",
+    "input": "xn--2g1d14o.xn--jti",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud804\udd80\u4074\ud952\udde3\uff0e\u10b5\ud835\udfdc\u200c\u0348",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud804\udd80\u4074\ud952\udde3.\u10b54\u200c\u0348",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud804\udd80\u4074\ud952\udde3.\u2d154\u200c\u0348",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--1mnx647cg3x1b.xn--4-zfb5123a",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--1mnx647cg3x1b.xn--4-zfb502tlsl",
+    "output": null
+  },
+  {
+    "comment": "V5; V6",
+    "input": "xn--1mnx647cg3x1b.xn--4-zfb324h",
+    "output": null
+  },
+  {
+    "comment": "C1; V5; V6",
+    "input": "xn--1mnx647cg3x1b.xn--4-zfb324h32o",
+    "output": null
+  },
+  {
+    "comment": "C1; P1; V5; V6",
+    "input": "\ud804\udd80\u4074\ud952\udde3\uff0e\u2d15\ud835\udfdc\u200c\u0348",
+    "output": null
+  }
+]
diff --git a/tests/wpt/ada_extra_setters_tests.json b/tests/wpt/ada_extra_setters_tests.json
new file mode 100644 (file)
index 0000000..9ba7523
--- /dev/null
@@ -0,0 +1,134 @@
+{
+  "comment": [
+    "#Additional tests designed by the ada team."
+  ],
+  "href": [
+    {
+      "comment": "Update with pathname, fragment and search state filled",
+      "href": "https://yagiz.co",
+      "new_value": "https://google.com/url?search=true#fragment",
+      "expected": {
+        "href": "https://google.com/url?search=true#fragment"
+      }
+    },
+    {
+      "comment": "Update with authority state filled",
+      "href": "https://yagiz.co",
+      "new_value": "https://username:password@localhost:5432/my-db",
+      "expected": {
+        "href": "https://username:password@localhost:5432/my-db"
+      }
+    },
+    {
+      "comment": "Removes leading and trailing control characters",
+      "href": "https://yagiz.co",
+      "new_value": "\u0000http://www.google.com\u0000",
+      "expected": {
+        "href": "http://www.google.com/"
+      }
+    }
+  ],
+  "port": [
+    {
+      "comment": "Should not update on leading control characters",
+      "href": "https://yagiz.co:443",
+      "new_value": "\u000080\u0000",
+      "expected": {
+        "port": ""
+      }
+    },
+    {
+      "comment": "Leading u0009 on special scheme",
+      "href": "https://yagiz.co:443",
+      "new_value": "\u00098080",
+      "expected": {
+        "port": "8080"
+      }
+    },
+    {
+      "comment": "Leading u0009 on non-special scheme",
+      "href": "wpt++://yagiz.co:443",
+      "new_value": "\u00098080",
+      "expected": {
+        "port": "8080"
+      }
+    },
+    {
+      "comment": "Trailing control characters",
+      "href": "https://yagiz.co:443",
+      "new_value": "8080\u0000",
+      "expected": {
+        "port": "8080"
+      }
+    },
+    {
+      "href": "https://yagiz.co",
+      "new_value": "\u000044\u00093\u0000",
+      "expected": {
+        "port": ""
+      }
+    },
+    {
+      "href": "https://yagiz.co:443",
+      "new_value": "\u0000\u0009\u0000",
+      "expected": {
+        "port": ""
+      }
+    },
+    {
+      "comment": "Should remove port on invalid input",
+      "href": "https://www.google.com:443",
+      "new_value": "99999",
+      "expected": {
+        "port": ""
+      }
+    },
+    {
+      "comment": "Should revert back to original on invalid input",
+      "href": "https://www.google.com:4343",
+      "new_value": "yagiz",
+      "expected": {
+        "port": "4343"
+      }
+    },
+    {
+      "comment": "Should use all ascii prefixed characters as port",
+      "href": "https://www.google.com:4343",
+      "new_value": "4yagiz",
+      "expected": {
+        "port": "4"
+      }
+    }
+  ],
+  "hash": [
+    {
+      "comment": "Should not trim leading and trailing control characters",
+      "href": "https://domain.com",
+      "new_value": "\u0000hello\u0000",
+      "expected": {
+        "hash": "#%00hello%00"
+      }
+    }
+  ],
+  "pathname": [
+    {
+      "comment": "Used by Node.js by node::url::FromFilePath",
+      "href": "file://",
+      "new_value": "/Users/yagiz/Developer/node/test/fixtures/loop.%25.js",
+      "expected": {
+        "pathname": "/Users/yagiz/Developer/node/test/fixtures/loop.%25.js"
+      }
+    }
+  ],
+  "search": [
+    {
+      "comment": "Remove non-existent param removes ? from URL",
+      "href": "data:space    ?test",
+      "new_value": "",
+      "expected": {
+        "search": "",
+        "pathname": "space"
+      }
+    }
+  ]
+}
diff --git a/tests/wpt/ada_extra_urltestdata.json b/tests/wpt/ada_extra_urltestdata.json
new file mode 100644 (file)
index 0000000..d6c162b
--- /dev/null
@@ -0,0 +1,289 @@
+[
+  "# Additional tests designed by the ada team.",
+  {
+    "input": "https://www.google.com",
+    "base": "http://[0:0:0:0:0:0:0:0",
+    "failure": true
+  },
+  {
+    "input": "#x",
+    "base": "about:blank",
+    "href": "about:blank#x",
+    "protocol":"about:",
+    "username": "",
+    "password": "",
+    "pathname":"blank",
+    "search": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "hash":"#x"
+  },
+  {
+    "input": "https://lemire.me/école",
+    "base": "about:blank",
+    "href": "https://lemire.me/%C3%A9cole",
+    "origin": "https://lemire.me",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "lemire.me",
+    "hostname": "lemire.me",
+    "port": "",
+    "pathname": "/%C3%A9cole",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://./",
+    "base": "about:blank",
+    "href": "http://./",
+    "origin": "http://.",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": ".",
+    "hostname": ".",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "example.com`x.example.com",
+    "failure": true
+  },
+  {
+    "input": "http://Yağız.com",
+    "base": "about:blank",
+    "href": "http://xn--yaz-isa4g.com/",
+    "origin": "http://xn--yaz-isa4g.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "xn--yaz-isa4g.com",
+    "hostname": "xn--yaz-isa4g.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "fs:/hello.eth",
+    "base": "about:blank",
+    "href": "fs:/hello.eth",
+    "origin": "null",
+    "protocol": "fs:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/hello.eth",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://\/\/\/\\'",
+    "base": "about:blank",
+    "href": "http://'/",
+    "origin": "http://'",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "'",
+    "hostname": "'",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "./foo",
+    "base": "http://www.example.org",
+    "href": "http://www.example.org/foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.org",
+    "hostname": "www.example.org",
+    "port": "",
+    "pathname": "/foo",
+    "search": "",
+    "hash": ""
+  },
+  "# Ipv6 Edge Cases",
+  {
+    "input": "http://[0:0:0:0:0:0:0:0",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "http://0:0:0:0:0:0:0:0]",
+    "base": "about:blank",
+    "failure": true
+  },
+  "# Opaque Paths",
+  {
+    "input": "mailto:a@b.com",
+    "base": "about:blank",
+    "href": "mailto:a@b.com",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "a@b.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "scheme:example.com",
+    "base": "about:blank",
+    "href": "scheme:example.com",
+    "origin": "null",
+    "protocol": "scheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "scheme:example.com/path",
+    "base": "about:blank",
+    "href": "scheme:example.com/path",
+    "origin": "null",
+    "protocol": "scheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/path",
+    "search": "",
+    "hash": ""
+  },
+  "# Port",
+  {
+    "input": "scheme://example.com:-1",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "scheme://example.com:+1",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "schéme://example.com",
+    "base": "about:blank",
+    "failure": true
+  },
+  "# Credentials",
+  {
+    "input": "scheme://username@@@@example.com",
+    "base": "about:blank",
+    "href": "scheme://username%40%40%40@example.com",
+    "origin": "null",
+    "protocol": "scheme:",
+    "username": "username%40%40%40",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost/path/to/file.txt",
+    "base": "about:blank",
+    "href": "file:///path/to/file.txt",
+    "origin": "null",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/path/to/file.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost:8098/path/to/file.txt",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "data:space    ?test#test",
+    "base": "about:blank",
+    "href": "data:space    ?test#test",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "space    ",
+    "search": "?test",
+    "hash": "#test"
+  },
+  {
+    "input": "",
+    "base": "about:blank",
+    "failure": true
+  },
+  {
+    "input": "https://example.com/\"quoted\"",
+    "base": "about:blank",
+    "href": "https://example.com/%22quoted%22",
+    "origin": "https://example.com",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%22quoted%22",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "a:b#",
+    "base": null,
+    "href": "a:b#",
+    "origin": null,
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..#",
+    "base": "a:b",
+    "href": "a:b#",
+    "origin": null,
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "b",
+    "search": "",
+    "hash": ""
+  }
+]
diff --git a/tests/wpt/ada_long_urltestdata.json b/tests/wpt/ada_long_urltestdata.json
new file mode 100644 (file)
index 0000000..6f93dfa
--- /dev/null
@@ -0,0 +1,19 @@
+[
+  "# Windows' codebase will not allow this case for now.",
+  "# Host",
+  {
+    "input": "http://ğığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığığı.com",
+    "base": "about:blank",
+    "href": "http://xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com/",
+    "origin": "http://xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com",
+    "hostname": "xn--teaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa78hbabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  }
+]
diff --git a/tests/wpt/percent-encoding.json b/tests/wpt/percent-encoding.json
new file mode 100644 (file)
index 0000000..eccd1db
--- /dev/null
@@ -0,0 +1,48 @@
+[
+  "Tests for percent-encoding.",
+  {
+    "input": "\u2020",
+    "output": {
+      "big5": "%26%238224%3B",
+      "euc-kr": "%A2%D3",
+      "utf-8": "%E2%80%A0",
+      "windows-1252": "%86"
+    }
+  },
+  "This uses a trailing A to prevent the URL parser from trimming the C0 control.",
+  {
+    "input": "\u000EA",
+    "output": {
+      "big5": "%0EA",
+      "iso-2022-jp": "%26%2365533%3BA",
+      "utf-8": "%0EA"
+    }
+  },
+  {
+    "input": "\u203E\u005C",
+    "output": {
+      "iso-2022-jp": "%1B(J~%1B(B\\",
+      "utf-8": "%E2%80%BE\\"
+    }
+  },
+  {
+    "input": "\uE5E5",
+    "output": {
+      "gb18030": "%26%2358853%3B",
+      "utf-8": "%EE%97%A5"
+    }
+  },
+  {
+    "input": "\u2212",
+    "output": {
+      "shift_jis": "%81|",
+      "utf-8": "%E2%88%92"
+    }
+  },
+  {
+    "input": "á|",
+    "output": {
+      "utf-8": "%C3%A1|"
+    }
+  }
+]
diff --git a/tests/wpt/setters_tests.json b/tests/wpt/setters_tests.json
new file mode 100644 (file)
index 0000000..82adf4c
--- /dev/null
@@ -0,0 +1,2380 @@
+{
+    "comment": [
+        "## Tests for setters of https://url.spec.whatwg.org/#urlutils-members",
+        "",
+        "This file contains a JSON object.",
+        "Other than 'comment', each key is an attribute of the `URL` interface",
+        "defined in WHATWG’s URL Standard.",
+        "The values are arrays of test case objects for that attribute.",
+        "",
+        "To run a test case for the attribute `attr`:",
+        "",
+        "* Create a new `URL` object with the value for the 'href' key",
+        "  the constructor single parameter. (Without a base URL.)",
+        "  This must not throw.",
+        "* Set the attribute `attr` to (invoke its setter with)",
+        "  with the value of for 'new_value' key.",
+        "* The value for the 'expected' key is another object.",
+        "  For each `key` / `value` pair of that object,",
+        "  get the attribute `key` (invoke its getter).",
+        "  The returned string must be equal to `value`.",
+        "",
+        "Note: the 'href' setter is already covered by urltestdata.json."
+    ],
+    "protocol": [
+        {
+            "comment": "The empty string is not a valid scheme. Setter leaves the URL unchanged.",
+            "href": "a://example.net",
+            "new_value": "",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "href": "a://example.net",
+            "new_value": "b",
+            "expected": {
+                "href": "b://example.net",
+                "protocol": "b:"
+            }
+        },
+        {
+            "href": "javascript:alert(1)",
+            "new_value": "defuse",
+            "expected": {
+                "href": "defuse:alert(1)",
+                "protocol": "defuse:"
+            }
+        },
+        {
+            "comment": "Upper-case ASCII is lower-cased",
+            "href": "a://example.net",
+            "new_value": "B",
+            "expected": {
+                "href": "b://example.net",
+                "protocol": "b:"
+            }
+        },
+        {
+            "comment": "Non-ASCII is rejected",
+            "href": "a://example.net",
+            "new_value": "é",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "comment": "No leading digit",
+            "href": "a://example.net",
+            "new_value": "0b",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "comment": "No leading punctuation",
+            "href": "a://example.net",
+            "new_value": "+b",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "href": "a://example.net",
+            "new_value": "bC0+-.",
+            "expected": {
+                "href": "bc0+-.://example.net",
+                "protocol": "bc0+-.:"
+            }
+        },
+        {
+            "comment": "Only some punctuation is acceptable",
+            "href": "a://example.net",
+            "new_value": "b,c",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "comment": "Non-ASCII is rejected",
+            "href": "a://example.net",
+            "new_value": "bé",
+            "expected": {
+                "href": "a://example.net",
+                "protocol": "a:"
+            }
+        },
+        {
+            "comment": "Can’t switch from URL containing username/password/port to file",
+            "href": "http://test@example.net",
+            "new_value": "file",
+            "expected": {
+                "href": "http://test@example.net/",
+                "protocol": "http:"
+            }
+        },
+        {
+            "href": "https://example.net:1234",
+            "new_value": "file",
+            "expected": {
+                "href": "https://example.net:1234/",
+                "protocol": "https:"
+            }
+        },
+        {
+            "href": "wss://x:x@example.net:1234",
+            "new_value": "file",
+            "expected": {
+                "href": "wss://x:x@example.net:1234/",
+                "protocol": "wss:"
+            }
+        },
+        {
+            "comment": "Can’t switch from file URL with no host",
+            "href": "file://localhost/",
+            "new_value": "http",
+            "expected": {
+                "href": "file:///",
+                "protocol": "file:"
+            }
+        },
+        {
+            "href": "file:///test",
+            "new_value": "https",
+            "expected": {
+                "href": "file:///test",
+                "protocol": "file:"
+            }
+        },
+        {
+            "href": "file:",
+            "new_value": "wss",
+            "expected": {
+                "href": "file:///",
+                "protocol": "file:"
+            }
+        },
+        {
+            "comment": "Can’t switch from special scheme to non-special",
+            "href": "http://example.net",
+            "new_value": "b",
+            "expected": {
+                "href": "http://example.net/",
+                "protocol": "http:"
+            }
+        },
+        {
+            "href": "file://hi/path",
+            "new_value": "s",
+            "expected": {
+                "href": "file://hi/path",
+                "protocol": "file:"
+            }
+        },
+        {
+            "href": "https://example.net",
+            "new_value": "s",
+            "expected": {
+                "href": "https://example.net/",
+                "protocol": "https:"
+            }
+        },
+        {
+            "href": "ftp://example.net",
+            "new_value": "test",
+            "expected": {
+                "href": "ftp://example.net/",
+                "protocol": "ftp:"
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base URL doesn’t have a host, but URL in a special scheme must.",
+            "href": "mailto:me@example.net",
+            "new_value": "http",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "protocol": "mailto:"
+            }
+        },
+        {
+            "comment": "Can’t switch from non-special scheme to special",
+            "href": "ssh://me@example.net",
+            "new_value": "http",
+            "expected": {
+                "href": "ssh://me@example.net",
+                "protocol": "ssh:"
+            }
+        },
+        {
+            "href": "ssh://me@example.net",
+            "new_value": "https",
+            "expected": {
+                "href": "ssh://me@example.net",
+                "protocol": "ssh:"
+            }
+        },
+        {
+            "href": "ssh://me@example.net",
+            "new_value": "file",
+            "expected": {
+                "href": "ssh://me@example.net",
+                "protocol": "ssh:"
+            }
+        },
+        {
+            "href": "ssh://example.net",
+            "new_value": "file",
+            "expected": {
+                "href": "ssh://example.net",
+                "protocol": "ssh:"
+            }
+        },
+        {
+            "href": "nonsense:///test",
+            "new_value": "https",
+            "expected": {
+                "href": "nonsense:///test",
+                "protocol": "nonsense:"
+            }
+        },
+        {
+            "comment": "Stuff after the first ':' is ignored",
+            "href": "http://example.net",
+            "new_value": "https:foo : bar",
+            "expected": {
+                "href": "https://example.net/",
+                "protocol": "https:"
+            }
+        },
+        {
+            "comment": "Stuff after the first ':' is ignored",
+            "href": "data:text/html,<p>Test",
+            "new_value": "view-source+data:foo : bar",
+            "expected": {
+                "href": "view-source+data:text/html,<p>Test",
+                "protocol": "view-source+data:"
+            }
+        },
+        {
+            "comment": "Port is set to null if it is the default for new scheme.",
+            "href": "http://foo.com:443/",
+            "new_value": "https",
+            "expected": {
+                "href": "https://foo.com/",
+                "protocol": "https:",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Tab and newline are stripped",
+            "href": "http://test/",
+            "new_value": "h\u000D\u000Att\u0009ps",
+            "expected": {
+              "href": "https://test/",
+              "protocol": "https:",
+              "port": ""
+            }
+        },
+        {
+            "href": "http://test/",
+            "new_value": "https\u000D",
+            "expected": {
+              "href": "https://test/",
+              "protocol": "https:"
+            }
+        },
+        {
+            "comment": "Non-tab/newline C0 controls result in no-op",
+            "href": "http://test/",
+            "new_value": "https\u0000",
+            "expected": {
+              "href": "http://test/",
+              "protocol": "http:"
+            }
+        },
+        {
+            "href": "http://test/",
+            "new_value": "https\u000C",
+            "expected": {
+              "href": "http://test/",
+              "protocol": "http:"
+            }
+        },
+        {
+            "href": "http://test/",
+            "new_value": "https\u000E",
+            "expected": {
+              "href": "http://test/",
+              "protocol": "http:"
+            }
+        },
+        {
+            "href": "http://test/",
+            "new_value": "https\u0020",
+            "expected": {
+              "href": "http://test/",
+              "protocol": "http:"
+            }
+        }
+    ],
+    "username": [
+        {
+            "comment": "No host means no username",
+            "href": "file:///home/you/index.html",
+            "new_value": "me",
+            "expected": {
+                "href": "file:///home/you/index.html",
+                "username": ""
+            }
+        },
+        {
+            "comment": "No host means no username",
+            "href": "unix:/run/foo.socket",
+            "new_value": "me",
+            "expected": {
+                "href": "unix:/run/foo.socket",
+                "username": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no username",
+            "href": "mailto:you@example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "mailto:you@example.net",
+                "username": ""
+            }
+        },
+        {
+            "href": "javascript:alert(1)",
+            "new_value": "wario",
+            "expected": {
+                "href": "javascript:alert(1)",
+                "username": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "http://me@example.net/",
+                "username": "me"
+            }
+        },
+        {
+            "href": "http://:secret@example.net",
+            "new_value": "me",
+            "expected": {
+                "href": "http://me:secret@example.net/",
+                "username": "me"
+            }
+        },
+        {
+            "href": "http://me@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "username": ""
+            }
+        },
+        {
+            "href": "http://me:secret@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://:secret@example.net/",
+                "username": ""
+            }
+        },
+        {
+            "comment": "UTF-8 percent encoding with the userinfo encode set.",
+            "href": "http://example.net",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "http://%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/",
+                "username": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is.",
+            "href": "http://example.net",
+            "new_value": "%c3%89té",
+            "expected": {
+                "href": "http://%c3%89t%C3%A9@example.net/",
+                "username": "%c3%89t%C3%A9"
+            }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "x",
+            "expected": {
+                "href": "sc:///",
+                "username": ""
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "wario",
+            "expected": {
+                "href": "javascript://wario@x/",
+                "username": "wario"
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "test",
+            "expected": {
+                "href": "file://test/",
+                "username": ""
+            }
+        }
+    ],
+    "password": [
+        {
+            "comment": "No host means no password",
+            "href": "file:///home/me/index.html",
+            "new_value": "secret",
+            "expected": {
+                "href": "file:///home/me/index.html",
+                "password": ""
+            }
+        },
+        {
+            "comment": "No host means no password",
+            "href": "unix:/run/foo.socket",
+            "new_value": "secret",
+            "expected": {
+                "href": "unix:/run/foo.socket",
+                "password": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no password",
+            "href": "mailto:me@example.net",
+            "new_value": "secret",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "password": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "secret",
+            "expected": {
+                "href": "http://:secret@example.net/",
+                "password": "secret"
+            }
+        },
+        {
+            "href": "http://me@example.net",
+            "new_value": "secret",
+            "expected": {
+                "href": "http://me:secret@example.net/",
+                "password": "secret"
+            }
+        },
+        {
+            "href": "http://:secret@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "password": ""
+            }
+        },
+        {
+            "href": "http://me:secret@example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://me@example.net/",
+                "password": ""
+            }
+        },
+        {
+            "comment": "UTF-8 percent encoding with the userinfo encode set.",
+            "href": "http://example.net",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "http://:%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9@example.net/",
+                "password": "%00%01%09%0A%0D%1F%20!%22%23$%&'()*+,-.%2F09%3A%3B%3C%3D%3E%3F%40AZ%5B%5C%5D%5E_%60az%7B%7C%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is.",
+            "href": "http://example.net",
+            "new_value": "%c3%89té",
+            "expected": {
+                "href": "http://:%c3%89t%C3%A9@example.net/",
+                "password": "%c3%89t%C3%A9"
+            }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "x",
+            "expected": {
+                "href": "sc:///",
+                "password": ""
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "bowser",
+            "expected": {
+                "href": "javascript://:bowser@x/",
+                "password": "bowser"
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "test",
+            "expected": {
+                "href": "file://test/",
+                "password": ""
+            }
+        }
+    ],
+    "host": [
+        {
+            "comment": "Non-special scheme",
+            "href": "sc://x/",
+            "new_value": "\u0000",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u0009",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000A",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000D",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": " ",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "#",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "/",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "?",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "@",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "ß",
+            "expected": {
+                "href": "sc://%C3%9F/",
+                "host": "%C3%9F",
+                "hostname": "%C3%9F"
+            }
+        },
+        {
+            "comment": "IDNA Nontransitional_Processing",
+            "href": "https://x/",
+            "new_value": "ß",
+            "expected": {
+                "href": "https://xn--zca/",
+                "host": "xn--zca",
+                "hostname": "xn--zca"
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no host",
+            "href": "mailto:me@example.net",
+            "new_value": "example.com",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no host",
+            "href": "data:text/plain,Stuff",
+            "new_value": "example.net",
+            "expected": {
+                "href": "data:text/plain,Stuff",
+                "host": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "example.com:8080",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port number is unchanged if not specified in the new value",
+            "href": "http://example.net:8080",
+            "new_value": "example.com",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port number is unchanged if not specified",
+            "href": "http://example.net:8080",
+            "new_value": "example.com:",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "The empty host is not valid for special schemes",
+            "href": "http://example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "The empty host is OK for non-special schemes",
+            "href": "view-source+http://example.net/foo",
+            "new_value": "",
+            "expected": {
+                "href": "view-source+http:///foo",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Path-only URLs can gain a host",
+            "href": "a:/foo",
+            "new_value": "example.net",
+            "expected": {
+                "href": "a://example.net/foo",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "IPv4 address syntax is normalized",
+            "href": "http://example.net",
+            "new_value": "0x7F000001:8080",
+            "expected": {
+                "href": "http://127.0.0.1:8080/",
+                "host": "127.0.0.1:8080",
+                "hostname": "127.0.0.1",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "IPv6 address syntax is normalized",
+            "href": "http://example.net",
+            "new_value": "[::0:01]:2",
+            "expected": {
+                "href": "http://[::1]:2/",
+                "host": "[::1]:2",
+                "hostname": "[::1]",
+                "port": "2"
+            }
+        },
+        {
+            "comment": "IPv6 literal address with port, crbug.com/1012416",
+            "href": "http://example.net",
+            "new_value": "[2001:db8::2]:4002",
+            "expected": {
+                "href": "http://[2001:db8::2]:4002/",
+                "host": "[2001:db8::2]:4002",
+                "hostname": "[2001:db8::2]",
+                "port": "4002"
+             }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "http://example.net",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "https://example.net",
+            "new_value": "example.com:443",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is only removed for the relevant scheme",
+            "href": "https://example.net",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "https://example.com:80/",
+                "host": "example.com:80",
+                "hostname": "example.com",
+                "port": "80"
+            }
+        },
+        {
+            "comment": "Port number is removed if new port is scheme default and existing URL has a non-default port",
+            "href": "http://example.net:8080",
+            "new_value": "example.com:80",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com/stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080/stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com?stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080?stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com#stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080#stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored for special schemes",
+            "href": "http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored for special schemes",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080\\stuff",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts",
+            "href": "view-source+http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "view-source+http://example.net/path",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "view-source+http://example.net/path",
+            "new_value": "example.com:8080stuff2",
+            "expected": {
+                "href": "view-source+http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080stuff2",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080+2",
+            "expected": {
+                "href": "http://example.com:8080/path",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers",
+            "href": "http://example.net/path",
+            "new_value": "example.com:65535",
+            "expected": {
+                "href": "http://example.com:65535/path",
+                "host": "example.com:65535",
+                "hostname": "example.com",
+                "port": "65535"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers, overflowing is an error. Hostname is still set, though.",
+            "href": "http://example.net/path",
+            "new_value": "example.com:65536",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Broken IPv6",
+            "href": "http://example.net/",
+            "new_value": "[google.com]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.4x]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "file://y/",
+            "new_value": "x:123",
+            "expected": {
+                "href": "file://y/",
+                "host": "y",
+                "hostname": "y",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://y/",
+            "new_value": "loc%41lhost",
+            "expected": {
+                "href": "file:///",
+                "host": "",
+                "hostname": "",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://hi/x",
+            "new_value": "",
+            "expected": {
+                "href": "file:///x",
+                "host": "",
+                "hostname": "",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc://test@test/",
+            "new_value": "",
+            "expected": {
+                "href": "sc://test@test/",
+                "host": "test",
+                "hostname": "test",
+                "username": "test"
+            }
+        },
+        {
+            "href": "sc://test:12/",
+            "new_value": "",
+            "expected": {
+                "href": "sc://test:12/",
+                "host": "test:12",
+                "hostname": "test",
+                "port": "12"
+            }
+        },
+        {
+            "comment": "Leading / is not stripped",
+            "href": "http://example.com/",
+            "new_value": "///bad.com",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "comment": "Leading / is not stripped",
+            "href": "sc://example.com/",
+            "new_value": "///bad.com",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "a%C2%ADb",
+            "expected": {
+                "href": "https://ab/",
+                "host": "ab",
+                "hostname": "ab"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "\u00AD",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "%C2%AD",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "xn--",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        }
+    ],
+    "hostname": [
+        {
+            "comment": "Non-special scheme",
+            "href": "sc://x/",
+            "new_value": "\u0000",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u0009",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000A",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "\u000D",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": " ",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "#",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "/",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "?",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "@",
+            "expected": {
+                "href": "sc://x/",
+                "host": "x",
+                "hostname": "x"
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no host",
+            "href": "mailto:me@example.net",
+            "new_value": "example.com",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Cannot-be-a-base means no host",
+            "href": "data:text/plain,Stuff",
+            "new_value": "example.net",
+            "expected": {
+                "href": "data:text/plain,Stuff",
+                "host": ""
+            }
+        },
+        {
+            "href": "http://example.net:8080",
+            "new_value": "example.com",
+            "expected": {
+                "href": "http://example.com:8080/",
+                "host": "example.com:8080",
+                "hostname": "example.com",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "The empty host is not valid for special schemes",
+            "href": "http://example.net",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "The empty host is OK for non-special schemes",
+            "href": "view-source+http://example.net/foo",
+            "new_value": "",
+            "expected": {
+                "href": "view-source+http:///foo",
+                "host": ""
+            }
+        },
+        {
+            "comment": "Path-only URLs can gain a host",
+            "href": "a:/foo",
+            "new_value": "example.net",
+            "expected": {
+                "href": "a://example.net/foo",
+                "host": "example.net"
+            }
+        },
+        {
+            "comment": "IPv4 address syntax is normalized",
+            "href": "http://example.net:8080",
+            "new_value": "0x7F000001",
+            "expected": {
+                "href": "http://127.0.0.1:8080/",
+                "host": "127.0.0.1:8080",
+                "hostname": "127.0.0.1",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "IPv6 address syntax is normalized",
+            "href": "http://example.net",
+            "new_value": "[::0:01]",
+            "expected": {
+                "href": "http://[::1]/",
+                "host": "[::1]",
+                "hostname": "[::1]",
+                "port": ""
+            }
+        },
+        {
+            "comment": ": delimiter invalidates entire value",
+            "href": "http://example.net/path",
+            "new_value": "example.com:8080",
+            "expected": {
+                "href": "http://example.net/path",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": ": delimiter invalidates entire value",
+            "href": "http://example.net:8080/path",
+            "new_value": "example.com:",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com/stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com?stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "example.com#stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored for special schemes",
+            "href": "http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "http://example.com/path",
+                "host": "example.com",
+                "hostname": "example.com",
+                "port": ""
+            }
+        },
+        {
+            "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts",
+            "href": "view-source+http://example.net/path",
+            "new_value": "example.com\\stuff",
+            "expected": {
+                "href": "view-source+http://example.net/path",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Broken IPv6",
+            "href": "http://example.net/",
+            "new_value": "[google.com]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.4x]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.3.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.2.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "http://example.net/",
+            "new_value": "[::1.]",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net"
+            }
+        },
+        {
+            "href": "file://y/",
+            "new_value": "x:123",
+            "expected": {
+                "href": "file://y/",
+                "host": "y",
+                "hostname": "y",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://y/",
+            "new_value": "loc%41lhost",
+            "expected": {
+                "href": "file:///",
+                "host": "",
+                "hostname": "",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://hi/x",
+            "new_value": "",
+            "expected": {
+                "href": "file:///x",
+                "host": "",
+                "hostname": "",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc://test@test/",
+            "new_value": "",
+            "expected": {
+                "href": "sc://test@test/",
+                "host": "test",
+                "hostname": "test",
+                "username": "test"
+            }
+        },
+        {
+            "href": "sc://test:12/",
+            "new_value": "",
+            "expected": {
+                "href": "sc://test:12/",
+                "host": "test:12",
+                "hostname": "test",
+                "port": "12"
+            }
+        },
+        {
+            "comment": "Drop /. from path",
+            "href": "non-spec:/.//p",
+            "new_value": "h",
+            "expected": {
+                "href": "non-spec://h//p",
+                "host": "h",
+                "hostname": "h",
+                "pathname": "//p"
+            }
+        },
+        {
+            "href": "non-spec:/.//p",
+            "new_value": "",
+            "expected": {
+                "href": "non-spec:////p",
+                "host": "",
+                "hostname": "",
+                "pathname": "//p"
+            }
+        },
+        {
+            "comment": "Leading / is not stripped",
+            "href": "http://example.com/",
+            "new_value": "///bad.com",
+            "expected": {
+                "href": "http://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "comment": "Leading / is not stripped",
+            "href": "sc://example.com/",
+            "new_value": "///bad.com",
+            "expected": {
+                "href": "sc:///",
+                "host": "",
+                "hostname": ""
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "a%C2%ADb",
+            "expected": {
+                "href": "https://ab/",
+                "host": "ab",
+                "hostname": "ab"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "\u00AD",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "%C2%AD",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        },
+        {
+            "href": "https://example.com/",
+            "new_value": "xn--",
+            "expected": {
+                "href": "https://example.com/",
+                "host": "example.com",
+                "hostname": "example.com"
+            }
+        }
+    ],
+    "port": [
+        {
+            "href": "http://example.net",
+            "new_value": "8080",
+            "expected": {
+                "href": "http://example.net:8080/",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port number is removed if empty is the new value",
+            "href": "http://example.net:8080",
+            "new_value": "",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "http://example.net:8080",
+            "new_value": "80",
+            "expected": {
+                "href": "http://example.net/",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is removed",
+            "href": "https://example.net:4433",
+            "new_value": "443",
+            "expected": {
+                "href": "https://example.net/",
+                "host": "example.net",
+                "hostname": "example.net",
+                "port": ""
+            }
+        },
+        {
+            "comment": "Default port number is only removed for the relevant scheme",
+            "href": "https://example.net",
+            "new_value": "80",
+            "expected": {
+                "href": "https://example.net:80/",
+                "host": "example.net:80",
+                "hostname": "example.net",
+                "port": "80"
+            }
+        },
+        {
+            "comment": "Stuff after a / delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "8080/stuff",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a ? delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "8080?stuff",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a # delimiter is ignored",
+            "href": "http://example.net/path",
+            "new_value": "8080#stuff",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Stuff after a \\ delimiter is ignored for special schemes",
+            "href": "http://example.net/path",
+            "new_value": "8080\\stuff",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "view-source+http://example.net/path",
+            "new_value": "8080stuff2",
+            "expected": {
+                "href": "view-source+http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "8080stuff2",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Anything other than ASCII digit stops the port parser in a setter but is not an error",
+            "href": "http://example.net/path",
+            "new_value": "8080+2",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers",
+            "href": "http://example.net/path",
+            "new_value": "65535",
+            "expected": {
+                "href": "http://example.net:65535/path",
+                "host": "example.net:65535",
+                "hostname": "example.net",
+                "port": "65535"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers, overflowing is an error",
+            "href": "http://example.net:8080/path",
+            "new_value": "65536",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Setting port to a string that doesn't parse as a number",
+            "href": "http://example.net:8080/path",
+            "new_value": "randomstring",
+            "expected": {
+                "href": "http://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Port numbers are 16 bit integers, overflowing is an error",
+            "href": "non-special://example.net:8080/path",
+            "new_value": "65536",
+            "expected": {
+                "href": "non-special://example.net:8080/path",
+                "host": "example.net:8080",
+                "hostname": "example.net",
+                "port": "8080"
+            }
+        },
+        {
+            "href": "file://test/",
+            "new_value": "12",
+            "expected": {
+                "href": "file://test/",
+                "port": ""
+            }
+        },
+        {
+            "href": "file://localhost/",
+            "new_value": "12",
+            "expected": {
+                "href": "file:///",
+                "port": ""
+            }
+        },
+        {
+            "href": "non-base:value",
+            "new_value": "12",
+            "expected": {
+                "href": "non-base:value",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc:///",
+            "new_value": "12",
+            "expected": {
+                "href": "sc:///",
+                "port": ""
+            }
+        },
+        {
+            "href": "sc://x/",
+            "new_value": "12",
+            "expected": {
+                "href": "sc://x:12/",
+                "port": "12"
+            }
+        },
+        {
+            "href": "javascript://x/",
+            "new_value": "12",
+            "expected": {
+                "href": "javascript://x:12/",
+                "port": "12"
+            }
+        },
+        {
+            "comment": "Leading u0009 on special scheme",
+            "href": "https://domain.com:443",
+            "new_value": "\u00098080",
+            "expected": {
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Leading u0009 on non-special scheme",
+            "href": "wpt++://domain.com:443",
+            "new_value": "\u00098080",
+            "expected": {
+                "port": "8080"
+            }
+        },
+        {
+            "comment": "Should use all ascii prefixed characters as port",
+            "href": "https://www.google.com:4343",
+            "new_value": "4wpt",
+            "expected": {
+                "port": "4"
+            }
+        }
+    ],
+    "pathname": [
+        {
+            "comment": "Opaque paths cannot be set",
+            "href": "mailto:me@example.net",
+            "new_value": "/foo",
+            "expected": {
+                "href": "mailto:me@example.net",
+                "pathname": "me@example.net"
+            }
+        },
+        {
+            "href": "data:original",
+            "new_value": "new value",
+            "expected": {
+                "href": "data:original",
+                "pathname": "original"
+            }
+        },
+        {
+            "href": "sc:original",
+            "new_value": "new value",
+            "expected": {
+                "href": "sc:original",
+                "pathname": "original"
+            }
+        },
+        {
+            "comment": "Special URLs cannot have their paths erased",
+            "href": "file:///some/path",
+            "new_value": "",
+            "expected": {
+                "href": "file:///",
+                "pathname": "/"
+            }
+        },
+        {
+            "comment": "Non-special URLs can have their paths erased",
+            "href": "foo://somehost/some/path",
+            "new_value": "",
+            "expected": {
+                "href": "foo://somehost",
+                "pathname": ""
+            }
+        },
+        {
+            "comment": "Non-special URLs with an empty host can have their paths erased",
+            "href": "foo:///some/path",
+            "new_value": "",
+            "expected": {
+                "href": "foo://",
+                "pathname": ""
+            }
+        },
+        {
+            "comment": "Path-only URLs cannot have their paths erased",
+            "href": "foo:/some/path",
+            "new_value": "",
+            "expected": {
+                "href": "foo:/",
+                "pathname": "/"
+            }
+        },
+        {
+            "comment": "Path-only URLs always have an initial slash",
+            "href": "foo:/some/path",
+            "new_value": "test",
+            "expected": {
+                "href": "foo:/test",
+                "pathname": "/test"
+            }
+        },
+        {
+            "href": "unix:/run/foo.socket?timeout=10",
+            "new_value": "/var/log/../run/bar.socket",
+            "expected": {
+                "href": "unix:/var/run/bar.socket?timeout=10",
+                "pathname": "/var/run/bar.socket"
+            }
+        },
+        {
+            "href": "https://example.net#nav",
+            "new_value": "home",
+            "expected": {
+                "href": "https://example.net/home#nav",
+                "pathname": "/home"
+            }
+        },
+        {
+            "href": "https://example.net#nav",
+            "new_value": "../home",
+            "expected": {
+                "href": "https://example.net/home#nav",
+                "pathname": "/home"
+            }
+        },
+        {
+            "comment": "\\ is a segment delimiter for 'special' URLs",
+            "href": "http://example.net/home?lang=fr#nav",
+            "new_value": "\\a\\%2E\\b\\%2e.\\c",
+            "expected": {
+                "href": "http://example.net/a/c?lang=fr#nav",
+                "pathname": "/a/c"
+            }
+        },
+        {
+            "comment": "\\ is *not* a segment delimiter for non-'special' URLs",
+            "href": "view-source+http://example.net/home?lang=fr#nav",
+            "new_value": "\\a\\%2E\\b\\%2e.\\c",
+            "expected": {
+                "href": "view-source+http://example.net/\\a\\%2E\\b\\%2e.\\c?lang=fr#nav",
+                "pathname": "/\\a\\%2E\\b\\%2e.\\c"
+            }
+        },
+        {
+            "comment": "UTF-8 percent encoding with the default encode set. Tabs and newlines are removed.",
+            "href": "a:/",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "a:/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9",
+                "pathname": "/%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E%3F@AZ[\\]^_%60az%7B|%7D~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is, including %2E outside dotted segments.",
+            "href": "http://example.net",
+            "new_value": "%2e%2E%c3%89té",
+            "expected": {
+                "href": "http://example.net/%2e%2E%c3%89t%C3%A9",
+                "pathname": "/%2e%2E%c3%89t%C3%A9"
+            }
+        },
+        {
+            "comment": "? needs to be encoded",
+            "href": "http://example.net",
+            "new_value": "?",
+            "expected": {
+                "href": "http://example.net/%3F",
+                "pathname": "/%3F"
+            }
+        },
+        {
+            "comment": "# needs to be encoded",
+            "href": "http://example.net",
+            "new_value": "#",
+            "expected": {
+                "href": "http://example.net/%23",
+                "pathname": "/%23"
+            }
+        },
+        {
+            "comment": "? needs to be encoded, non-special scheme",
+            "href": "sc://example.net",
+            "new_value": "?",
+            "expected": {
+                "href": "sc://example.net/%3F",
+                "pathname": "/%3F"
+            }
+        },
+        {
+            "comment": "# needs to be encoded, non-special scheme",
+            "href": "sc://example.net",
+            "new_value": "#",
+            "expected": {
+                "href": "sc://example.net/%23",
+                "pathname": "/%23"
+            }
+        },
+        {
+            "comment": "? doesn't mess up encoding",
+            "href": "http://example.net",
+            "new_value": "/?é",
+            "expected": {
+                "href": "http://example.net/%3F%C3%A9",
+                "pathname": "/%3F%C3%A9"
+            }
+        },
+        {
+            "comment": "# doesn't mess up encoding",
+            "href": "http://example.net",
+            "new_value": "/#é",
+            "expected": {
+                "href": "http://example.net/%23%C3%A9",
+                "pathname": "/%23%C3%A9"
+            }
+        },
+        {
+            "comment": "File URLs and (back)slashes",
+            "href": "file://monkey/",
+            "new_value": "\\\\",
+            "expected": {
+                "href": "file://monkey//",
+                "pathname": "//"
+            }
+        },
+        {
+            "comment": "File URLs and (back)slashes",
+            "href": "file:///unicorn",
+            "new_value": "//\\/",
+            "expected": {
+                "href": "file://////",
+                "pathname": "////"
+            }
+        },
+        {
+            "comment": "File URLs and (back)slashes",
+            "href": "file:///unicorn",
+            "new_value": "//monkey/..//",
+            "expected": {
+                "href": "file://///",
+                "pathname": "///"
+            }
+        },
+        {
+            "comment": "Serialize /. in path",
+            "href": "non-spec:/",
+            "new_value": "/.//p",
+            "expected": {
+                "href": "non-spec:/.//p",
+                "pathname": "//p"
+            }
+        },
+        {
+            "href": "non-spec:/",
+            "new_value": "/..//p",
+            "expected": {
+                "href": "non-spec:/.//p",
+                "pathname": "//p"
+            }
+        },
+        {
+            "href": "non-spec:/",
+            "new_value": "//p",
+            "expected": {
+                "href": "non-spec:/.//p",
+                "pathname": "//p"
+            }
+        },
+        {
+            "comment": "Drop /. from path",
+            "href": "non-spec:/.//",
+            "new_value": "p",
+            "expected": {
+                "href": "non-spec:/p",
+                "pathname": "/p"
+            }
+        },
+        {
+            "comment": "Non-special URLs with non-opaque paths percent-encode U+0020",
+            "href": "data:/nospace",
+            "new_value": "space ",
+            "expected": {
+                "href": "data:/space%20",
+                "pathname": "/space%20"
+            }
+        },
+        {
+            "href": "sc:/nospace",
+            "new_value": "space ",
+            "expected": {
+                "href": "sc:/space%20",
+                "pathname": "/space%20"
+            }
+        },
+        {
+            "comment": "Trailing space should be encoded",
+            "href": "http://example.net",
+            "new_value": " ",
+            "expected": {
+                "href": "http://example.net/%20",
+                "pathname": "/%20"
+            }
+        },
+        {
+            "comment": "Trailing C0 control should be encoded",
+            "href": "http://example.net",
+            "new_value": "\u0000",
+            "expected": {
+                "href": "http://example.net/%00",
+                "pathname": "/%00"
+            }
+        }
+    ],
+    "search": [
+        {
+            "href": "https://example.net#nav",
+            "new_value": "lang=fr",
+            "expected": {
+                "href": "https://example.net/?lang=fr#nav",
+                "search": "?lang=fr"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "lang=fr",
+            "expected": {
+                "href": "https://example.net/?lang=fr#nav",
+                "search": "?lang=fr"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "?lang=fr",
+            "expected": {
+                "href": "https://example.net/?lang=fr#nav",
+                "search": "?lang=fr"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "??lang=fr",
+            "expected": {
+                "href": "https://example.net/??lang=fr#nav",
+                "search": "??lang=fr"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "?",
+            "expected": {
+                "href": "https://example.net/?#nav",
+                "search": ""
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "",
+            "expected": {
+                "href": "https://example.net/#nav",
+                "search": ""
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US",
+            "new_value": "",
+            "expected": {
+                "href": "https://example.net/",
+                "search": ""
+            }
+        },
+        {
+            "href": "https://example.net",
+            "new_value": "",
+            "expected": {
+                "href": "https://example.net/",
+                "search": ""
+            }
+        },
+        {
+            "comment": "UTF-8 percent encoding with the query encode set. Tabs and newlines are removed.",
+            "href": "a:/",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "a:/?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
+                "search": "?%00%01%1F%20!%22%23$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_`az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is",
+            "href": "http://example.net",
+            "new_value": "%c3%89té",
+            "expected": {
+                "href": "http://example.net/?%c3%89t%C3%A9",
+                "search": "?%c3%89t%C3%A9"
+            }
+        },
+        {
+            "comment": "Drop trailing spaces from trailing opaque paths",
+            "href": "data:space ?query",
+            "new_value": "",
+            "expected": {
+                "href": "data:space",
+                "pathname": "space",
+                "search": ""
+            }
+        },
+        {
+            "href": "sc:space ?query",
+            "new_value": "",
+            "expected": {
+                "href": "sc:space",
+                "pathname": "space",
+                "search": ""
+            }
+        },
+        {
+            "comment": "Do not drop trailing spaces from non-trailing opaque paths",
+            "href": "data:space  ?query#fragment",
+            "new_value": "",
+            "expected": {
+                "href": "data:space  #fragment",
+                "search": ""
+            }
+        },
+        {
+            "href": "sc:space  ?query#fragment",
+            "new_value": "",
+            "expected": {
+                "href": "sc:space  #fragment",
+                "search": ""
+            }
+        },
+        {
+            "comment": "Trailing space should be encoded",
+            "href": "http://example.net",
+            "new_value": " ",
+            "expected": {
+                "href": "http://example.net/?%20",
+                "search": "?%20"
+            }
+        },
+        {
+            "comment": "Trailing C0 control should be encoded",
+            "href": "http://example.net",
+            "new_value": "\u0000",
+            "expected": {
+                "href": "http://example.net/?%00",
+                "search": "?%00"
+            }
+        }
+    ],
+    "hash": [
+        {
+            "href": "https://example.net",
+            "new_value": "main",
+            "expected": {
+                "href": "https://example.net/#main",
+                "hash": "#main"
+            }
+        },
+        {
+            "href": "https://example.net#nav",
+            "new_value": "main",
+            "expected": {
+                "href": "https://example.net/#main",
+                "hash": "#main"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US",
+            "new_value": "##nav",
+            "expected": {
+                "href": "https://example.net/?lang=en-US##nav",
+                "hash": "##nav"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "#main",
+            "expected": {
+                "href": "https://example.net/?lang=en-US#main",
+                "hash": "#main"
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "#",
+            "expected": {
+                "href": "https://example.net/?lang=en-US#",
+                "hash": ""
+            }
+        },
+        {
+            "href": "https://example.net?lang=en-US#nav",
+            "new_value": "",
+            "expected": {
+                "href": "https://example.net/?lang=en-US",
+                "hash": ""
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo bar",
+            "expected": {
+                "href": "http://example.net/#foo%20bar",
+                "hash": "#foo%20bar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo\"bar",
+            "expected": {
+                "href": "http://example.net/#foo%22bar",
+                "hash": "#foo%22bar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo<bar",
+            "expected": {
+                "href": "http://example.net/#foo%3Cbar",
+                "hash": "#foo%3Cbar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo>bar",
+            "expected": {
+                "href": "http://example.net/#foo%3Ebar",
+                "hash": "#foo%3Ebar"
+            }
+        },
+        {
+            "href": "http://example.net",
+            "new_value": "#foo`bar",
+            "expected": {
+                "href": "http://example.net/#foo%60bar",
+                "hash": "#foo%60bar"
+            }
+        },
+        {
+            "comment": "Simple percent-encoding; tabs and newlines are removed",
+            "href": "a:/",
+            "new_value": "\u0000\u0001\t\n\r\u001f !\"#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~\u007f\u0080\u0081Éé",
+            "expected": {
+                "href": "a:/#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9",
+                "hash": "#%00%01%1F%20!%22#$%&'()*+,-./09:;%3C=%3E?@AZ[\\]^_%60az{|}~%7F%C2%80%C2%81%C3%89%C3%A9"
+            }
+        },
+        {
+            "comment": "Percent-encode NULLs in fragment",
+            "href": "http://example.net",
+            "new_value": "a\u0000b",
+            "expected": {
+                "href": "http://example.net/#a%00b",
+                "hash": "#a%00b"
+            }
+        },
+        {
+            "comment": "Percent-encode NULLs in fragment",
+            "href": "non-spec:/",
+            "new_value": "a\u0000b",
+            "expected": {
+                "href": "non-spec:/#a%00b",
+                "hash": "#a%00b"
+            }
+        },
+        {
+            "comment": "Bytes already percent-encoded are left as-is",
+            "href": "http://example.net",
+            "new_value": "%c3%89té",
+            "expected": {
+                "href": "http://example.net/#%c3%89t%C3%A9",
+                "hash": "#%c3%89t%C3%A9"
+            }
+        },
+        {
+            "href": "javascript:alert(1)",
+            "new_value": "castle",
+            "expected": {
+                "href": "javascript:alert(1)#castle",
+                "hash": "#castle"
+            }
+        },
+        {
+            "comment": "Drop trailing spaces from trailing opaque paths",
+            "href": "data:space                                                                                                                                  #fragment",
+            "new_value": "",
+            "expected": {
+                "href": "data:space",
+                "pathname": "space",
+                "hash": ""
+            }
+        },
+        {
+            "href": "sc:space    #fragment",
+            "new_value": "",
+            "expected": {
+                "href": "sc:space",
+                "pathname": "space",
+                "hash": ""
+            }
+        },
+        {
+            "comment": "Do not drop trailing spaces from non-trailing opaque paths",
+            "href": "data:space  ?query#fragment",
+            "new_value": "",
+            "expected": {
+                "href": "data:space  ?query",
+                "hash": ""
+            }
+        },
+        {
+            "href": "sc:space  ?query#fragment",
+            "new_value": "",
+            "expected": {
+                "href": "sc:space  ?query",
+                "hash": ""
+            }
+        },
+        {
+            "comment": "Trailing space should be encoded",
+            "href": "http://example.net",
+            "new_value": " ",
+            "expected": {
+                "href": "http://example.net/#%20",
+                "hash": "#%20"
+            }
+        },
+        {
+            "comment": "Trailing C0 control should be encoded",
+            "href": "http://example.net",
+            "new_value": "\u0000",
+            "expected": {
+                "href": "http://example.net/#%00",
+                "hash": "#%00"
+            }
+        }
+    ],
+    "href": [
+        {
+            "href": "file:///var/log/system.log",
+            "new_value": "http://0300.168.0xF0",
+            "expected": {
+                "href": "http://192.168.0.240/",
+                "protocol": "http:"
+            }
+        }
+    ]
+}
diff --git a/tests/wpt/toascii.json b/tests/wpt/toascii.json
new file mode 100644 (file)
index 0000000..bca28b4
--- /dev/null
@@ -0,0 +1,176 @@
+[
+  "This resource is focused on highlighting issues with UTS #46 ToASCII",
+  {
+    "comment": "Label with hyphens in 3rd and 4th position",
+    "input": "aa--",
+    "output": "aa--"
+  },
+  {
+    "input": "a†--",
+    "output": "xn--a---kp0a"
+  },
+  {
+    "input": "ab--c",
+    "output": "ab--c"
+  },
+  {
+    "comment": "Label with leading hyphen",
+    "input": "-x",
+    "output": "-x"
+  },
+  {
+    "input": "-†",
+    "output": "xn----xhn"
+  },
+  {
+    "input": "-x.xn--zca",
+    "output": "-x.xn--zca"
+  },
+  {
+    "input": "-x.ß",
+    "output": "-x.xn--zca"
+  },
+  {
+    "comment": "Label with trailing hyphen",
+    "input": "x-.xn--zca",
+    "output": "x-.xn--zca"
+  },
+  {
+    "input": "x-.ß",
+    "output": "x-.xn--zca"
+  },
+  {
+    "comment": "Empty labels",
+    "input": "x..xn--zca",
+    "output": "x..xn--zca"
+  },
+  {
+    "input": "x..ß",
+    "output": "x..xn--zca"
+  },
+  {
+    "comment": "Invalid Punycode",
+    "input": "xn--a",
+    "output": null
+  },
+  {
+    "input": "xn--a.xn--zca",
+    "output": null
+  },
+  {
+    "input": "xn--a.ß",
+    "output": null
+  },
+  {
+    "input": "xn--ls8h=",
+    "output": null
+  },
+  {
+    "comment": "Invalid Punycode (contains non-ASCII character)",
+    "input": "xn--tešla",
+    "output": null
+  },
+  {
+    "comment": "Valid Punycode",
+    "input": "xn--zca.xn--zca",
+    "output": "xn--zca.xn--zca"
+  },
+  {
+    "comment": "Mixed",
+    "input": "xn--zca.ß",
+    "output": "xn--zca.xn--zca"
+  },
+  {
+    "input": "ab--c.xn--zca",
+    "output": "ab--c.xn--zca"
+  },
+  {
+    "input": "ab--c.ß",
+    "output": "ab--c.xn--zca"
+  },
+  {
+    "comment": "CheckJoiners is true",
+    "input": "\u200D.example",
+    "output": null
+  },
+  {
+    "input": "xn--1ug.example",
+    "output": null
+  },
+  {
+    "comment": "CheckBidi is true",
+    "input": "يa",
+    "output": null
+  },
+  {
+    "input": "xn--a-yoc",
+    "output": null
+  },
+  {
+    "comment": "processing_option is Nontransitional_Processing",
+    "input": "ශ්‍රී",
+    "output": "xn--10cl1a0b660p"
+  },
+  {
+    "input": "نامه‌ای",
+    "output": "xn--mgba3gch31f060k"
+  },
+  {
+    "comment": "U+FFFD",
+    "input": "\uFFFD.com",
+    "output": null
+  },
+  {
+    "comment": "U+FFFD character encoded in Punycode",
+    "input": "xn--zn7c.com",
+    "output": null
+  },
+  {
+    "comment": "Label longer than 63 code points",
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901†",
+    "output": "xn--x01234567890123456789012345678901234567890123456789012345678901-6963b"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca"
+  },
+  {
+    "input": "x01234567890123456789012345678901234567890123456789012345678901x.ß",
+    "output": "x01234567890123456789012345678901234567890123456789012345678901x.xn--zca"
+  },
+  {
+    "comment": "Domain excluding TLD longer than 253 code points",
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.x"
+  },
+  {
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca"
+  },
+  {
+    "input": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.ß",
+    "output": "01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.01234567890123456789012345678901234567890123456789.0123456789012345678901234567890123456789012345678.xn--zca"
+  },
+  {
+    "comment": "IDNA ignored code points",
+    "input": "a\u00ADb",
+    "output": "ab"
+  },
+  {
+    "comment": "Interesting UseSTD3ASCIIRules=false cases",
+    "input": "≠",
+    "output": "xn--1ch"
+  },
+  {
+    "input": "≮",
+    "output": "xn--gdh"
+  },
+  {
+    "input": "≯",
+    "output": "xn--hdh"
+  }
+]
diff --git a/tests/wpt/urltestdata.json b/tests/wpt/urltestdata.json
new file mode 100644 (file)
index 0000000..69767a2
--- /dev/null
@@ -0,0 +1,9688 @@
+[
+  "See ../README.md for a description of the format.",
+  {
+    "input": "http://example\t.\norg",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://user:pass@foo:21/bar;par?b#c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://user:pass@foo:21/bar;par?b#c",
+    "origin": "http://foo:21",
+    "protocol": "http:",
+    "username": "user",
+    "password": "pass",
+    "host": "foo:21",
+    "hostname": "foo",
+    "port": "21",
+    "pathname": "/bar;par",
+    "search": "?b",
+    "hash": "#c"
+  },
+  {
+    "input": "https://test:@test",
+    "base": null,
+    "href": "https://test@test/",
+    "origin": "https://test",
+    "protocol": "https:",
+    "username": "test",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://:@test",
+    "base": null,
+    "href": "https://test/",
+    "origin": "https://test",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://test:@test/x",
+    "base": null,
+    "href": "non-special://test@test/x",
+    "origin": "null",
+    "protocol": "non-special:",
+    "username": "test",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://:@test/x",
+    "base": null,
+    "href": "non-special://test/x",
+    "origin": "null",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:foo.com",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/foo.com",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/foo.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\t   :foo.com   \n",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:foo.com",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:foo.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": " foo.com  ",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/foo.com",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/foo.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "a:\t foo.com",
+    "base": "http://example.org/foo/bar",
+    "href": "a: foo.com",
+    "origin": "null",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": " foo.com",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:21/ b ? d # e ",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f:21/%20b%20?%20d%20#%20e",
+    "origin": "http://f:21",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f:21",
+    "hostname": "f",
+    "port": "21",
+    "pathname": "/%20b%20",
+    "search": "?%20d%20",
+    "hash": "#%20e"
+  },
+  {
+    "input": "lolscheme:x x#x x",
+    "base": null,
+    "href": "lolscheme:x x#x%20x",
+    "protocol": "lolscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "x x",
+    "search": "",
+    "hash": "#x%20x"
+  },
+  {
+    "input": "http://f:/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f/c",
+    "origin": "http://f",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f",
+    "hostname": "f",
+    "port": "",
+    "pathname": "/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:0/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f:0/c",
+    "origin": "http://f:0",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f:0",
+    "hostname": "f",
+    "port": "0",
+    "pathname": "/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:00000000000000/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f:0/c",
+    "origin": "http://f:0",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f:0",
+    "hostname": "f",
+    "port": "0",
+    "pathname": "/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:00000000000000000000080/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f/c",
+    "origin": "http://f",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f",
+    "hostname": "f",
+    "port": "",
+    "pathname": "/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:b/c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://f: /c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://f:\n/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://f/c",
+    "origin": "http://f",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "f",
+    "hostname": "f",
+    "port": "",
+    "pathname": "/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://f:fifty-two/c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://f:999999/c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "non-special://f:999999/c",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://f: 21 / b ? d # e ",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "  \t",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":foo.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:foo.com/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:foo.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":foo.com\\",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:foo.com/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:foo.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":a",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:a",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:a",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":\\",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":#",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:#",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "#",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar#",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "#/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar#/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": "#/"
+  },
+  {
+    "input": "#\\",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar#\\",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": "#\\"
+  },
+  {
+    "input": "#;?",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar#;?",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": "#;?"
+  },
+  {
+    "input": "?",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar?",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ":23",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:23",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:23",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/:23",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/:23",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/:23",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\x",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/x",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\\\x\\hello",
+    "base": "http://example.org/foo/bar",
+    "href": "http://x/hello",
+    "origin": "http://x",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "x",
+    "hostname": "x",
+    "port": "",
+    "pathname": "/hello",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "::",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/::",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/::",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "::23",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/::23",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/::23",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo://",
+    "base": "http://example.org/foo/bar",
+    "href": "foo://",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://a:b@c:29/d",
+    "base": "http://example.org/foo/bar",
+    "href": "http://a:b@c:29/d",
+    "origin": "http://c:29",
+    "protocol": "http:",
+    "username": "a",
+    "password": "b",
+    "host": "c:29",
+    "hostname": "c",
+    "port": "29",
+    "pathname": "/d",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http::@c:29",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/:@c:29",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/:@c:29",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://&a:foo(b]c@d:2/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://&a:foo(b%5Dc@d:2/",
+    "origin": "http://d:2",
+    "protocol": "http:",
+    "username": "&a",
+    "password": "foo(b%5Dc",
+    "host": "d:2",
+    "hostname": "d",
+    "port": "2",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://::@c@d:2",
+    "base": "http://example.org/foo/bar",
+    "href": "http://:%3A%40c@d:2/",
+    "origin": "http://d:2",
+    "protocol": "http:",
+    "username": "",
+    "password": "%3A%40c",
+    "host": "d:2",
+    "hostname": "d",
+    "port": "2",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo.com:b@d/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo.com:b@d/",
+    "origin": "http://d",
+    "protocol": "http:",
+    "username": "foo.com",
+    "password": "b",
+    "host": "d",
+    "hostname": "d",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo.com/\\@",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo.com//@",
+    "origin": "http://foo.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.com",
+    "hostname": "foo.com",
+    "port": "",
+    "pathname": "//@",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:\\\\foo.com\\",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo.com/",
+    "origin": "http://foo.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.com",
+    "hostname": "foo.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:\\\\a\\b:c\\d@foo.com\\",
+    "base": "http://example.org/foo/bar",
+    "href": "http://a/b:c/d@foo.com/",
+    "origin": "http://a",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "a",
+    "hostname": "a",
+    "port": "",
+    "pathname": "/b:c/d@foo.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo:/",
+    "base": "http://example.org/foo/bar",
+    "href": "foo:/",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo:/bar.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "foo:/bar.com/",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/bar.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo://///////",
+    "base": "http://example.org/foo/bar",
+    "href": "foo://///////",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "///////",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo://///////bar.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "foo://///////bar.com/",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "///////bar.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "foo:////://///",
+    "base": "http://example.org/foo/bar",
+    "href": "foo:////://///",
+    "origin": "null",
+    "protocol": "foo:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//://///",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "c:/foo",
+    "base": "http://example.org/foo/bar",
+    "href": "c:/foo",
+    "origin": "null",
+    "protocol": "c:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//foo/bar",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo/bar",
+    "origin": "http://foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo/path;a??e#f#g",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo/path;a??e#f#g",
+    "origin": "http://foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/path;a",
+    "search": "??e",
+    "hash": "#f#g"
+  },
+  {
+    "input": "http://foo/abcd?efgh?ijkl",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo/abcd?efgh?ijkl",
+    "origin": "http://foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/abcd",
+    "search": "?efgh?ijkl",
+    "hash": ""
+  },
+  {
+    "input": "http://foo/abcd#foo?bar",
+    "base": "http://example.org/foo/bar",
+    "href": "http://foo/abcd#foo?bar",
+    "origin": "http://foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/abcd",
+    "search": "",
+    "hash": "#foo?bar"
+  },
+  {
+    "input": "[61:24:74]:98",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/[61:24:74]:98",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/[61:24:74]:98",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:[61:27]/:foo",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/[61:27]/:foo",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/[61:27]/:foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[1::2]:3:4",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://2001::1",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://2001::1]",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://2001::1]:80",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://[2001::1]",
+    "base": "http://example.org/foo/bar",
+    "href": "http://[2001::1]/",
+    "origin": "http://[2001::1]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[2001::1]",
+    "hostname": "[2001::1]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[::127.0.0.1]",
+    "base": "http://example.org/foo/bar",
+    "href": "http://[::7f00:1]/",
+    "origin": "http://[::7f00:1]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[::7f00:1]",
+    "hostname": "[::7f00:1]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[::127.0.0.1.]",
+    "base": "http://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "http://[0:0:0:0:0:0:13.1.68.3]",
+    "base": "http://example.org/foo/bar",
+    "href": "http://[::d01:4403]/",
+    "origin": "http://[::d01:4403]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[::d01:4403]",
+    "hostname": "[::d01:4403]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[2001::1]:80",
+    "base": "http://example.org/foo/bar",
+    "href": "http://[2001::1]/",
+    "origin": "http://[2001::1]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[2001::1]",
+    "hostname": "[2001::1]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/example.com/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ftp://example.com/",
+    "origin": "ftp://example.com",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "https://example.com/",
+    "origin": "https://example.com",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "madeupscheme:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "madeupscheme:/example.com/",
+    "origin": "null",
+    "protocol": "madeupscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "file:///example.com/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://example:1/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://example:test/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://example%/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://[example]/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "ftps:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ftps:/example.com/",
+    "origin": "null",
+    "protocol": "ftps:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "gopher:/example.com/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ws://example.com/",
+    "origin": "ws://example.com",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "wss://example.com/",
+    "origin": "wss://example.com",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "data:/example.com/",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "javascript:/example.com/",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto:/example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "mailto:/example.com/",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/example.com/",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ftp://example.com/",
+    "origin": "ftp://example.com",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "https://example.com/",
+    "origin": "https://example.com",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "madeupscheme:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "madeupscheme:example.com/",
+    "origin": "null",
+    "protocol": "madeupscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftps:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ftps:example.com/",
+    "origin": "null",
+    "protocol": "ftps:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "gopher:example.com/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "ws://example.com/",
+    "origin": "ws://example.com",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "wss://example.com/",
+    "origin": "wss://example.com",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "data:example.com/",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "javascript:example.com/",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto:example.com/",
+    "base": "http://example.org/foo/bar",
+    "href": "mailto:example.com/",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/a/b/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/a/b/c",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/a/b/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/a/ /c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/a/%20/c",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/a/%20/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/a%2fc",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/a%2fc",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/a%2fc",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/a/%2f/c",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/a/%2f/c",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/a/%2f/c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "#β",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar#%CE%B2",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": "#%CE%B2"
+  },
+  {
+    "input": "data:text/html,test#test",
+    "base": "http://example.org/foo/bar",
+    "href": "data:text/html,test#test",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "text/html,test",
+    "search": "",
+    "hash": "#test"
+  },
+  {
+    "input": "tel:1234567890",
+    "base": "http://example.org/foo/bar",
+    "href": "tel:1234567890",
+    "origin": "null",
+    "protocol": "tel:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "1234567890",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on https://felixfbecker.github.io/whatwg-url-custom-host-repro/",
+  {
+    "input": "ssh://example.com/foo/bar.git",
+    "base": "http://example.org/",
+    "href": "ssh://example.com/foo/bar.git",
+    "origin": "null",
+    "protocol": "ssh:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/bar.git",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/file.html",
+  {
+    "input": "file:c:\\foo\\bar.html",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///c:/foo/bar.html",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:/foo/bar.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "  File:c|////foo\\bar.html",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///c:////foo/bar.html",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:////foo/bar.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|/foo/bar",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///C:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/C|\\foo\\bar",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///C:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//C|/foo/bar",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///C:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//server/file",
+    "base": "file:///tmp/mock/path",
+    "href": "file://server/file",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "server",
+    "hostname": "server",
+    "port": "",
+    "pathname": "/file",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\\\server\\file",
+    "base": "file:///tmp/mock/path",
+    "href": "file://server/file",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "server",
+    "hostname": "server",
+    "port": "",
+    "pathname": "/file",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/\\server/file",
+    "base": "file:///tmp/mock/path",
+    "href": "file://server/file",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "server",
+    "hostname": "server",
+    "port": "",
+    "pathname": "/file",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///foo/bar.txt",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///foo/bar.txt",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/foo/bar.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///home/me",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///home/me",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/home/me",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///test",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://test",
+    "base": "file:///tmp/mock/path",
+    "href": "file://test/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost/",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost/test",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///tmp/mock/test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/tmp/mock/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:test",
+    "base": "file:///tmp/mock/path",
+    "href": "file:///tmp/mock/test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/tmp/mock/test",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/path.js",
+  {
+    "input": "http://example.com/././foo",
+    "base": null,
+    "href": "http://example.com/foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/./.foo",
+    "base": null,
+    "href": "http://example.com/.foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/.foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/.",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/./",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar/..",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar/../",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/..bar",
+    "base": null,
+    "href": "http://example.com/foo/..bar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/..bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar/../ton",
+    "base": null,
+    "href": "http://example.com/foo/ton",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/ton",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar/../ton/../../a",
+    "base": null,
+    "href": "http://example.com/a",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/a",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/../../..",
+    "base": null,
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/../../../ton",
+    "base": null,
+    "href": "http://example.com/ton",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/ton",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/%2e",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/%2e%2",
+    "base": null,
+    "href": "http://example.com/foo/%2e%2",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/%2e%2",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/%2e./%2e%2e/.%2e/%2e.bar",
+    "base": null,
+    "href": "http://example.com/%2e.bar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%2e.bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com////../..",
+    "base": null,
+    "href": "http://example.com//",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar//../..",
+    "base": null,
+    "href": "http://example.com/foo/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo/bar//..",
+    "base": null,
+    "href": "http://example.com/foo/bar/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo/bar/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo",
+    "base": null,
+    "href": "http://example.com/foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/%20foo",
+    "base": null,
+    "href": "http://example.com/%20foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%20foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%",
+    "base": null,
+    "href": "http://example.com/foo%",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%2",
+    "base": null,
+    "href": "http://example.com/foo%2",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%2",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%2zbar",
+    "base": null,
+    "href": "http://example.com/foo%2zbar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%2zbar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%2©zbar",
+    "base": null,
+    "href": "http://example.com/foo%2%C3%82%C2%A9zbar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%2%C3%82%C2%A9zbar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%41%7a",
+    "base": null,
+    "href": "http://example.com/foo%41%7a",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%41%7a",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo\t\u0091%91",
+    "base": null,
+    "href": "http://example.com/foo%C2%91%91",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%C2%91%91",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo%00%51",
+    "base": null,
+    "href": "http://example.com/foo%00%51",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foo%00%51",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/(%28:%3A%29)",
+    "base": null,
+    "href": "http://example.com/(%28:%3A%29)",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/(%28:%3A%29)",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/%3A%3a%3C%3c",
+    "base": null,
+    "href": "http://example.com/%3A%3a%3C%3c",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%3A%3a%3C%3c",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/foo\tbar",
+    "base": null,
+    "href": "http://example.com/foobar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/foobar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com\\\\foo\\\\bar",
+    "base": null,
+    "href": "http://example.com//foo//bar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "//foo//bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+    "base": null,
+    "href": "http://example.com/%7Ffp3%3Eju%3Dduvgw%3Dd",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%7Ffp3%3Eju%3Dduvgw%3Dd",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/@asdf%40",
+    "base": null,
+    "href": "http://example.com/@asdf%40",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/@asdf%40",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/你好你好",
+    "base": null,
+    "href": "http://example.com/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%E4%BD%A0%E5%A5%BD%E4%BD%A0%E5%A5%BD",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/‥/foo",
+    "base": null,
+    "href": "http://example.com/%E2%80%A5/foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%E2%80%A5/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com//foo",
+    "base": null,
+    "href": "http://example.com/%EF%BB%BF/foo",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%EF%BB%BF/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/‮/foo/‭/bar",
+    "base": null,
+    "href": "http://example.com/%E2%80%AE/foo/%E2%80%AD/bar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%E2%80%AE/foo/%E2%80%AD/bar",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/script-tests/relative.js",
+  {
+    "input": "http://www.google.com/foo?bar=baz#",
+    "base": null,
+    "href": "http://www.google.com/foo?bar=baz#",
+    "origin": "http://www.google.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.google.com",
+    "hostname": "www.google.com",
+    "port": "",
+    "pathname": "/foo",
+    "search": "?bar=baz",
+    "hash": ""
+  },
+  {
+    "input": "http://www.google.com/foo?bar=baz# »",
+    "base": null,
+    "href": "http://www.google.com/foo?bar=baz#%20%C2%BB",
+    "origin": "http://www.google.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.google.com",
+    "hostname": "www.google.com",
+    "port": "",
+    "pathname": "/foo",
+    "search": "?bar=baz",
+    "hash": "#%20%C2%BB"
+  },
+  {
+    "input": "data:test# »",
+    "base": null,
+    "href": "data:test#%20%C2%BB",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "test",
+    "search": "",
+    "hash": "#%20%C2%BB"
+  },
+  {
+    "input": "http://www.google.com",
+    "base": null,
+    "href": "http://www.google.com/",
+    "origin": "http://www.google.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.google.com",
+    "hostname": "www.google.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.0x00A80001",
+    "base": null,
+    "href": "http://192.168.0.1/",
+    "origin": "http://192.168.0.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.0.1",
+    "hostname": "192.168.0.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://www/foo%2Ehtml",
+    "base": null,
+    "href": "http://www/foo%2Ehtml",
+    "origin": "http://www",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www",
+    "hostname": "www",
+    "port": "",
+    "pathname": "/foo%2Ehtml",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://www/foo/%2E/html",
+    "base": null,
+    "href": "http://www/foo/html",
+    "origin": "http://www",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www",
+    "hostname": "www",
+    "port": "",
+    "pathname": "/foo/html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://user:pass@/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://%25DOMAIN:foobar@foodomain.com/",
+    "base": null,
+    "href": "http://%25DOMAIN:foobar@foodomain.com/",
+    "origin": "http://foodomain.com",
+    "protocol": "http:",
+    "username": "%25DOMAIN",
+    "password": "foobar",
+    "host": "foodomain.com",
+    "hostname": "foodomain.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:\\\\www.google.com\\foo",
+    "base": null,
+    "href": "http://www.google.com/foo",
+    "origin": "http://www.google.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.google.com",
+    "hostname": "www.google.com",
+    "port": "",
+    "pathname": "/foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo:80/",
+    "base": null,
+    "href": "http://foo/",
+    "origin": "http://foo",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo:81/",
+    "base": null,
+    "href": "http://foo:81/",
+    "origin": "http://foo:81",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo:81",
+    "hostname": "foo",
+    "port": "81",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "httpa://foo:80/",
+    "base": null,
+    "href": "httpa://foo:80/",
+    "origin": "null",
+    "protocol": "httpa:",
+    "username": "",
+    "password": "",
+    "host": "foo:80",
+    "hostname": "foo",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://foo:-80/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://foo:443/",
+    "base": null,
+    "href": "https://foo/",
+    "origin": "https://foo",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://foo:80/",
+    "base": null,
+    "href": "https://foo:80/",
+    "origin": "https://foo:80",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "foo:80",
+    "hostname": "foo",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp://foo:21/",
+    "base": null,
+    "href": "ftp://foo/",
+    "origin": "ftp://foo",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp://foo:80/",
+    "base": null,
+    "href": "ftp://foo:80/",
+    "origin": "ftp://foo:80",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "foo:80",
+    "hostname": "foo",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher://foo:70/",
+    "base": null,
+    "href": "gopher://foo:70/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "foo:70",
+    "hostname": "foo",
+    "port": "70",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher://foo:443/",
+    "base": null,
+    "href": "gopher://foo:443/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "foo:443",
+    "hostname": "foo",
+    "port": "443",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws://foo:80/",
+    "base": null,
+    "href": "ws://foo/",
+    "origin": "ws://foo",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws://foo:81/",
+    "base": null,
+    "href": "ws://foo:81/",
+    "origin": "ws://foo:81",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "foo:81",
+    "hostname": "foo",
+    "port": "81",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws://foo:443/",
+    "base": null,
+    "href": "ws://foo:443/",
+    "origin": "ws://foo:443",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "foo:443",
+    "hostname": "foo",
+    "port": "443",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws://foo:815/",
+    "base": null,
+    "href": "ws://foo:815/",
+    "origin": "ws://foo:815",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "foo:815",
+    "hostname": "foo",
+    "port": "815",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss://foo:80/",
+    "base": null,
+    "href": "wss://foo:80/",
+    "origin": "wss://foo:80",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "foo:80",
+    "hostname": "foo",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss://foo:81/",
+    "base": null,
+    "href": "wss://foo:81/",
+    "origin": "wss://foo:81",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "foo:81",
+    "hostname": "foo",
+    "port": "81",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss://foo:443/",
+    "base": null,
+    "href": "wss://foo/",
+    "origin": "wss://foo",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "foo",
+    "hostname": "foo",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss://foo:815/",
+    "base": null,
+    "href": "wss://foo:815/",
+    "origin": "wss://foo:815",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "foo:815",
+    "hostname": "foo",
+    "port": "815",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/example.com/",
+    "base": null,
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp:/example.com/",
+    "base": null,
+    "href": "ftp://example.com/",
+    "origin": "ftp://example.com",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https:/example.com/",
+    "base": null,
+    "href": "https://example.com/",
+    "origin": "https://example.com",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "madeupscheme:/example.com/",
+    "base": null,
+    "href": "madeupscheme:/example.com/",
+    "origin": "null",
+    "protocol": "madeupscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:/example.com/",
+    "base": null,
+    "href": "file:///example.com/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftps:/example.com/",
+    "base": null,
+    "href": "ftps:/example.com/",
+    "origin": "null",
+    "protocol": "ftps:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher:/example.com/",
+    "base": null,
+    "href": "gopher:/example.com/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws:/example.com/",
+    "base": null,
+    "href": "ws://example.com/",
+    "origin": "ws://example.com",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss:/example.com/",
+    "base": null,
+    "href": "wss://example.com/",
+    "origin": "wss://example.com",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data:/example.com/",
+    "base": null,
+    "href": "data:/example.com/",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript:/example.com/",
+    "base": null,
+    "href": "javascript:/example.com/",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto:/example.com/",
+    "base": null,
+    "href": "mailto:/example.com/",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:example.com/",
+    "base": null,
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftp:example.com/",
+    "base": null,
+    "href": "ftp://example.com/",
+    "origin": "ftp://example.com",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https:example.com/",
+    "base": null,
+    "href": "https://example.com/",
+    "origin": "https://example.com",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "madeupscheme:example.com/",
+    "base": null,
+    "href": "madeupscheme:example.com/",
+    "origin": "null",
+    "protocol": "madeupscheme:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ftps:example.com/",
+    "base": null,
+    "href": "ftps:example.com/",
+    "origin": "null",
+    "protocol": "ftps:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "gopher:example.com/",
+    "base": null,
+    "href": "gopher:example.com/",
+    "origin": "null",
+    "protocol": "gopher:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ws:example.com/",
+    "base": null,
+    "href": "ws://example.com/",
+    "origin": "ws://example.com",
+    "protocol": "ws:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wss:example.com/",
+    "base": null,
+    "href": "wss://example.com/",
+    "origin": "wss://example.com",
+    "protocol": "wss:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data:example.com/",
+    "base": null,
+    "href": "data:example.com/",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript:example.com/",
+    "base": null,
+    "href": "javascript:example.com/",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto:example.com/",
+    "base": null,
+    "href": "mailto:example.com/",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "example.com/",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/segments-userinfo-vs-host.html",
+  {
+    "input": "http:@www.example.com",
+    "base": null,
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/@www.example.com",
+    "base": null,
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://@www.example.com",
+    "base": null,
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:a:b@www.example.com",
+    "base": null,
+    "href": "http://a:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/a:b@www.example.com",
+    "base": null,
+    "href": "http://a:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://a:b@www.example.com",
+    "base": null,
+    "href": "http://a:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://@pple.com",
+    "base": null,
+    "href": "http://pple.com/",
+    "origin": "http://pple.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "pple.com",
+    "hostname": "pple.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http::b@www.example.com",
+    "base": null,
+    "href": "http://:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/:b@www.example.com",
+    "base": null,
+    "href": "http://:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://:b@www.example.com",
+    "base": null,
+    "href": "http://:b@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "b",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/:@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http://user@/www.example.com",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http:@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http:/@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http://@/www.example.com",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https:@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http:a:b@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http:/a:b@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http://a:b@/www.example.com",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http::@/www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http:a:@www.example.com",
+    "base": null,
+    "href": "http://a@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:/a:@www.example.com",
+    "base": null,
+    "href": "http://a@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://a:@www.example.com",
+    "base": null,
+    "href": "http://a@www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "a",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://www.@pple.com",
+    "base": null,
+    "href": "http://www.@pple.com/",
+    "origin": "http://pple.com",
+    "protocol": "http:",
+    "username": "www.",
+    "password": "",
+    "host": "pple.com",
+    "hostname": "pple.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http:@:www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http:/@:www.example.com",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "http://@:www.example.com",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://:@www.example.com",
+    "base": null,
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# Others",
+  {
+    "input": "/",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": ".",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "./test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../aaa/test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/aaa/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/aaa/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../../test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "中/test.txt",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example.com/%E4%B8%AD/test.txt",
+    "origin": "http://www.example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "port": "",
+    "pathname": "/%E4%B8%AD/test.txt",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://www.example2.com",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example2.com/",
+    "origin": "http://www.example2.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example2.com",
+    "hostname": "www.example2.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//www.example2.com",
+    "base": "http://www.example.com/test",
+    "href": "http://www.example2.com/",
+    "origin": "http://www.example2.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.example2.com",
+    "hostname": "www.example2.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:...",
+    "base": "http://www.example.com/test",
+    "href": "file:///...",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/...",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:..",
+    "base": "http://www.example.com/test",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:a",
+    "base": "http://www.example.com/test",
+    "href": "file:///a",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/a",
+    "search": "",
+    "hash": ""
+  },
+  "# Based on http://trac.webkit.org/browser/trunk/LayoutTests/fast/url/host.html",
+  "Basic canonicalization, uppercase should be converted to lowercase",
+  {
+    "input": "http://ExAmPlE.CoM",
+    "base": "http://other.com/",
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example example.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://Goo%20 goo%7C|.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[:]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "U+3000 is mapped to U+0020 (space) which is disallowed",
+  {
+    "input": "http://GOO\u00a0\u3000goo.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "Other types of space (no-break, zero-width, zero-width-no-break) are name-prepped away to nothing. U+200B, U+2060, and U+FEFF, are ignored",
+  {
+    "input": "http://GOO\u200b\u2060\ufeffgoo.com",
+    "base": "http://other.com/",
+    "href": "http://googoo.com/",
+    "origin": "http://googoo.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "googoo.com",
+    "hostname": "googoo.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Leading and trailing C0 control or space",
+  {
+    "input": "\u0000\u001b\u0004\u0012 http://example.com/\u001f \u000d ",
+    "base": null,
+    "href": "http://example.com/",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Ideographic full stop (full-width period for Chinese, etc.) should be treated as a dot. U+3002 is mapped to U+002E (dot)",
+  {
+    "input": "http://www.foo。bar.com",
+    "base": "http://other.com/",
+    "href": "http://www.foo.bar.com/",
+    "origin": "http://www.foo.bar.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "www.foo.bar.com",
+    "hostname": "www.foo.bar.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Invalid unicode characters should fail... U+FDD0 is disallowed; %ef%b7%90 is U+FDD0",
+  {
+    "input": "http://\ufdd0zyx.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "This is the same as previous but escaped",
+  {
+    "input": "http://%ef%b7%90zyx.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "U+FFFD",
+  {
+    "input": "https://\ufffd",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://%EF%BF%BD",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://x/\ufffd?\ufffd#\ufffd",
+    "base": null,
+    "href": "https://x/%EF%BF%BD?%EF%BF%BD#%EF%BF%BD",
+    "origin": "https://x",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "x",
+    "hostname": "x",
+    "port": "",
+    "pathname": "/%EF%BF%BD",
+    "search": "?%EF%BF%BD",
+    "hash": "#%EF%BF%BD"
+  },
+  "Domain is ASCII, but a label is invalid IDNA",
+  {
+    "input": "http://a.b.c.xn--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.xn--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  "IDNA labels should be matched case-insensitively",
+  {
+    "input": "http://a.b.c.XN--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a.b.c.Xn--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.XN--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://10.0.0.xN--pokxncvks",
+    "base": null,
+    "failure": true
+  },
+  "Test name prepping, fullwidth input should be converted to ASCII and NOT IDN-ized. This is 'Go' in fullwidth UTF-8/UTF-16.",
+  {
+    "input": "http://Go.com",
+    "base": "http://other.com/",
+    "href": "http://go.com/",
+    "origin": "http://go.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "go.com",
+    "hostname": "go.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "URL spec forbids the following. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24257",
+  {
+    "input": "http://%41.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://%ef%bc%85%ef%bc%94%ef%bc%91.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "...%00 in fullwidth should fail (also as escaped UTF-8 input)",
+  {
+    "input": "http://%00.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://%ef%bc%85%ef%bc%90%ef%bc%90.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "Basic IDN support, UTF-8 and UTF-16 input should be converted to IDN",
+  {
+    "input": "http://你好你好",
+    "base": "http://other.com/",
+    "href": "http://xn--6qqa088eba/",
+    "origin": "http://xn--6qqa088eba",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "xn--6qqa088eba",
+    "hostname": "xn--6qqa088eba",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://faß.ExAmPlE/",
+    "base": null,
+    "href": "https://xn--fa-hia.example/",
+    "origin": "https://xn--fa-hia.example",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "xn--fa-hia.example",
+    "hostname": "xn--fa-hia.example",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://faß.ExAmPlE/",
+    "base": null,
+    "href": "sc://fa%C3%9F.ExAmPlE/",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "fa%C3%9F.ExAmPlE",
+    "hostname": "fa%C3%9F.ExAmPlE",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Invalid escaped characters should fail and the percents should be escaped. https://www.w3.org/Bugs/Public/show_bug.cgi?id=24191",
+  {
+    "input": "http://%zz%66%a.com",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "If we get an invalid character that has been escaped.",
+  {
+    "input": "http://%25",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://hello%00",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "Escaped numbers should be treated like IP addresses if they are.",
+  {
+    "input": "http://%30%78%63%30%2e%30%32%35%30.01",
+    "base": "http://other.com/",
+    "href": "http://192.168.0.1/",
+    "origin": "http://192.168.0.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.0.1",
+    "hostname": "192.168.0.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://%30%78%63%30%2e%30%32%35%30.01%2e",
+    "base": "http://other.com/",
+    "href": "http://192.168.0.1/",
+    "origin": "http://192.168.0.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.0.1",
+    "hostname": "192.168.0.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.168.0.257",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "Invalid escaping in hosts causes failure",
+  {
+    "input": "http://%3g%78%63%30%2e%30%32%35%30%2E.01",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "A space in a host causes failure",
+  {
+    "input": "http://192.168.0.1 hello",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "https://x x:12",
+    "base": null,
+    "failure": true
+  },
+  "Fullwidth and escaped UTF-8 fullwidth should still be treated as IP",
+  {
+    "input": "http://0Xc0.0250.01",
+    "base": "http://other.com/",
+    "href": "http://192.168.0.1/",
+    "origin": "http://192.168.0.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.0.1",
+    "hostname": "192.168.0.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Domains with empty labels",
+  {
+    "input": "http://./",
+    "base": null,
+    "href": "http://./",
+    "origin": "http://.",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": ".",
+    "hostname": ".",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://../",
+    "base": null,
+    "href": "http://../",
+    "origin": "http://..",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "..",
+    "hostname": "..",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Non-special domains with empty labels",
+  {
+    "input": "h://.",
+    "base": null,
+    "href": "h://.",
+    "origin": "null",
+    "protocol": "h:",
+    "username": "",
+    "password": "",
+    "host": ".",
+    "hostname": ".",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  "Broken IPv6",
+  {
+    "input": "http://[www.google.com]/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://[google.com]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.2.3.4x]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.2.3.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.2.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::.1.2]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::1.]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::.1]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://[::%31]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://%5B::1]",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  "Misc Unicode",
+  {
+    "input": "http://foo:💩@example.com/bar",
+    "base": "http://other.com/",
+    "href": "http://foo:%F0%9F%92%A9@example.com/bar",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "foo",
+    "password": "%F0%9F%92%A9",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/bar",
+    "search": "",
+    "hash": ""
+  },
+  "# resolving a fragment against any scheme succeeds",
+  {
+    "input": "#",
+    "base": "test:test",
+    "href": "test:test#",
+    "origin": "null",
+    "protocol": "test:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "#x",
+    "base": "mailto:x@x.com",
+    "href": "mailto:x@x.com#x",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "x@x.com",
+    "search": "",
+    "hash": "#x"
+  },
+  {
+    "input": "#x",
+    "base": "data:,",
+    "href": "data:,#x",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": ",",
+    "search": "",
+    "hash": "#x"
+  },
+  {
+    "input": "#x",
+    "base": "about:blank",
+    "href": "about:blank#x",
+    "origin": "null",
+    "protocol": "about:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "blank",
+    "search": "",
+    "hash": "#x"
+  },
+  {
+    "input": "#x:y",
+    "base": "about:blank",
+    "href": "about:blank#x:y",
+    "origin": "null",
+    "protocol": "about:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "blank",
+    "search": "",
+    "hash": "#x:y"
+  },
+  {
+    "input": "#",
+    "base": "test:test?test",
+    "href": "test:test?test#",
+    "origin": "null",
+    "protocol": "test:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "test",
+    "search": "?test",
+    "hash": ""
+  },
+  "# multiple @ in authority state",
+  {
+    "input": "https://@test@test@example:800/",
+    "base": "http://doesnotmatter/",
+    "href": "https://%40test%40test@example:800/",
+    "origin": "https://example:800",
+    "protocol": "https:",
+    "username": "%40test%40test",
+    "password": "",
+    "host": "example:800",
+    "hostname": "example",
+    "port": "800",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://@@@example",
+    "base": "http://doesnotmatter/",
+    "href": "https://%40%40@example/",
+    "origin": "https://example",
+    "protocol": "https:",
+    "username": "%40%40",
+    "password": "",
+    "host": "example",
+    "hostname": "example",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "non-az-09 characters",
+  {
+    "input": "http://`{}:`{}@h/`{}?`{}",
+    "base": "http://doesnotmatter/",
+    "href": "http://%60%7B%7D:%60%7B%7D@h/%60%7B%7D?`{}",
+    "origin": "http://h",
+    "protocol": "http:",
+    "username": "%60%7B%7D",
+    "password": "%60%7B%7D",
+    "host": "h",
+    "hostname": "h",
+    "port": "",
+    "pathname": "/%60%7B%7D",
+    "search": "?`{}",
+    "hash": ""
+  },
+  "byte is ' and url is special",
+  {
+    "input": "http://host/?'",
+    "base": null,
+    "href": "http://host/?%27",
+    "origin": "http://host",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/",
+    "search": "?%27",
+    "hash": ""
+  },
+  {
+    "input": "notspecial://host/?'",
+    "base": null,
+    "href": "notspecial://host/?'",
+    "origin": "null",
+    "protocol": "notspecial:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/",
+    "search": "?'",
+    "hash": ""
+  },
+  "# Credentials in base",
+  {
+    "input": "/some/path",
+    "base": "http://user@example.org/smth",
+    "href": "http://user@example.org/some/path",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "user",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/some/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "",
+    "base": "http://user:pass@example.org:21/smth",
+    "href": "http://user:pass@example.org:21/smth",
+    "origin": "http://example.org:21",
+    "protocol": "http:",
+    "username": "user",
+    "password": "pass",
+    "host": "example.org:21",
+    "hostname": "example.org",
+    "port": "21",
+    "pathname": "/smth",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/some/path",
+    "base": "http://user:pass@example.org:21/smth",
+    "href": "http://user:pass@example.org:21/some/path",
+    "origin": "http://example.org:21",
+    "protocol": "http:",
+    "username": "user",
+    "password": "pass",
+    "host": "example.org:21",
+    "hostname": "example.org",
+    "port": "21",
+    "pathname": "/some/path",
+    "search": "",
+    "hash": ""
+  },
+  "# a set of tests designed by zcorpan for relative URLs with unknown schemes",
+  {
+    "input": "i",
+    "base": "sc:sd",
+    "failure": true
+  },
+  {
+    "input": "i",
+    "base": "sc:sd/sd",
+    "failure": true
+  },
+  {
+    "input": "i",
+    "base": "sc:/pa/pa",
+    "href": "sc:/pa/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "i",
+    "base": "sc://ho/pa",
+    "href": "sc://ho/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "ho",
+    "hostname": "ho",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "i",
+    "base": "sc:///pa/pa",
+    "href": "sc:///pa/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../i",
+    "base": "sc:sd",
+    "failure": true
+  },
+  {
+    "input": "../i",
+    "base": "sc:sd/sd",
+    "failure": true
+  },
+  {
+    "input": "../i",
+    "base": "sc:/pa/pa",
+    "href": "sc:/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../i",
+    "base": "sc://ho/pa",
+    "href": "sc://ho/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "ho",
+    "hostname": "ho",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "../i",
+    "base": "sc:///pa/pa",
+    "href": "sc:///i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/i",
+    "base": "sc:sd",
+    "failure": true
+  },
+  {
+    "input": "/i",
+    "base": "sc:sd/sd",
+    "failure": true
+  },
+  {
+    "input": "/i",
+    "base": "sc:/pa/pa",
+    "href": "sc:/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/i",
+    "base": "sc://ho/pa",
+    "href": "sc://ho/i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "ho",
+    "hostname": "ho",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/i",
+    "base": "sc:///pa/pa",
+    "href": "sc:///i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/i",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "?i",
+    "base": "sc:sd",
+    "failure": true
+  },
+  {
+    "input": "?i",
+    "base": "sc:sd/sd",
+    "failure": true
+  },
+  {
+    "input": "?i",
+    "base": "sc:/pa/pa",
+    "href": "sc:/pa/pa?i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/pa",
+    "search": "?i",
+    "hash": ""
+  },
+  {
+    "input": "?i",
+    "base": "sc://ho/pa",
+    "href": "sc://ho/pa?i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "ho",
+    "hostname": "ho",
+    "port": "",
+    "pathname": "/pa",
+    "search": "?i",
+    "hash": ""
+  },
+  {
+    "input": "?i",
+    "base": "sc:///pa/pa",
+    "href": "sc:///pa/pa?i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/pa",
+    "search": "?i",
+    "hash": ""
+  },
+  {
+    "input": "#i",
+    "base": "sc:sd",
+    "href": "sc:sd#i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "sd",
+    "search": "",
+    "hash": "#i"
+  },
+  {
+    "input": "#i",
+    "base": "sc:sd/sd",
+    "href": "sc:sd/sd#i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "sd/sd",
+    "search": "",
+    "hash": "#i"
+  },
+  {
+    "input": "#i",
+    "base": "sc:/pa/pa",
+    "href": "sc:/pa/pa#i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/pa",
+    "search": "",
+    "hash": "#i"
+  },
+  {
+    "input": "#i",
+    "base": "sc://ho/pa",
+    "href": "sc://ho/pa#i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "ho",
+    "hostname": "ho",
+    "port": "",
+    "pathname": "/pa",
+    "search": "",
+    "hash": "#i"
+  },
+  {
+    "input": "#i",
+    "base": "sc:///pa/pa",
+    "href": "sc:///pa/pa#i",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pa/pa",
+    "search": "",
+    "hash": "#i"
+  },
+  "# make sure that relative URL logic works on known typically non-relative schemes too",
+  {
+    "input": "about:/../",
+    "base": null,
+    "href": "about:/",
+    "origin": "null",
+    "protocol": "about:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data:/../",
+    "base": null,
+    "href": "data:/",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript:/../",
+    "base": null,
+    "href": "javascript:/",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto:/../",
+    "base": null,
+    "href": "mailto:/",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# unknown schemes and their hosts",
+  {
+    "input": "sc://ñ.test/",
+    "base": null,
+    "href": "sc://%C3%B1.test/",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1.test",
+    "hostname": "%C3%B1.test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://%/",
+    "base": null,
+    "href": "sc://%/",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%",
+    "hostname": "%",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://@/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://te@s:t@/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://:/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://:12/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "x",
+    "base": "sc://ñ",
+    "href": "sc://%C3%B1/x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "/x",
+    "search": "",
+    "hash": ""
+  },
+  "# unknown schemes and backslashes",
+  {
+    "input": "sc:\\../",
+    "base": null,
+    "href": "sc:\\../",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "\\../",
+    "search": "",
+    "hash": ""
+  },
+  "# unknown scheme with path looking like a password",
+  {
+    "input": "sc::a@example.net",
+    "base": null,
+    "href": "sc::a@example.net",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": ":a@example.net",
+    "search": "",
+    "hash": ""
+  },
+  "# unknown scheme with bogus percent-encoding",
+  {
+    "input": "wow:%NBD",
+    "base": null,
+    "href": "wow:%NBD",
+    "origin": "null",
+    "protocol": "wow:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "%NBD",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "wow:%1G",
+    "base": null,
+    "href": "wow:%1G",
+    "origin": "null",
+    "protocol": "wow:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "%1G",
+    "search": "",
+    "hash": ""
+  },
+  "# unknown scheme with non-URL characters",
+  {
+    "input": "wow:\uFFFF",
+    "base": null,
+    "href": "wow:%EF%BF%BF",
+    "origin": "null",
+    "protocol": "wow:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "%EF%BF%BF",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.com/\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF?\uD800\uD801\uDFFE\uDFFF\uFDD0\uFDCF\uFDEF\uFDF0\uFFFE\uFFFF",
+    "base": null,
+    "href": "http://example.com/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF",
+    "origin": "http://example.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "port": "",
+    "pathname": "/%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF",
+    "search": "?%EF%BF%BD%F0%90%9F%BE%EF%BF%BD%EF%B7%90%EF%B7%8F%EF%B7%AF%EF%B7%B0%EF%BF%BE%EF%BF%BF",
+    "hash": ""
+  },
+  "Forbidden host code points",
+  {
+    "input": "sc://a\u0000b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a<b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a>b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a[b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a\\b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a]b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a^b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "sc://a|b/",
+    "base": null,
+    "failure": true
+  },
+  "Forbidden host codepoints: tabs and newlines are removed during preprocessing",
+  {
+    "input": "foo://ho\u0009st/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://ho\u000Ast/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://ho\u000Dst/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"foo://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  "Forbidden domain code-points",
+  {
+    "input": "http://a\u0000b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0001b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0002b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0003b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0004b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0005b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0006b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0007b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0008b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Bb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Cb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Eb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u000Fb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0010b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0011b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0012b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0013b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0014b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0015b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0016b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0017b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0018b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u0019b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Ab/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Bb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Cb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Db/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Eb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u001Fb/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a%b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a<b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a>b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a[b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a]b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a^b",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a|b/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://a\u007Fb/",
+    "base": null,
+    "failure": true
+  },
+  "Forbidden domain codepoints: tabs and newlines are removed during preprocessing",
+  {
+    "input": "http://ho\u0009st/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://ho\u000Ast/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://ho\u000Dst/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href":"http://host/",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  "Encoded forbidden domain codepoints in special URLs",
+  {
+    "input": "http://ho%00st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%01st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%02st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%03st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%04st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%05st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%06st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%07st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%08st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%09st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Ast/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Bst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Cst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Dst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Est/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%0Fst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%10st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%11st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%12st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%13st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%14st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%15st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%16st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%17st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%18st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%19st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Ast/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Bst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Cst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Dst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Est/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%1Fst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%20st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%23st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%25st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%2Fst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Ast/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Cst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Est/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%3Fst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%40st/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Bst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Cst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%5Dst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%7Cst/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://ho%7Fst/",
+    "base": null,
+    "failure": true
+  },
+  "Allowed host/domain code points",
+  {
+    "input": "http://!\"$&'()*+,-.;=_`{}~/",
+    "base": null,
+    "href": "http://!\"$&'()*+,-.;=_`{}~/",
+    "origin": "http://!\"$&'()*+,-.;=_`{}~",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "!\"$&'()*+,-.;=_`{}~",
+    "hostname": "!\"$&'()*+,-.;=_`{}~",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000B\u000C\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u007F!\"$%&'()*+,-.;=_`{}~/",
+    "base": null,
+    "href": "sc://%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~/",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
+    "hostname": "%01%02%03%04%05%06%07%08%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F!\"$%&'()*+,-.;=_`{}~",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# Hosts and percent-encoding",
+  {
+    "input": "ftp://example.com%80/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "ftp://example.com%A0/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://example.com%80/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://example.com%A0/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "ftp://%e2%98%83",
+    "base": null,
+    "href": "ftp://xn--n3h/",
+    "origin": "ftp://xn--n3h",
+    "protocol": "ftp:",
+    "username": "",
+    "password": "",
+    "host": "xn--n3h",
+    "hostname": "xn--n3h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "https://%e2%98%83",
+    "base": null,
+    "href": "https://xn--n3h/",
+    "origin": "https://xn--n3h",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "xn--n3h",
+    "hostname": "xn--n3h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# tests from jsdom/whatwg-url designed for code coverage",
+  {
+    "input": "http://127.0.0.1:10100/relative_import.html",
+    "base": null,
+    "href": "http://127.0.0.1:10100/relative_import.html",
+    "origin": "http://127.0.0.1:10100",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "127.0.0.1:10100",
+    "hostname": "127.0.0.1",
+    "port": "10100",
+    "pathname": "/relative_import.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://facebook.com/?foo=%7B%22abc%22",
+    "base": null,
+    "href": "http://facebook.com/?foo=%7B%22abc%22",
+    "origin": "http://facebook.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "facebook.com",
+    "hostname": "facebook.com",
+    "port": "",
+    "pathname": "/",
+    "search": "?foo=%7B%22abc%22",
+    "hash": ""
+  },
+  {
+    "input": "https://localhost:3000/jqueryui@1.2.3",
+    "base": null,
+    "href": "https://localhost:3000/jqueryui@1.2.3",
+    "origin": "https://localhost:3000",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "localhost:3000",
+    "hostname": "localhost",
+    "port": "3000",
+    "pathname": "/jqueryui@1.2.3",
+    "search": "",
+    "hash": ""
+  },
+  "# tab/LF/CR",
+  {
+    "input": "h\tt\nt\rp://h\to\ns\rt:9\t0\n0\r0/p\ta\nt\rh?q\tu\ne\rry#f\tr\na\rg",
+    "base": null,
+    "href": "http://host:9000/path?query#frag",
+    "origin": "http://host:9000",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "host:9000",
+    "hostname": "host",
+    "port": "9000",
+    "pathname": "/path",
+    "search": "?query",
+    "hash": "#frag"
+  },
+  "# Stringification of URL.searchParams",
+  {
+    "input": "?a=b&c=d",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar?a=b&c=d",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "?a=b&c=d",
+    "searchParams": "a=b&c=d",
+    "hash": ""
+  },
+  {
+    "input": "??a=b&c=d",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar??a=b&c=d",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "??a=b&c=d",
+    "searchParams": "%3Fa=b&c=d",
+    "hash": ""
+  },
+  "# Scheme only",
+  {
+    "input": "http:",
+    "base": "http://example.org/foo/bar",
+    "href": "http://example.org/foo/bar",
+    "origin": "http://example.org",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "searchParams": "",
+    "hash": ""
+  },
+  {
+    "input": "http:",
+    "base": "https://example.org/foo/bar",
+    "failure": true
+  },
+  {
+    "input": "sc:",
+    "base": "https://example.org/foo/bar",
+    "href": "sc:",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "searchParams": "",
+    "hash": ""
+  },
+  "# Percent encoding of fragments",
+  {
+    "input": "http://foo.bar/baz?qux#foo\bbar",
+    "base": null,
+    "href": "http://foo.bar/baz?qux#foo%08bar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%08bar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo\"bar",
+    "base": null,
+    "href": "http://foo.bar/baz?qux#foo%22bar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%22bar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo<bar",
+    "base": null,
+    "href": "http://foo.bar/baz?qux#foo%3Cbar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%3Cbar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo>bar",
+    "base": null,
+    "href": "http://foo.bar/baz?qux#foo%3Ebar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%3Ebar"
+  },
+  {
+    "input": "http://foo.bar/baz?qux#foo`bar",
+    "base": null,
+    "href": "http://foo.bar/baz?qux#foo%60bar",
+    "origin": "http://foo.bar",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "foo.bar",
+    "hostname": "foo.bar",
+    "port": "",
+    "pathname": "/baz",
+    "search": "?qux",
+    "searchParams": "qux=",
+    "hash": "#foo%60bar"
+  },
+  "# IPv4 parsing (via https://github.com/nodejs/node/pull/10317)",
+  {
+    "input": "http://1.2.3.4/",
+    "base": "http://other.com/",
+    "href": "http://1.2.3.4/",
+    "origin": "http://1.2.3.4",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "1.2.3.4",
+    "hostname": "1.2.3.4",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://1.2.3.4./",
+    "base": "http://other.com/",
+    "href": "http://1.2.3.4/",
+    "origin": "http://1.2.3.4",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "1.2.3.4",
+    "hostname": "1.2.3.4",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.168.257",
+    "base": "http://other.com/",
+    "href": "http://192.168.1.1/",
+    "origin": "http://192.168.1.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.1.1",
+    "hostname": "192.168.1.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.168.257.",
+    "base": "http://other.com/",
+    "href": "http://192.168.1.1/",
+    "origin": "http://192.168.1.1",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.1.1",
+    "hostname": "192.168.1.1",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://192.168.257.com",
+    "base": "http://other.com/",
+    "href": "http://192.168.257.com/",
+    "origin": "http://192.168.257.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "192.168.257.com",
+    "hostname": "192.168.257.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://256",
+    "base": "http://other.com/",
+    "href": "http://0.0.1.0/",
+    "origin": "http://0.0.1.0",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0.0.1.0",
+    "hostname": "0.0.1.0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://256.com",
+    "base": "http://other.com/",
+    "href": "http://256.com/",
+    "origin": "http://256.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "256.com",
+    "hostname": "256.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://999999999",
+    "base": "http://other.com/",
+    "href": "http://59.154.201.255/",
+    "origin": "http://59.154.201.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "59.154.201.255",
+    "hostname": "59.154.201.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://999999999.",
+    "base": "http://other.com/",
+    "href": "http://59.154.201.255/",
+    "origin": "http://59.154.201.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "59.154.201.255",
+    "hostname": "59.154.201.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://999999999.com",
+    "base": "http://other.com/",
+    "href": "http://999999999.com/",
+    "origin": "http://999999999.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "999999999.com",
+    "hostname": "999999999.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://10000000000",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://10000000000.com",
+    "base": "http://other.com/",
+    "href": "http://10000000000.com/",
+    "origin": "http://10000000000.com",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "10000000000.com",
+    "hostname": "10000000000.com",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://4294967295",
+    "base": "http://other.com/",
+    "href": "http://255.255.255.255/",
+    "origin": "http://255.255.255.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "255.255.255.255",
+    "hostname": "255.255.255.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://4294967296",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://0xffffffff",
+    "base": "http://other.com/",
+    "href": "http://255.255.255.255/",
+    "origin": "http://255.255.255.255",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "255.255.255.255",
+    "hostname": "255.255.255.255",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0xffffffff1",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "https://0x.0x.0",
+    "base": null,
+    "href": "https://0.0.0.0/",
+    "origin": "https://0.0.0.0",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "0.0.0.0",
+    "hostname": "0.0.0.0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "More IPv4 parsing (via https://github.com/jsdom/whatwg-url/issues/92)",
+  {
+    "input": "https://0x100000000/test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://256.0.0.1/test",
+    "base": null,
+    "failure": true
+  },
+  "# file URLs containing percent-encoded Windows drive letters (shouldn't work)",
+  {
+    "input": "file:///C%3A/",
+    "base": null,
+    "href": "file:///C%3A/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C%3A/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///C%7C/",
+    "base": null,
+    "href": "file:///C%7C/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C%7C/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://%43%3A",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://%43%7C",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://%43|",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://C%7C",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://%43%7C/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://%43%7C/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "asdf://%43|/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "asdf://%43%7C/",
+    "base": null,
+    "href": "asdf://%43%7C/",
+    "origin": "null",
+    "protocol": "asdf:",
+    "username": "",
+    "password": "",
+    "host": "%43%7C",
+    "hostname": "%43%7C",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# file URLs relative to other file URLs (via https://github.com/jsdom/whatwg-url/pull/60)",
+  {
+    "input": "pix/submit.gif",
+    "base": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/anchor.html",
+    "href": "file:///C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/Users/Domenic/Dropbox/GitHub/tmpvar/jsdom/test/level2/html/files/pix/submit.gif",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///C:/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# More file URL tests by zcorpan and annevk",
+  {
+    "input": "/",
+    "base": "file:///C:/a/b",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/",
+    "base": "file://h/C:/a/b",
+    "href": "file://h/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "h",
+    "hostname": "h",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/",
+    "base": "file://h/a/b",
+    "href": "file://h/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "h",
+    "hostname": "h",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//d:",
+    "base": "file:///C:/a/b",
+    "href": "file:///d:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/d:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//d:/..",
+    "base": "file:///C:/a/b",
+    "href": "file:///d:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/d:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///ab:/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..",
+    "base": "file:///1:/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": ""
+  },
+  {
+    "input": "file:",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": ""
+  },
+  {
+    "input": "?x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "file:?x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "#x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test#x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": "#x"
+  },
+  {
+    "input": "file:#x",
+    "base": "file:///test?test#test",
+    "href": "file:///test?test#x",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?test",
+    "hash": "#x"
+  },
+  "# File URLs and many (back)slashes",
+  {
+    "input": "file:\\\\//",
+    "base": null,
+    "href": "file:////",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:\\\\\\\\",
+    "base": null,
+    "href": "file:////",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:\\\\\\\\?fox",
+    "base": null,
+    "href": "file:////?fox",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "?fox",
+    "hash": ""
+  },
+  {
+    "input": "file:\\\\\\\\#guppy",
+    "base": null,
+    "href": "file:////#guppy",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": "#guppy"
+  },
+  {
+    "input": "file://spider///",
+    "base": null,
+    "href": "file://spider///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "spider",
+    "hostname": "spider",
+    "port": "",
+    "pathname": "///",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:\\\\localhost//",
+    "base": null,
+    "href": "file:////",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///localhost//cat",
+    "base": null,
+    "href": "file:///localhost//cat",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/localhost//cat",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://\\/localhost//cat",
+    "base": null,
+    "href": "file:////localhost//cat",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//localhost//cat",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost//a//../..//",
+    "base": null,
+    "href": "file://///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "///",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/////mouse",
+    "base": "file:///elephant",
+    "href": "file://///mouse",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "///mouse",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\//pig",
+    "base": "file://lion/",
+    "href": "file:///pig",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/pig",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\/localhost//pig",
+    "base": "file://lion/",
+    "href": "file:////pig",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//pig",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//localhost//pig",
+    "base": "file://lion/",
+    "href": "file:////pig",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//pig",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/..//localhost//pig",
+    "base": "file://lion/",
+    "href": "file://lion//localhost//pig",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "lion",
+    "hostname": "lion",
+    "port": "",
+    "pathname": "//localhost//pig",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://",
+    "base": "file://ape/",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "# File URLs with non-empty hosts",
+  {
+    "input": "/rooibos",
+    "base": "file://tea/",
+    "href": "file://tea/rooibos",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "tea",
+    "hostname": "tea",
+    "port": "",
+    "pathname": "/rooibos",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/?chai",
+    "base": "file://tea/",
+    "href": "file://tea/?chai",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "tea",
+    "hostname": "tea",
+    "port": "",
+    "pathname": "/",
+    "search": "?chai",
+    "hash": ""
+  },
+  "# Windows drive letter handling with the 'file:' base URL",
+  {
+    "input": "C|",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|",
+    "base": "file://host/D:/dir1/dir2/file",
+    "href": "file://host/C:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|#",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:#",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|?",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:?",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|/",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|\n/",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|\\",
+    "base": "file://host/dir/file",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C",
+    "base": "file://host/dir/file",
+    "href": "file://host/dir/C",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/dir/C",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "C|a",
+    "base": "file://host/dir/file",
+    "href": "file://host/dir/C|a",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/dir/C|a",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter quirk in the file slash state",
+  {
+    "input": "/c:/foo/bar",
+    "base": "file:///c:/baz/qux",
+    "href": "file:///c:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/c|/foo/bar",
+    "base": "file:///c:/baz/qux",
+    "href": "file:///c:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:\\c:\\foo\\bar",
+    "base": "file:///c:/baz/qux",
+    "href": "file:///c:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/c:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/c:/foo/bar",
+    "base": "file://host/path",
+    "href": "file://host/c:/foo/bar",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/c:/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  "# Do not drop the host in the presence of a drive letter",
+  {
+    "input": "file://example.net/C:/",
+    "base": null,
+    "href": "file://example.net/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "example.net",
+    "hostname": "example.net",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://1.2.3.4/C:/",
+    "base": null,
+    "href": "file://1.2.3.4/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "1.2.3.4",
+    "hostname": "1.2.3.4",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://[1::8]/C:/",
+    "base": null,
+    "href": "file://[1::8]/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "[1::8]",
+    "hostname": "[1::8]",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# Copy the host from the base URL in the following cases",
+  {
+    "input": "C|/",
+    "base": "file://host/",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/C:/",
+    "base": "file://host/",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:C:/",
+    "base": "file://host/",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:/C:/",
+    "base": "file://host/",
+    "href": "file://host/C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "host",
+    "hostname": "host",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# Copy the empty host from the input in the following cases",
+  {
+    "input": "//C:/",
+    "base": "file://host/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://C:/",
+    "base": "file://host/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///C:/",
+    "base": "file://host/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///C:/",
+    "base": "file://host/",
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# Windows drive letter quirk (no host)",
+  {
+    "input": "file:/C|/",
+    "base": null,
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://C|/",
+    "base": null,
+    "href": "file:///C:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/C:/",
+    "search": "",
+    "hash": ""
+  },
+  "# file URLs without base URL by Rimas Misevičius",
+  {
+    "input": "file:",
+    "base": null,
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:?q=v",
+    "base": null,
+    "href": "file:///?q=v",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "?q=v",
+    "hash": ""
+  },
+  {
+    "input": "file:#frag",
+    "base": null,
+    "href": "file:///#frag",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": "#frag"
+  },
+  "# file: drive letter cases from https://crbug.com/1078698",
+  {
+    "input": "file:///Y:",
+    "base": null,
+    "href": "file:///Y:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/Y:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///Y:/",
+    "base": null,
+    "href": "file:///Y:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/Y:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///./Y",
+    "base": null,
+    "href": "file:///Y",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/Y",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///./Y:",
+    "base": null,
+    "href": "file:///Y:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/Y:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\\\\\.\\Y:",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  "# file: drive letter cases from https://crbug.com/1078698 but lowercased",
+  {
+    "input": "file:///y:",
+    "base": null,
+    "href": "file:///y:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/y:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///y:/",
+    "base": null,
+    "href": "file:///y:/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/y:/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///./y",
+    "base": null,
+    "href": "file:///y",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/y",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///./y:",
+    "base": null,
+    "href": "file:///y:",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/y:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "\\\\\\.\\y:",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  "# Additional file URL tests for (https://github.com/whatwg/url/issues/405)",
+  {
+    "input": "file://localhost//a//../..//foo",
+    "base": null,
+    "href": "file://///foo",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "///foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://localhost////foo",
+    "base": null,
+    "href": "file://////foo",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "////foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:////foo",
+    "base": null,
+    "href": "file:////foo",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//foo",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///one/two",
+    "base": "file:///",
+    "href": "file:///one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/one/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:////one/two",
+    "base": "file:///",
+    "href": "file:////one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//one/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//one/two",
+    "base": "file:///",
+    "href": "file://one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "one",
+    "hostname": "one",
+    "port": "",
+    "pathname": "/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///one/two",
+    "base": "file:///",
+    "href": "file:///one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/one/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "////one/two",
+    "base": "file:///",
+    "href": "file:////one/two",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//one/two",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:///.//",
+    "base": "file:////",
+    "href": "file:////",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  "File URL tests for https://github.com/whatwg/url/issues/549",
+  {
+    "input": "file:.//p",
+    "base": null,
+    "href": "file:////p",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//p",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file:/.//p",
+    "base": null,
+    "href": "file:////p",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//p",
+    "search": "",
+    "hash": ""
+  },
+  "# IPv6 tests",
+  {
+    "input": "http://[1:0::]",
+    "base": "http://example.net/",
+    "href": "http://[1::]/",
+    "origin": "http://[1::]",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[1::]",
+    "hostname": "[1::]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[0:1:2:3:4:5:6:7:8]",
+    "base": "http://example.net/",
+    "failure": true
+  },
+  {
+    "input": "https://[0::0::0]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:.0]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:0:]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:1:2:3:4:5:6:7.0.0.0.1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.00.0.0.0]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.290.0.0.0]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://[0:1.23.23]",
+    "base": null,
+    "failure": true
+  },
+  "# Empty host",
+  {
+    "input": "http://?",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://#",
+    "base": null,
+    "failure": true
+  },
+  "Port overflow (2^32 + 81)",
+  {
+    "input": "http://f:4294967377/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "Port overflow (2^64 + 81)",
+  {
+    "input": "http://f:18446744073709551697/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "Port overflow (2^128 + 81)",
+  {
+    "input": "http://f:340282366920938463463374607431768211537/c",
+    "base": "http://example.org/",
+    "failure": true
+  },
+  "# Non-special-URL path tests",
+  {
+    "input": "sc://ñ",
+    "base": null,
+    "href": "sc://%C3%B1",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://ñ?x",
+    "base": null,
+    "href": "sc://%C3%B1?x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "sc://ñ#x",
+    "base": null,
+    "href": "sc://%C3%B1#x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": "#x"
+  },
+  {
+    "input": "#x",
+    "base": "sc://ñ",
+    "href": "sc://%C3%B1#x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": "#x"
+  },
+  {
+    "input": "?x",
+    "base": "sc://ñ",
+    "href": "sc://%C3%B1?x",
+    "origin": "null",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "%C3%B1",
+    "hostname": "%C3%B1",
+    "port": "",
+    "pathname": "",
+    "search": "?x",
+    "hash": ""
+  },
+  {
+    "input": "sc://?",
+    "base": null,
+    "href": "sc://?",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "sc://#",
+    "base": null,
+    "href": "sc://#",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///",
+    "base": "sc://x/",
+    "href": "sc:///",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "////",
+    "base": "sc://x/",
+    "href": "sc:////",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "////x/",
+    "base": "sc://x/",
+    "href": "sc:////x/",
+    "protocol": "sc:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//x/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "tftp://foobar.com/someconfig;mode=netascii",
+    "base": null,
+    "href": "tftp://foobar.com/someconfig;mode=netascii",
+    "origin": "null",
+    "protocol": "tftp:",
+    "username": "",
+    "password": "",
+    "host": "foobar.com",
+    "hostname": "foobar.com",
+    "port": "",
+    "pathname": "/someconfig;mode=netascii",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "telnet://user:pass@foobar.com:23/",
+    "base": null,
+    "href": "telnet://user:pass@foobar.com:23/",
+    "origin": "null",
+    "protocol": "telnet:",
+    "username": "user",
+    "password": "pass",
+    "host": "foobar.com:23",
+    "hostname": "foobar.com",
+    "port": "23",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "ut2004://10.10.10.10:7777/Index.ut2",
+    "base": null,
+    "href": "ut2004://10.10.10.10:7777/Index.ut2",
+    "origin": "null",
+    "protocol": "ut2004:",
+    "username": "",
+    "password": "",
+    "host": "10.10.10.10:7777",
+    "hostname": "10.10.10.10",
+    "port": "7777",
+    "pathname": "/Index.ut2",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+    "base": null,
+    "href": "redis://foo:bar@somehost:6379/0?baz=bam&qux=baz",
+    "origin": "null",
+    "protocol": "redis:",
+    "username": "foo",
+    "password": "bar",
+    "host": "somehost:6379",
+    "hostname": "somehost",
+    "port": "6379",
+    "pathname": "/0",
+    "search": "?baz=bam&qux=baz",
+    "hash": ""
+  },
+  {
+    "input": "rsync://foo@host:911/sup",
+    "base": null,
+    "href": "rsync://foo@host:911/sup",
+    "origin": "null",
+    "protocol": "rsync:",
+    "username": "foo",
+    "password": "",
+    "host": "host:911",
+    "hostname": "host",
+    "port": "911",
+    "pathname": "/sup",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "git://github.com/foo/bar.git",
+    "base": null,
+    "href": "git://github.com/foo/bar.git",
+    "origin": "null",
+    "protocol": "git:",
+    "username": "",
+    "password": "",
+    "host": "github.com",
+    "hostname": "github.com",
+    "port": "",
+    "pathname": "/foo/bar.git",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "irc://myserver.com:6999/channel?passwd",
+    "base": null,
+    "href": "irc://myserver.com:6999/channel?passwd",
+    "origin": "null",
+    "protocol": "irc:",
+    "username": "",
+    "password": "",
+    "host": "myserver.com:6999",
+    "hostname": "myserver.com",
+    "port": "6999",
+    "pathname": "/channel",
+    "search": "?passwd",
+    "hash": ""
+  },
+  {
+    "input": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+    "base": null,
+    "href": "dns://fw.example.org:9999/foo.bar.org?type=TXT",
+    "origin": "null",
+    "protocol": "dns:",
+    "username": "",
+    "password": "",
+    "host": "fw.example.org:9999",
+    "hostname": "fw.example.org",
+    "port": "9999",
+    "pathname": "/foo.bar.org",
+    "search": "?type=TXT",
+    "hash": ""
+  },
+  {
+    "input": "ldap://localhost:389/ou=People,o=JNDITutorial",
+    "base": null,
+    "href": "ldap://localhost:389/ou=People,o=JNDITutorial",
+    "origin": "null",
+    "protocol": "ldap:",
+    "username": "",
+    "password": "",
+    "host": "localhost:389",
+    "hostname": "localhost",
+    "port": "389",
+    "pathname": "/ou=People,o=JNDITutorial",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "git+https://github.com/foo/bar",
+    "base": null,
+    "href": "git+https://github.com/foo/bar",
+    "origin": "null",
+    "protocol": "git+https:",
+    "username": "",
+    "password": "",
+    "host": "github.com",
+    "hostname": "github.com",
+    "port": "",
+    "pathname": "/foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "urn:ietf:rfc:2648",
+    "base": null,
+    "href": "urn:ietf:rfc:2648",
+    "origin": "null",
+    "protocol": "urn:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "ietf:rfc:2648",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "tag:joe@example.org,2001:foo/bar",
+    "base": null,
+    "href": "tag:joe@example.org,2001:foo/bar",
+    "origin": "null",
+    "protocol": "tag:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "joe@example.org,2001:foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  "Serialize /. in path",
+  {
+    "input": "non-spec:/.//",
+    "base": null,
+    "href": "non-spec:/.//",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-spec:/..//",
+    "base": null,
+    "href": "non-spec:/.//",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-spec:/a/..//",
+    "base": null,
+    "href": "non-spec:/.//",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-spec:/.//path",
+    "base": null,
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-spec:/..//path",
+    "base": null,
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-spec:/a/..//path",
+    "base": null,
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/.//path",
+    "base": "non-spec:/p",
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/..//path",
+    "base": "non-spec:/p",
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "..//path",
+    "base": "non-spec:/p",
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "a/..//path",
+    "base": "non-spec:/p",
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "",
+    "base": "non-spec:/..//p",
+    "href": "non-spec:/.//p",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//p",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "path",
+    "base": "non-spec:/..//p",
+    "href": "non-spec:/.//path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "//path",
+    "search": "",
+    "hash": ""
+  },
+  "Do not serialize /. in path",
+  {
+    "input": "../path",
+    "base": "non-spec:/.//p",
+    "href": "non-spec:/path",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  "# percent encoded hosts in non-special-URLs",
+  {
+    "input": "non-special://%E2%80%A0/",
+    "base": null,
+    "href": "non-special://%E2%80%A0/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "%E2%80%A0",
+    "hostname": "%E2%80%A0",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://H%4fSt/path",
+    "base": null,
+    "href": "non-special://H%4fSt/path",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "H%4fSt",
+    "hostname": "H%4fSt",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  "# IPv6 in non-special-URLs",
+  {
+    "input": "non-special://[1:2:0:0:5:0:0:0]/",
+    "base": null,
+    "href": "non-special://[1:2:0:0:5::]/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2:0:0:5::]",
+    "hostname": "[1:2:0:0:5::]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[1:2:0:0:0:0:0:3]/",
+    "base": null,
+    "href": "non-special://[1:2::3]/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2::3]",
+    "hostname": "[1:2::3]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[1:2::3]:80/",
+    "base": null,
+    "href": "non-special://[1:2::3]:80/",
+    "protocol": "non-special:",
+    "username": "",
+    "password": "",
+    "host": "[1:2::3]:80",
+    "hostname": "[1:2::3]",
+    "port": "80",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "non-special://[:80/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "blob:https://example.com:443/",
+    "base": null,
+    "href": "blob:https://example.com:443/",
+    "origin": "https://example.com",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "https://example.com:443/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:http://example.org:88/",
+    "base": null,
+    "href": "blob:http://example.org:88/",
+    "origin": "http://example.org:88",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "http://example.org:88/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "base": null,
+    "href": "blob:d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "d3958f5c-0777-0845-9dcf-2cb28783acaf",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:",
+    "base": null,
+    "href": "blob:",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  "blob: in blob:",
+  {
+    "input": "blob:blob:",
+    "base": null,
+    "href": "blob:blob:",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "blob:",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:blob:https://example.org/",
+    "base": null,
+    "href": "blob:blob:https://example.org/",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "blob:https://example.org/",
+    "search": "",
+    "hash": ""
+  },
+  "Non-http(s): in blob:",
+  {
+    "input": "blob:about:blank",
+    "base": null,
+    "href": "blob:about:blank",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "about:blank",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:file://host/path",
+    "base": null,
+    "href": "blob:file://host/path",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "file://host/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:ftp://host/path",
+    "base": null,
+    "href": "blob:ftp://host/path",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "ftp://host/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:ws://example.org/",
+    "base": null,
+    "href": "blob:ws://example.org/",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "ws://example.org/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "blob:wss://example.org/",
+    "base": null,
+    "href": "blob:wss://example.org/",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "wss://example.org/",
+    "search": "",
+    "hash": ""
+  },
+  "Percent-encoded http: in blob:",
+  {
+    "input": "blob:http%3a//example.org/",
+    "base": null,
+    "href": "blob:http%3a//example.org/",
+    "origin": "null",
+    "protocol": "blob:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "http%3a//example.org/",
+    "search": "",
+    "hash": ""
+  },
+  "Invalid IPv4 radix digits",
+  {
+    "input": "http://0x7f.0.0.0x7g",
+    "base": null,
+    "href": "http://0x7f.0.0.0x7g/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://0X7F.0.0.0X7G",
+    "base": null,
+    "href": "http://0x7f.0.0.0x7g/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "0x7f.0.0.0x7g",
+    "hostname": "0x7f.0.0.0x7g",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Invalid IPv4 portion of IPv6 address",
+  {
+    "input": "http://[::127.0.0.0.1]",
+    "base": null,
+    "failure": true
+  },
+  "Uncompressed IPv6 addresses with 0",
+  {
+    "input": "http://[0:1:0:1:0:1:0:1]",
+    "base": null,
+    "href": "http://[0:1:0:1:0:1:0:1]/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[0:1:0:1:0:1:0:1]",
+    "hostname": "[0:1:0:1:0:1:0:1]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://[1:0:1:0:1:0:1:0]",
+    "base": null,
+    "href": "http://[1:0:1:0:1:0:1:0]/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "[1:0:1:0:1:0:1:0]",
+    "hostname": "[1:0:1:0:1:0:1:0]",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  "Percent-encoded query and fragment",
+  {
+    "input": "http://example.org/test?\u0022",
+    "base": null,
+    "href": "http://example.org/test?%22",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%22",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u0023",
+    "base": null,
+    "href": "http://example.org/test?#",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u003C",
+    "base": null,
+    "href": "http://example.org/test?%3C",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%3C",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u003E",
+    "base": null,
+    "href": "http://example.org/test?%3E",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%3E",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?\u2323",
+    "base": null,
+    "href": "http://example.org/test?%E2%8C%A3",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%E2%8C%A3",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?%23%23",
+    "base": null,
+    "href": "http://example.org/test?%23%23",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%23%23",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?%GH",
+    "base": null,
+    "href": "http://example.org/test?%GH",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?%GH",
+    "hash": ""
+  },
+  {
+    "input": "http://example.org/test?a#%EF",
+    "base": null,
+    "href": "http://example.org/test?a#%EF",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#%EF"
+  },
+  {
+    "input": "http://example.org/test?a#%GH",
+    "base": null,
+    "href": "http://example.org/test?a#%GH",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#%GH"
+  },
+  "URLs that require a non-about:blank base. (Also serve as invalid base tests.)",
+  {
+    "input": "a",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "a/",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "a//",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  "Bases that don't fail to parse but fail to be bases",
+  {
+    "input": "test-a-colon.html",
+    "base": "a:",
+    "failure": true
+  },
+  {
+    "input": "test-a-colon-b.html",
+    "base": "a:b",
+    "failure": true
+  },
+  "Other base URL tests, that must succeed",
+  {
+    "input": "test-a-colon-slash.html",
+    "base": "a:/",
+    "href": "a:/test-a-colon-slash.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-slash-slash.html",
+    "base": "a://",
+    "href": "a:///test-a-colon-slash-slash.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash-slash.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-slash-b.html",
+    "base": "a:/b",
+    "href": "a:/test-a-colon-slash-b.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test-a-colon-slash-b.html",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "test-a-colon-slash-slash-b.html",
+    "base": "a://b",
+    "href": "a://b/test-a-colon-slash-slash-b.html",
+    "protocol": "a:",
+    "username": "",
+    "password": "",
+    "host": "b",
+    "hostname": "b",
+    "port": "",
+    "pathname": "/test-a-colon-slash-slash-b.html",
+    "search": "",
+    "hash": ""
+  },
+  "Null code point in fragment",
+  {
+    "input": "http://example.org/test?a#b\u0000c",
+    "base": null,
+    "href": "http://example.org/test?a#b%00c",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#b%00c"
+  },
+  {
+    "input": "non-spec://example.org/test?a#b\u0000c",
+    "base": null,
+    "href": "non-spec://example.org/test?a#b%00c",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#b%00c"
+  },
+  {
+    "input": "non-spec:/test?a#b\u0000c",
+    "base": null,
+    "href": "non-spec:/test?a#b%00c",
+    "protocol": "non-spec:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "?a",
+    "hash": "#b%00c"
+  },
+  "First scheme char - not allowed: https://github.com/whatwg/url/issues/464",
+  {
+    "input": "10.0.0.7:8080/foo.html",
+    "base": "file:///some/dir/bar.html",
+    "href": "file:///some/dir/10.0.0.7:8080/foo.html",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/some/dir/10.0.0.7:8080/foo.html",
+    "search": "",
+    "hash": ""
+  },
+  "Subsequent scheme chars - not allowed",
+  {
+    "input": "a!@$*=/foo.html",
+    "base": "file:///some/dir/bar.html",
+    "href": "file:///some/dir/a!@$*=/foo.html",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/some/dir/a!@$*=/foo.html",
+    "search": "",
+    "hash": ""
+  },
+  "First and subsequent scheme chars - allowed",
+  {
+    "input": "a1234567890-+.:foo/bar",
+    "base": "http://example.com/dir/file",
+    "href": "a1234567890-+.:foo/bar",
+    "protocol": "a1234567890-+.:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "foo/bar",
+    "search": "",
+    "hash": ""
+  },
+  "IDNA ignored code points in file URLs hosts",
+  {
+    "input": "file://a\u00ADb/p",
+    "base": null,
+    "href": "file://ab/p",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "ab",
+    "hostname": "ab",
+    "port": "",
+    "pathname": "/p",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "file://a%C2%ADb/p",
+    "base": null,
+    "href": "file://ab/p",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "ab",
+    "hostname": "ab",
+    "port": "",
+    "pathname": "/p",
+    "search": "",
+    "hash": ""
+  },
+  "IDNA hostnames which get mapped to 'localhost'",
+  {
+    "input": "file://loC𝐀𝐋𝐇𝐨𝐬𝐭/usr/bin",
+    "base": null,
+    "href": "file:///usr/bin",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/usr/bin",
+    "search": "",
+    "hash": ""
+  },
+  "Empty host after the domain to ASCII",
+  {
+    "input": "file://\u00ad/p",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://%C2%AD/p",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "file://xn--/p",
+    "base": null,
+    "failure": true
+  },
+  "https://bugzilla.mozilla.org/show_bug.cgi?id=1647058",
+  {
+    "input": "#link",
+    "base": "https://example.org/##link",
+    "href": "https://example.org/#link",
+    "protocol": "https:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": "#link"
+  },
+  "UTF-8 percent-encode of C0 control percent-encode set and supersets",
+  {
+    "input": "non-special:cannot-be-a-base-url-\u0000\u0001\u001F\u001E\u007E\u007F\u0080",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+    "origin": "null",
+    "password": "",
+    "pathname": "cannot-be-a-base-url-%00%01%1F%1E~%7F%C2%80",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://www.example.com/path{\u007Fpath.html?query'\u007F=query#fragment<\u007Ffragment",
+    "base": null,
+    "hash": "#fragment%3C%7Ffragment",
+    "host": "www.example.com",
+    "hostname": "www.example.com",
+    "href": "https://www.example.com/path%7B%7Fpath.html?query%27%7F=query#fragment%3C%7Ffragment",
+    "origin": "https://www.example.com",
+    "password": "",
+    "pathname": "/path%7B%7Fpath.html",
+    "port": "",
+    "protocol": "https:",
+    "search": "?query%27%7F=query",
+    "username": ""
+  },
+  {
+    "input": "https://user:pass[\u007F@foo/bar",
+    "base": "http://example.org",
+    "hash": "",
+    "host": "foo",
+    "hostname": "foo",
+    "href": "https://user:pass%5B%7F@foo/bar",
+    "origin": "https://foo",
+    "password": "pass%5B%7F",
+    "pathname": "/bar",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": "user"
+  },
+  "Tests for the distinct percent-encode sets",
+  {
+    "input": "foo:// !\"$%&'()*+,-.;<=>@[\\]^_`{|}~@host/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "null",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~"
+  },
+  {
+    "input": "wss:// !\"$%&'()*+,-.;<=>@[]^_`{|}~@host/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": "%20!%22$%&'()*+,-.%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~"
+  },
+  {
+    "input": "foo://joe: !\"$%&'()*+,-.:;<=>@[\\]^_`{|}~@host/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "null",
+    "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5C%5D%5E_%60%7B%7C%7D~",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": "joe"
+  },
+  {
+    "input": "wss://joe: !\"$%&'()*+,-.:;<=>@[]^_`{|}~@host/",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://joe:%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~@host/",
+    "origin": "wss://host",
+    "password": "%20!%22$%&'()*+,-.%3A%3B%3C%3D%3E%40%5B%5D%5E_%60%7B%7C%7D~",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": "joe"
+  },
+  {
+    "input": "foo://!\"$%&'()*+,-.;=_`{}~/",
+    "base": null,
+    "hash": "",
+    "host": "!\"$%&'()*+,-.;=_`{}~",
+    "hostname": "!\"$%&'()*+,-.;=_`{}~",
+    "href":"foo://!\"$%&'()*+,-.;=_`{}~/",
+    "origin": "null",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://!\"$&'()*+,-.;=_`{}~/",
+    "base": null,
+    "hash": "",
+    "host": "!\"$&'()*+,-.;=_`{}~",
+    "hostname": "!\"$&'()*+,-.;=_`{}~",
+    "href":"wss://!\"$&'()*+,-.;=_`{}~/",
+    "origin": "wss://!\"$&'()*+,-.;=_`{}~",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[\\]^_%60%7B|%7D~",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://host/ !\"$%&'()*+,-./:;<=>@[\\]^_`{|}~",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/%20!%22$%&'()*+,-./:;%3C=%3E@[/]^_%60%7B|%7D~",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "foo://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/dir/?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "?%20!%22$%&'()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "username": ""
+  },
+  {
+    "input": "wss://host/dir/? !\"$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": null,
+    "hash": "",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/dir/?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "?%20!%22$%&%27()*+,-./:;%3C=%3E?@[\\]^_`{|}~",
+    "username": ""
+  },
+  {
+    "input": "foo://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": null,
+    "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "host": "host",
+    "hostname": "host",
+    "href": "foo://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "origin": "null",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "foo:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "wss://host/dir/# !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+    "base": null,
+    "hash": "#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "host": "host",
+    "hostname": "host",
+    "href": "wss://host/dir/#%20!%22#$%&'()*+,-./:;%3C=%3E?@[\\]^_%60{|}~",
+    "origin": "wss://host",
+    "password": "",
+    "pathname": "/dir/",
+    "port":"",
+    "protocol": "wss:",
+    "search": "",
+    "username": ""
+  },
+  "Ensure that input schemes are not ignored when resolving non-special URLs",
+  {
+    "input": "abc:rootless",
+    "base": "abc://host/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:rootless",
+    "base": "abc:/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:rootless",
+    "base": "abc:path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:rootless",
+    "password": "",
+    "pathname": "rootless",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "abc:/rooted",
+    "base": "abc://host/path",
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href":"abc:/rooted",
+    "password": "",
+    "pathname": "/rooted",
+    "port":"",
+    "protocol": "abc:",
+    "search": "",
+    "username": ""
+  },
+  "Empty query and fragment with blank should throw an error",
+  {
+    "input": "#",
+    "base": null,
+    "failure": true,
+    "relativeTo": "any-base"
+  },
+  {
+    "input": "?",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  "Last component looks like a number, but not valid IPv4",
+  {
+    "input": "http://1.2.3.4.5",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.4.5.",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://0..0x300/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://0..0x300./",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256.256",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://256.256.256.256.256.",
+    "base": "http://other.com/",
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.08",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.08.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://1.2.3.09",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://09.2.3.4",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://09.2.3.4.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://01.2.3.4.5",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://01.2.3.4.5.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://0x100.2.3.4",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://0x100.2.3.4.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://0x1.2.3.4.5",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://0x1.2.3.4.5.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.1.2.3.4",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.1.2.3.4.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.2.3.4",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.2.3.4.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.09",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.09.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x4",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x4.",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.09..",
+    "base": null,
+    "hash": "",
+    "host": "foo.09..",
+    "hostname": "foo.09..",
+    "href":"http://foo.09../",
+    "password": "",
+    "pathname": "/",
+    "port":"",
+    "protocol": "http:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "http://0999999999999999999/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.0x",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://foo.0XFfFfFfFfFfFfFfFfFfAcE123",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "http://💩.123/",
+    "base": null,
+    "failure": true
+  },
+  "U+0000 and U+FFFF in various places",
+  {
+    "input": "https://\u0000y",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://x/\u0000y",
+    "base": null,
+    "hash": "",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/%00y",
+    "password": "",
+    "pathname": "/%00y",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://x/?\u0000y",
+    "base": null,
+    "hash": "",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/?%00y",
+    "password": "",
+    "pathname": "/",
+    "port": "",
+    "protocol": "https:",
+    "search": "?%00y",
+    "username": ""
+  },
+  {
+    "input": "https://x/?#\u0000y",
+    "base": null,
+    "hash": "#%00y",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/?#%00y",
+    "password": "",
+    "pathname": "/",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://\uFFFFy",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://x/\uFFFFy",
+    "base": null,
+    "hash": "",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/%EF%BF%BFy",
+    "password": "",
+    "pathname": "/%EF%BF%BFy",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://x/?\uFFFFy",
+    "base": null,
+    "hash": "",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/?%EF%BF%BFy",
+    "password": "",
+    "pathname": "/",
+    "port": "",
+    "protocol": "https:",
+    "search": "?%EF%BF%BFy",
+    "username": ""
+  },
+  {
+    "input": "https://x/?#\uFFFFy",
+    "base": null,
+    "hash": "#%EF%BF%BFy",
+    "host": "x",
+    "hostname": "x",
+    "href": "https://x/?#%EF%BF%BFy",
+    "password": "",
+    "pathname": "/",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:\u0000y",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:%00y",
+    "password": "",
+    "pathname": "%00y",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/\u0000y",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/%00y",
+    "password": "",
+    "pathname": "x/%00y",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/?\u0000y",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/?%00y",
+    "password": "",
+    "pathname": "x/",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "?%00y",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/?#\u0000y",
+    "base": null,
+    "hash": "#%00y",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/?#%00y",
+    "password": "",
+    "pathname": "x/",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:\uFFFFy",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:%EF%BF%BFy",
+    "password": "",
+    "pathname": "%EF%BF%BFy",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/\uFFFFy",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/%EF%BF%BFy",
+    "password": "",
+    "pathname": "x/%EF%BF%BFy",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/?\uFFFFy",
+    "base": null,
+    "hash": "",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/?%EF%BF%BFy",
+    "password": "",
+    "pathname": "x/",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "?%EF%BF%BFy",
+    "username": ""
+  },
+  {
+    "input": "non-special:x/?#\uFFFFy",
+    "base": null,
+    "hash": "#%EF%BF%BFy",
+    "host": "",
+    "hostname": "",
+    "href": "non-special:x/?#%EF%BF%BFy",
+    "password": "",
+    "pathname": "x/",
+    "port": "",
+    "protocol": "non-special:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "",
+    "base": null,
+    "failure": true,
+    "relativeTo": "non-opaque-path-base"
+  },
+  {
+    "input": "https://example.com/\"quoted\"",
+    "base": null,
+    "hash": "",
+    "host": "example.com",
+    "hostname": "example.com",
+    "href": "https://example.com/%22quoted%22",
+    "origin": "https://example.com",
+    "password": "",
+    "pathname": "/%22quoted%22",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "input": "https://a%C2%ADb/",
+    "base": null,
+    "hash": "",
+    "host": "ab",
+    "hostname": "ab",
+    "href": "https://ab/",
+    "origin": "https://ab",
+    "password": "",
+    "pathname": "/",
+    "port": "",
+    "protocol": "https:",
+    "search": "",
+    "username": ""
+  },
+  {
+    "comment": "Empty host after domain to ASCII",
+    "input": "https://\u00AD/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://%C2%AD/",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "https://xn--/",
+    "base": null,
+    "failure": true
+  },
+  "Non-special schemes that some implementations might incorrectly treat as special",
+  {
+    "input": "data://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "data://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "data:///test",
+    "base": null,
+    "href": "data:///test",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data://test/a/../b",
+    "base": null,
+    "href": "data://test/b",
+    "origin": "null",
+    "protocol": "data:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "data://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "data://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "data://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "javascript://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "javascript://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "javascript:///test",
+    "base": null,
+    "href": "javascript:///test",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript://test/a/../b",
+    "base": null,
+    "href": "javascript://test/b",
+    "origin": "null",
+    "protocol": "javascript:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "javascript://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "javascript://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "javascript://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "mailto://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "mailto://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "mailto:///test",
+    "base": null,
+    "href": "mailto:///test",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto://test/a/../b",
+    "base": null,
+    "href": "mailto://test/b",
+    "origin": "null",
+    "protocol": "mailto:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "mailto://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "mailto://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "mailto://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "intent://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "intent://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "intent:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "intent:///test",
+    "base": null,
+    "href": "intent:///test",
+    "origin": "null",
+    "protocol": "intent:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "intent://test/a/../b",
+    "base": null,
+    "href": "intent://test/b",
+    "origin": "null",
+    "protocol": "intent:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "intent://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "intent://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "intent://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "urn://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "urn://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "urn:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "urn:///test",
+    "base": null,
+    "href": "urn:///test",
+    "origin": "null",
+    "protocol": "urn:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "urn://test/a/../b",
+    "base": null,
+    "href": "urn://test/b",
+    "origin": "null",
+    "protocol": "urn:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "urn://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "urn://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "urn://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "turn://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "turn://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "turn:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "turn:///test",
+    "base": null,
+    "href": "turn:///test",
+    "origin": "null",
+    "protocol": "turn:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "turn://test/a/../b",
+    "base": null,
+    "href": "turn://test/b",
+    "origin": "null",
+    "protocol": "turn:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "turn://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "turn://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "turn://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "stun://example.com:8080/pathname?search#hash",
+    "base": null,
+    "href": "stun://example.com:8080/pathname?search#hash",
+    "origin": "null",
+    "protocol": "stun:",
+    "username": "",
+    "password": "",
+    "host": "example.com:8080",
+    "hostname": "example.com",
+    "port": "8080",
+    "pathname": "/pathname",
+    "search": "?search",
+    "hash": "#hash"
+  },
+  {
+    "input": "stun:///test",
+    "base": null,
+    "href": "stun:///test",
+    "origin": "null",
+    "protocol": "stun:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/test",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "stun://test/a/../b",
+    "base": null,
+    "href": "stun://test/b",
+    "origin": "null",
+    "protocol": "stun:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/b",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "stun://:443",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "stun://test:test",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "stun://[:1]",
+    "base": null,
+    "failure": true
+  },
+  {
+    "input": "w://x:0",
+    "base": null,
+    "href": "w://x:0",
+    "origin": "null",
+    "protocol": "w:",
+    "username": "",
+    "password": "",
+    "host": "x:0",
+    "hostname": "x",
+    "port": "0",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "west://x:0",
+    "base": null,
+    "href": "west://x:0",
+    "origin": "null",
+    "protocol": "west:",
+    "username": "",
+    "password": "",
+    "host": "x:0",
+    "hostname": "x",
+    "port": "0",
+    "pathname": "",
+    "search": "",
+    "hash": ""
+  },
+  "Scheme relative path starting with multiple slashes",
+  {
+    "input": "///test",
+    "base": "http://example.org/",
+    "href": "http://test/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///\\//\\//test",
+    "base": "http://example.org/",
+    "href": "http://test/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "test",
+    "hostname": "test",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///example.org/path",
+    "base": "http://example.org/",
+    "href": "http://example.org/path",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///example.org/../path",
+    "base": "http://example.org/",
+    "href": "http://example.org/path",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///example.org/../../",
+    "base": "http://example.org/",
+    "href": "http://example.org/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///example.org/../path/../../",
+    "base": "http://example.org/",
+    "href": "http://example.org/",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///example.org/../path/../../path",
+    "base": "http://example.org/",
+    "href": "http://example.org/path",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/\\/\\//example.org/../path",
+    "base": "http://example.org/",
+    "href": "http://example.org/path",
+    "protocol": "http:",
+    "username": "",
+    "password": "",
+    "host": "example.org",
+    "hostname": "example.org",
+    "port": "",
+    "pathname": "/path",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "///abcdef/../",
+    "base": "file:///",
+    "href": "file:///",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "/\\//\\/a/../",
+    "base": "file:///",
+    "href": "file://////",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "",
+    "hostname": "",
+    "port": "",
+    "pathname": "////",
+    "search": "",
+    "hash": ""
+  },
+  {
+    "input": "//a/../",
+    "base": "file:///",
+    "href": "file://a/",
+    "protocol": "file:",
+    "username": "",
+    "password": "",
+    "host": "a",
+    "hostname": "a",
+    "port": "",
+    "pathname": "/",
+    "search": "",
+    "hash": ""
+  }
+]
diff --git a/tests/wpt/verifydnslength_tests.json b/tests/wpt/verifydnslength_tests.json
new file mode 100644 (file)
index 0000000..a8c36df
--- /dev/null
@@ -0,0 +1,88 @@
+[
+  "Tests for the verify_dns_length function. See the section 2.3.4 of https://www.ietf.org/rfc/rfc1035.txt .",
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 1",
+    "input": "http://lorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlore.com",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 2",
+    "input": "http://br.lorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlore.org/",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 3",
+    "input": "http://exampleexampleexampleexampleexampleexampleexampleexampleexampleexample.lorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlore.org.br/",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://br.exampleexampleexampleexampleexampleexampleexampleexampleexampleexample.lorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlorenlore",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://example..com.br",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://example.com.br..",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://example.com.br....",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://.example.com.br",
+    "failure": true
+  },
+  {
+    "message": "A domain label must be a mininum of 1 character and a maximum of 63 characters. 4",
+    "input": "http://example.com..br",
+    "failure": true
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 1",
+    "input": "http://example.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br",
+    "failure": true
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 4",
+    "input": "http://example.com.",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 2",
+    "input": "http://example.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.comm.br.",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 3",
+    "input": "http://example.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.comm.br",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 3",
+    "input": "http://example.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.com.br.comm.br.",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 6",
+    "input": "http://aaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaa.com",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 6",
+    "input": "http://example.com.",
+    "failure": false
+  },
+  {
+    "message": "A domain name must be a maximum of 253 characters (or 254 if there is a dot at the end). 6",
+    "input": "http://example.com./",
+    "failure": false
+  }
+]
diff --git a/tests/wpt_tests.cpp b/tests/wpt_tests.cpp
new file mode 100644 (file)
index 0000000..eadf058
--- /dev/null
@@ -0,0 +1,477 @@
+#include <cstring>
+#include <filesystem>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+
+#include "gtest/gtest.h"
+#include "ada.h"
+#include "ada/character_sets-inl.h"
+#include "ada/parser.h"
+#include "ada/url.h"
+#include "ada/url_aggregator.h"
+
+// This function copies your input onto a memory buffer that
+// has just the necessary size. This will entice tools to detect
+// an out-of-bound access.
+template <class result_type = ada::url_aggregator>
+ada::result<result_type> ada_parse(std::string_view view,
+                                   const result_type *base = nullptr) {
+  std::cout << "about to parse '" << view << "' [" << view.size() << " bytes]"
+            << std::endl;
+  std::unique_ptr<char[]> buffer(new char[view.size()]);
+  memcpy(buffer.get(), view.data(), view.size());
+  return ada::parse<result_type>(std::string_view(buffer.get(), view.size()),
+                                 base);
+}
+
+template ada::result<ada::url> ada_parse(std::string_view view,
+                                         const ada::url *base);
+template ada::result<ada::url_aggregator> ada_parse(
+    std::string_view view, const ada::url_aggregator *base);
+
+#include "simdjson.h"
+
+using namespace simdjson;
+
+#ifndef WPT_DATA_DIR
+#define WPT_DATA_DIR "wpt/"
+#endif
+const char *PERCENT_ENCODING_JSON = WPT_DATA_DIR "percent-encoding.json";
+const char *SETTERS_TESTS_JSON = WPT_DATA_DIR "setters_tests.json";
+const char *ADA_SETTERS_TESTS_JSON =
+    WPT_DATA_DIR "ada_extra_setters_tests.json";
+const char *TOASCII_JSON = WPT_DATA_DIR "toascii.json";
+const char *IDNA_TEST_V2 = WPT_DATA_DIR "IdnaTestV2.json";
+const char *URLTESTDATA_JSON = WPT_DATA_DIR "urltestdata.json";
+const char *ADA_URLTESTDATA_JSON = WPT_DATA_DIR "ada_extra_urltestdata.json";
+const char *VERIFYDNSLENGTH_TESTS_JSON =
+    WPT_DATA_DIR "verifydnslength_tests.json";
+
+using Types = testing::Types<ada::url, ada::url_aggregator>;
+template <class T>
+struct wpt_tests_typed : testing::Test {};
+TYPED_TEST_SUITE(wpt_tests_typed, Types);
+
+std::stringstream error_buffer;
+
+bool file_exists(const char *filename) {
+  namespace fs = std::filesystem;
+  std::filesystem::path f{filename};
+  if (std::filesystem::exists(filename)) {
+    std::cout << "  file found: " << filename << std::endl;
+    return true;
+  } else {
+    std::cerr << "  file missing: " << filename << std::endl;
+    error_buffer << "  file missing: " << filename << std::endl;
+    return false;
+  }
+}
+
+TEST(wpt_tests, idna_test_v2_to_ascii) {
+  ondemand::parser parser;
+  ASSERT_TRUE(file_exists(IDNA_TEST_V2));
+  padded_string json = padded_string::load(IDNA_TEST_V2);
+  ondemand::document doc = parser.iterate(json);
+  try {
+    for (auto element : doc.get_array()) {
+      if (element.type() == ondemand::json_type::string) {
+        continue;
+      }
+
+      ondemand::object object = element.get_object();
+      auto json_string =
+          std::string(std::string_view(simdjson::to_json_string(object)));
+      std::string_view input = object["input"].get_string();
+
+      std::optional<std::string> output;
+      ada::unicode::to_ascii(output, input, input.find('%'));
+      auto expected_output = object["output"];
+      auto given_output = output.has_value() ? output.value() : "";
+
+      if (expected_output.is_null()) {
+        ASSERT_EQ(given_output, "");
+      } else if (expected_output.type() == ondemand::json_type::string) {
+        std::string_view str_expected_output = expected_output.get_string();
+        ASSERT_EQ(str_expected_output, given_output);
+      }
+    }
+  } catch (simdjson::simdjson_error &error) {
+    std::cerr << "JSON error: " << error.what() << " near "
+              << doc.current_location() << " in " << TOASCII_JSON << std::endl;
+    FAIL();
+  }
+  SUCCEED();
+}
+
+TEST(wpt_tests, percent_encoding) {
+  ondemand::parser parser;
+  size_t counter{0};
+
+  ASSERT_TRUE(file_exists(PERCENT_ENCODING_JSON));
+  padded_string json = padded_string::load(PERCENT_ENCODING_JSON);
+  ondemand::document doc = parser.iterate(json);
+  try {
+    for (auto element : doc.get_array()) {
+      if (element.type() == ondemand::json_type::string) {
+        std::cout << "   comment: " << element.get_string() << std::endl;
+      } else if (element.type() == ondemand::json_type::object) {
+        ondemand::object object = element.get_object();
+        std::string element_string(std::string_view(object.raw_json()));
+        object.reset();
+
+        // We might want to decode the strings into UTF-8, but some of the
+        // strings are not always valid UTF-8 (e.g., you have unmatched
+        // surrogates which are forbidden by the UTF-8 spec).
+        auto input_element = object["input"];
+        std::string_view input;
+        // Try UTF-8.
+        bool allow_replacement_characters = true;
+        EXPECT_FALSE(
+            input_element.get_string(allow_replacement_characters).get(input));
+        std::string my_input_encoded = ada::unicode::percent_encode(
+            input, ada::character_sets::QUERY_PERCENT_ENCODE);
+        ondemand::object outputs = object["output"].get_object();
+        std::string_view expected_view;
+        ASSERT_FALSE(outputs["utf-8"].get(expected_view));
+        ASSERT_EQ(my_input_encoded, expected_view);
+        counter++;
+      }
+    }
+  } catch (simdjson::simdjson_error &error) {
+    std::cerr << "JSON error: " << error.what() << " near "
+              << doc.current_location() << " in " << TOASCII_JSON << std::endl;
+    FAIL();
+  }
+  std::cout << "Tests executed = " << counter << std::endl;
+  SUCCEED();
+}
+
+TYPED_TEST(wpt_tests_typed, setters_tests_encoding) {
+  for (auto source : {SETTERS_TESTS_JSON, ADA_SETTERS_TESTS_JSON}) {
+    ondemand::parser parser;
+    ASSERT_TRUE(file_exists(source));
+    padded_string json = padded_string::load(source);
+    ondemand::document doc = parser.iterate(json);
+    try {
+      ondemand::object main_object = doc.get_object();
+
+      for (auto mainfield : main_object) {
+        auto category = mainfield.key().value();
+        ondemand::array cases = mainfield.value();
+
+        if (category == "comment") {
+          continue;
+        } else {
+          std::cout << "  " << category << ":" << std::endl;
+        }
+
+        for (auto element_value : cases) {
+          ondemand::object element = element_value;
+          std::string element_string =
+              std::string(std::string_view(element.raw_json()));
+          element.reset();
+          std::string_view new_value = element["new_value"].get_string();
+          std::string_view href = element["href"];
+          std::string_view comment{};
+          if (!element["comment"].get(comment)) {
+            std::cout << "    comment: " << comment << std::endl;
+          }
+
+          auto base = ada_parse<TypeParam>(href);
+          ASSERT_TRUE(base.has_value());
+          if constexpr (std::is_same<ada::url_aggregator, TypeParam>::value) {
+            ASSERT_TRUE(base->validate());
+            element_string += "\n" + base->to_diagram() + "\n";
+          }
+
+          std::cout << "      " << href << std::endl;
+
+          if (category == "protocol") {
+            std::string_view expected = element["expected"]["protocol"];
+            base->set_protocol(new_value);
+            ASSERT_EQ(base->get_protocol(), expected);
+          } else if (category == "username") {
+            std::string_view expected = element["expected"]["username"];
+            base->set_username(new_value);
+            ASSERT_EQ(base->get_username(), expected);
+          } else if (category == "password") {
+            std::string_view expected = element["expected"]["password"];
+            base->set_password(new_value);
+            ASSERT_EQ(base->get_password(), expected);
+          } else if (category == "host") {
+            std::string_view expected;
+
+            // We only support valid UTF-8 cases.
+            if (!element["expected"]["host"].get(expected)) {
+              base->set_host(new_value);
+              ASSERT_EQ(base->get_host(), expected);
+            }
+          } else if (category == "hostname") {
+            std::string_view expected;
+
+            // TODO: Handle invalid utf-8 tests too.
+            if (!element["expected"]["hostname"].get(expected)) {
+              base->set_hostname(new_value);
+              ASSERT_EQ(base->get_hostname(), expected);
+            }
+          } else if (category == "port") {
+            std::string_view expected = element["expected"]["port"];
+            base->set_port(new_value);
+            ASSERT_EQ(base->get_port(), expected);
+          } else if (category == "pathname") {
+            std::string_view expected = element["expected"]["pathname"];
+            base->set_pathname(new_value);
+            ASSERT_EQ(base->get_pathname(), expected);
+          } else if (category == "search") {
+            std::string_view expected = element["expected"]["search"];
+            base->set_search(new_value);
+            ASSERT_EQ(base->get_search(), expected);
+
+            std::string_view expected_pathname;
+            if (!element["expected"]["pathname"].get(expected_pathname)) {
+              ASSERT_EQ(base->get_pathname(), expected_pathname);
+            }
+          } else if (category == "hash") {
+            std::string_view expected = element["expected"]["hash"];
+            base->set_hash(new_value);
+            ASSERT_EQ(base->get_hash(), expected);
+          } else if (category == "href") {
+            std::string_view expected = element["expected"]["href"];
+            base->set_href(new_value);
+            ASSERT_TRUE(base->set_href(new_value));
+            ASSERT_EQ(base->get_href(), expected);
+          }
+        }
+      }
+    } catch (simdjson::simdjson_error &error) {
+      std::cerr << "JSON error: " << error.what() << " near "
+                << doc.current_location() << " in " << source << std::endl;
+      FAIL();
+    }
+  }
+  SUCCEED();
+}
+
+TYPED_TEST(wpt_tests_typed, toascii_encoding) {
+  ondemand::parser parser;
+  ASSERT_TRUE(file_exists(TOASCII_JSON));
+  padded_string json = padded_string::load(TOASCII_JSON);
+  ondemand::document doc = parser.iterate(json);
+  try {
+    for (auto element : doc.get_array()) {
+      if (element.type() == ondemand::json_type::string) {
+        std::cout << "   comment: " << element.get_string() << std::endl;
+      } else if (element.type() == ondemand::json_type::object) {
+        ondemand::object object = element.get_object();
+        auto element_string =
+            std::string(std::string_view(simdjson::to_json_string(object)));
+
+        std::string_view input = object["input"];
+        std::optional<std::string> output;
+        ada::unicode::to_ascii(output, input, input.find('%'));
+        auto expected_output = object["output"];
+
+        // The following code replicates `toascii.window.js` from web-platform
+        // tests.
+        // @see
+        // https://github.com/web-platform-tests/wpt/blob/master/url/toascii.window.js
+        auto current =
+            ada::parse<TypeParam>("https://" + std::string(input) + "/x");
+
+        if (expected_output.type() == ondemand::json_type::string) {
+          std::string_view stringified_output = expected_output.get_string();
+          ASSERT_EQ(current->get_host(), stringified_output);
+          ASSERT_EQ(current->get_hostname(), stringified_output);
+          ASSERT_EQ(current->get_pathname(), "/x");
+          ASSERT_EQ(current->get_href(),
+                    "https://" + std::string(stringified_output) + "/x");
+        } else if (expected_output.is_null()) {
+          ASSERT_FALSE(current.has_value());
+        }
+
+        // Test setters for host and hostname values.
+        auto setter = ada::parse<TypeParam>("https://x/x");
+        ASSERT_EQ(setter->set_host(input), !expected_output.is_null());
+        ASSERT_EQ(setter->set_hostname(input), !expected_output.is_null());
+
+        if (expected_output.type() == ondemand::json_type::string) {
+          std::string_view stringified_output = expected_output.get_string();
+          ASSERT_EQ(setter->get_host(), stringified_output);
+          ASSERT_EQ(setter->get_hostname(), stringified_output);
+        } else if (expected_output.is_null()) {
+          // host and hostname should not be updated if the input is invalid.
+          ASSERT_EQ(setter->get_host(), "x");
+          ASSERT_EQ(setter->get_hostname(), "x");
+        }
+      }
+    }
+  } catch (simdjson::simdjson_error &error) {
+    std::cerr << "JSON error: " << error.what() << " near "
+              << doc.current_location() << " in " << TOASCII_JSON << std::endl;
+    FAIL();
+  }
+  SUCCEED();
+}
+
+TYPED_TEST(wpt_tests_typed, urltestdata_encoding) {
+  for (auto source : {URLTESTDATA_JSON, ADA_URLTESTDATA_JSON}) {
+    ondemand::parser parser;
+    size_t counter{};
+    ASSERT_TRUE(file_exists(source));
+    padded_string json = padded_string::load(source);
+    ondemand::document doc = parser.iterate(json);
+    try {
+      for (auto element : doc.get_array()) {
+        if (element.type() == ondemand::json_type::string) {
+          std::string_view comment = element.get_string().value();
+          std::cout << comment << std::endl;
+        } else if (element.type() == ondemand::json_type::object) {
+          ondemand::object object = element.get_object();
+          std::string element_string =
+              std::string(std::string_view(object.raw_json()));
+          object.reset();
+
+          std::string_view input{};
+          bool allow_replacement_characters = true;
+          ASSERT_FALSE(object["input"]
+                           .get_string(allow_replacement_characters)
+                           .get(input));
+          std::cout << "input='" << input << "' [" << input.size() << " bytes]"
+                    << std::endl;
+          std::string_view base;
+          ada::result<TypeParam> base_url;
+          if (!object["base"].get(base)) {
+            std::cout << "base=" << base << std::endl;
+            base_url = ada_parse<TypeParam>(base);
+            if (!base_url) {
+              bool failure = false;
+              if (!object["failure"].get(failure) && failure == true) {
+                // We are good. Failure was expected.
+                continue;  // We can't proceed any further.
+              } else {
+                ASSERT_TRUE(base_url.has_value());
+              }
+            }
+          }
+          bool failure = false;
+          auto input_url = (!object["base"].get(base))
+                               ? ada_parse<TypeParam>(input, &*base_url)
+                               : ada_parse<TypeParam>(input);
+          if (!object["failure"].get(failure) && failure == true) {
+            ASSERT_EQ(input_url.has_value(), !failure);
+          } else {
+            ASSERT_TRUE(input_url.has_value());
+            // Next we test the 'to_string' method.
+            if constexpr (std::is_same<ada::url_aggregator, TypeParam>::value) {
+              ASSERT_TRUE(input_url->validate());
+            }
+            std::string parsed_url_json = input_url->to_string();
+            if constexpr (std::is_same<ada::url_aggregator, TypeParam>::value) {
+              std::cout << "\n====\n" + input_url->to_diagram() + "\n====\n";
+            }
+            std::string_view protocol = object["protocol"].get_string();
+            ASSERT_EQ(input_url->get_protocol(), protocol);
+
+            std::string_view username = object["username"].get_string();
+            ASSERT_EQ(input_url->get_username(), username);
+
+            std::string_view password = object["password"].get_string();
+            ASSERT_EQ(input_url->get_password(), password);
+
+            std::string_view host = object["host"].get_string();
+            ASSERT_EQ(input_url->get_host(), host);
+
+            std::string_view hostname = object["hostname"].get_string();
+            ASSERT_EQ(input_url->get_hostname(), hostname);
+
+            std::string_view port = object["port"].get_string();
+            ASSERT_EQ(input_url->get_port(), port);
+
+            std::string_view pathname = object["pathname"].get_string();
+            ASSERT_EQ(input_url->get_pathname(), pathname);
+
+            std::string_view search = object["search"].get_string();
+            ASSERT_EQ(input_url->get_search(), search);
+
+            std::string_view hash = object["hash"].get_string();
+            ASSERT_EQ(input_url->get_hash(), hash);
+
+            std::string_view href = object["href"].get_string();
+            ASSERT_EQ(input_url->get_href(), href);
+
+            // The origin key may be missing. In that case, the API's origin
+            // attribute is not tested.
+            std::string_view origin;
+            if (!object["origin"].get(origin)) {
+              ASSERT_EQ(input_url->get_origin(), origin);
+            }
+
+            // We need padding.
+            simdjson::padded_string padded_url_json = parsed_url_json;
+            // We need a second parser.
+            ondemand::parser urlparser;
+            ondemand::document parsed_doc = urlparser.iterate(padded_url_json);
+            std::cout << "serialized JSON = " << padded_url_json << std::endl;
+            ondemand::object parsed_object = parsed_doc.get_object();
+            std::string_view json_recovered_path;
+            if (parsed_object["path"].get_string().get(json_recovered_path)) {
+              if constexpr (std::is_same<ada::url, TypeParam>::value) {
+                std::cerr << "The serialized url instance does not provide a "
+                             "'path' key or the JSON is invalid."
+                          << std::endl;
+                FAIL();
+              }
+            } else {
+              ASSERT_EQ(json_recovered_path, pathname);
+            }
+            counter++;
+          }
+        }
+      }
+    } catch (simdjson::simdjson_error &error) {
+      std::cerr << "JSON error: " << error.what() << " near "
+                << doc.current_location() << " in " << source << std::endl;
+      FAIL();
+    }
+    std::cout << "Tests executed = " << counter << std::endl;
+  }
+  SUCCEED();
+}
+
+TEST(wpt_tests, verify_dns_length) {
+  const char *source = VERIFYDNSLENGTH_TESTS_JSON;
+  size_t counter{};
+  ondemand::parser parser;
+  ASSERT_TRUE(file_exists(source));
+  padded_string json = padded_string::load(source);
+  ondemand::document doc = parser.iterate(json);
+  try {
+    for (auto element : doc.get_array()) {
+      if (element.type() == ondemand::json_type::string) {
+        std::string_view comment = element.get_string();
+        std::cout << comment << std::endl;
+      } else if (element.type() == ondemand::json_type::object) {
+        ondemand::object object = element.get_object();
+        std::string element_string =
+            std::string(std::string_view(object.raw_json()));
+        object.reset();
+        std::string_view input = object["input"].get_string();
+        std::string message =
+            std::string(object["message"].get_string().value());
+        bool failure = object["failure"].get_bool().value();
+        ada::result<ada::url> input_url = ada_parse<ada::url>(input);
+        ASSERT_EQ(!input_url->has_valid_domain(), failure);
+        counter++;
+      }
+    }
+  } catch (simdjson::simdjson_error &error) {
+    std::cerr << "JSON error: " << error.what() << " near "
+              << doc.current_location() << " in " << source << std::endl;
+    FAIL();
+  }
+  std::cout << "Tests executed = " << counter << std::endl;
+  SUCCEED();
+}
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644 (file)
index 0000000..92c73ec
--- /dev/null
@@ -0,0 +1 @@
+add_subdirectory(cli)
\ No newline at end of file
diff --git a/tools/cli/CMakeLists.txt b/tools/cli/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ff57220
--- /dev/null
@@ -0,0 +1,29 @@
+add_executable(adaparse adaparse.cpp line_iterator.h)
+target_link_libraries(adaparse PRIVATE ada)
+target_include_directories(adaparse PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
+if(MSVC AND BUILD_SHARED_LIBS)
+  # Copy the ada dll into the directory
+  add_custom_command(TARGET adaparse POST_BUILD        # Adds a post-build event
+    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake -E copy_if_different..."
+        "$<TARGET_FILE:ada>"      # <--this is in-file
+        "$<TARGET_FILE_DIR:adaparse>")                 # <--this is out-file path
+endif()
+CPMAddPackage("gh:fmtlib/fmt#7.1.3")
+CPMAddPackage(
+  GITHUB_REPOSITORY jarro2783/cxxopts
+  VERSION 3.1.1
+  OPTIONS "CXXOPTS_BUILD_EXAMPLES NO" "CXXOPTS_BUILD_TESTS NO" "CXXOPTS_ENABLE_INSTALL YES"
+)
+target_link_libraries(adaparse PRIVATE cxxopts::cxxopts fmt::fmt)
+
+if(MSVC OR MINGW)
+  target_compile_definitions(adaparse PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
+endif()
+
+install(
+   TARGETS
+    adaparse
+   ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+   LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+   RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+)
\ No newline at end of file
diff --git a/tools/cli/adaparse.cpp b/tools/cli/adaparse.cpp
new file mode 100644 (file)
index 0000000..6496943
--- /dev/null
@@ -0,0 +1,292 @@
+#include <chrono>
+#include <cxxopts.hpp>
+#include <fstream>
+#include <fmt/os.h>
+
+#include "ada.h"
+#include "line_iterator.h"
+#ifdef _MSC_VER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+uint64_t nano() {
+  return std::chrono::duration_cast<::std::chrono::nanoseconds>(
+             std::chrono::steady_clock::now().time_since_epoch())
+      .count();
+}
+
+template <typename Callable>
+bool print_part(Callable&& fmt_or_adaparse_print, std::string_view get_part,
+                const ada::url_aggregator& url) {
+  if (get_part.size() == 4) {
+    if (get_part[0] == 'h') {
+      if (get_part == "href") {
+        fmt_or_adaparse_print("{}\n", url.get_href());
+        return true;
+      } else if (get_part == "host") {
+        fmt_or_adaparse_print("{}\n", url.get_host());
+        return true;
+      } else if (get_part == "hash") {
+        fmt_or_adaparse_print("{}\n", url.get_hash());
+        return true;
+      }
+    } else if (get_part[0] == 'p') {
+      if (get_part == "port") {
+        fmt_or_adaparse_print("{}\n", url.get_port());
+        return true;
+      }
+    }
+  } else if (get_part.size() == 6) {
+    if (get_part == "origin") {
+      fmt_or_adaparse_print("{}\n", url.get_origin());
+      return true;
+    }
+    if (get_part == "search") {
+      fmt_or_adaparse_print("{}\n", url.get_search());
+      return true;
+    }
+  } else if (get_part.size() == 8) {
+    if (get_part[0] == 'p') {
+      if (get_part == "protocol") {
+        fmt_or_adaparse_print("{}\n", url.get_protocol());
+        return true;
+      } else if (get_part == "password") {
+        fmt_or_adaparse_print("{}\n", url.get_password());
+        return true;
+      } else if (get_part == "pathname") {
+        fmt_or_adaparse_print("{}\n", url.get_pathname());
+        return true;
+      }
+    } else if (get_part == "hostname") {
+      fmt_or_adaparse_print("{}\n", url.get_hostname());
+      return true;
+    }
+  } else if (get_part == "username") {
+    fmt_or_adaparse_print("{}\n", url.get_username());
+    return true;
+  }
+
+  fmt::print(stderr, "\"{}\" not found\n", get_part);
+  return false;
+}
+
+// This function parses a FILE * descriptor, line by line (URL by URL).It
+// applies the given Callable (in this case the lambda adaparse_print in the
+// main() function) to print the output either to a file on disk or to the
+// console, depending on arguments given.
+// It also optionally will output a benchmark to the console.
+template <typename Callable>
+int piped_file(Callable&& adaparse_print, const cxxopts::ParseResult result,
+               FILE* input_file) {
+  constexpr size_t cache_length = 32768;
+  std::unique_ptr<char[]> cachebuffer(new char[cache_length]{});
+
+  uint64_t before = nano();
+
+  size_t total_bytes_read{0};
+  size_t bytes_read_this_loop_iteration{0};
+  size_t offset{0};
+  size_t lines{0};
+  size_t blocks{0};
+  std::string get_part{};
+  if (result.count("get")) {
+    get_part = result["get"].as<std::string>();
+  }
+
+  // Get the file descriptor from the FILE * input_file
+#ifdef _MSC_VER
+  int input_fd = _fileno(input_file);
+#else
+  int input_fd = fileno(input_file);
+#endif
+
+#ifdef _MSC_VER
+  while ((bytes_read_this_loop_iteration =
+              _read(input_fd, cachebuffer.get() + offset,
+                    (unsigned int)(cache_length - offset)))) {
+#else
+  while ((bytes_read_this_loop_iteration = read(
+              input_fd, cachebuffer.get() + offset, cache_length - offset))) {
+#endif
+    total_bytes_read += bytes_read_this_loop_iteration;
+    blocks++;
+    size_t capacity = bytes_read_this_loop_iteration + offset;
+    line_iterator li(cachebuffer.get(), capacity);
+
+    while (li.find_another_complete_line()) {
+      std::string_view line = li.grab_line();
+
+      auto url = ada::parse<ada::url_aggregator>(line);
+      if (!url) {
+        adaparse_print("Invalid URL: {}\n", line);
+      } else if (!get_part.empty()) {
+        print_part(adaparse_print, get_part, url.value());
+      }
+
+      lines++;
+    }
+    if ((offset = li.tail()) > 0) {
+      memmove(cachebuffer.get(), cachebuffer.get() + capacity - offset, offset);
+    }
+  }
+  if (offset > 0) {
+    // have a line of length offset at cachebuffer.get()
+    std::string_view line(cachebuffer.get(), offset);
+
+    auto url = ada::parse<ada::url_aggregator>(line);
+    if (!url) {
+      adaparse_print("Invalid URL:{}\n", line);
+    } else if (!get_part.empty()) {
+      print_part(adaparse_print, get_part, url.value());
+    }
+
+    lines++;
+  }
+
+  if (result.count("benchmark")) {
+    uint64_t after = nano();
+    double giga = total_bytes_read / 1000000000.;
+
+    fmt::print(
+        "read {} bytes in {} ns using {} lines, used {} "
+        "loads\n",
+        total_bytes_read, (after - before), lines, blocks);
+
+    double seconds = (after - before) / 1000000000.;
+    double speed = giga / seconds;
+    fmt::print("{} GB/s\n", speed);
+  }
+
+  return EXIT_SUCCESS;
+}
+
+/**
+ * @private
+ *
+ * Running this executable, you can quickly test ada:
+ *
+ * $ adaparse "http://www.google.com/bal?a==11#fddfds"
+ * {
+ *       "buffer":"http://www.google.com/bal?a==11#fddfds",
+ *       "protocol":"http:",
+ *       "host":"www.google.com",
+ *       "path":"/bal",
+ *       "opaque path":false,
+ *       "query":"?a==11",
+ *       "fragment":"#fddfds",
+ *       "protocol_end":5,
+ *       "username_end":7,
+ *       "host_start":7,
+ *       "host_end":21,
+ *       "port":null,
+ *       "pathname_start":21,
+ *       "search_start":25,
+ *       "hash_start":31
+ * }
+ *
+ * $ ./buildbench/tools/adaparse -d http://www.google.com/bal\?a\=\=11\#fddfds
+ * http://www.google.com/bal?a==11#fddfds [38 bytes]
+ *      | |             |   |     |
+ *      | |             |   |     `------ hash_start
+ *      | |             |   `------------ search_start 25
+ *      | |             `---------------- pathname_start 21
+ *      | |             `---------------- host_end 21
+ *      | `------------------------------ host_start 7
+ *      | `------------------------------ username_end 7
+ *      `-------------------------------- protocol_end 5
+ **/
+
+int main(int argc, char** argv) {
+  std::ios::sync_with_stdio(false);
+
+  cxxopts::Options options("adaparse",
+                           "Command-line version of the Ada URL parser");
+
+  // clang-format off
+  options.add_options()
+    ("d,diagram", "Print a diagram of the result", cxxopts::value<bool>()->default_value("false"))
+    ("u,url", "URL", cxxopts::value<std::string>())
+    ("g,get", "Get a specific part of the URL (e.g., 'origin', 'host', etc.)",  cxxopts::value<std::string>())
+    ("b,benchmark", "Display chronometer for piped_file function", cxxopts::value<bool>()->default_value("false"))
+    ("p,path", "Takes in a path to a file and process all the URL within", cxxopts::value<std::string>())
+    ("o,output", "Takes in a path and outputs to a text file.", cxxopts::value<std::string>()->default_value("/dev/null"))
+    ("h,help", "Print usage");
+  // clang-format on
+
+  options.parse_positional({"url"});
+
+  auto result = options.parse(argc, argv);
+
+  std::string output_filename = result["output"].as<std::string>();
+  auto out = fmt::output_file(output_filename);
+  bool has_result = result.count("output");
+
+  auto adaparse_print = [has_result, &out](const std::string& format_str,
+                                           auto&&... args) {
+    std::string formatted_str =
+        fmt::format(format_str, std::forward<decltype(args)>(args)...);
+    if (has_result) {
+      out.print(formatted_str);
+    } else {
+      fmt::print("{}", formatted_str);
+    }
+  };
+
+#ifdef _MSC_VER
+  if (!_isatty(_fileno(stdin))) {
+#else
+  if (!isatty(fileno(stdin))) {
+#endif
+    return piped_file(adaparse_print, result, stdin) ? EXIT_SUCCESS
+                                                     : EXIT_FAILURE;
+  }
+
+  if (result.count("path")) {
+    auto file_path = result["path"].as<std::string>();
+    auto file = fopen(file_path.c_str(), "r");
+    if (file) {
+      piped_file(adaparse_print, result, file);
+      fclose(file);
+      return EXIT_SUCCESS;
+    } else {
+      fmt::print(stderr, "Error opening file: {}\n", strerror(errno));
+      return EXIT_FAILURE;
+    }
+  }
+
+  // the first argument without an option name will be parsed into file
+  if (result.count("help") || !result.count("url")) {
+    fmt::print(stderr, "{}\n", options.help());
+    return EXIT_SUCCESS;
+  }
+
+  auto input_url = result["url"].as<std::string>();
+  bool to_diagram = result["diagram"].as<bool>();
+
+  auto url = ada::parse<ada::url_aggregator>(input_url);
+  if (!url) {
+    fmt::print(stderr, "Invalid URL: {}\n", input_url);
+    return EXIT_FAILURE;
+  }
+
+  if (result.count("get")) {
+    std::string get_part = result["get"].as<std::string>();
+
+    auto print_lambda = [](const std::string& format_str, auto&&... args) {
+      fmt::print(format_str, std::forward<decltype(args)>(args)...);
+    };
+
+    return print_part(print_lambda, get_part, url.value()) ? EXIT_SUCCESS
+                                                           : EXIT_FAILURE;
+  };
+
+  if (to_diagram) {
+    fmt::print("{}\n", url->to_diagram());
+  } else {
+    fmt::print("{}\n", url->to_string());
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/tools/cli/benchmark_adaparse.sh b/tools/cli/benchmark_adaparse.sh
new file mode 100644 (file)
index 0000000..9a9f0c3
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+echo "Benchmarking piping function"
+
+# Set the number of trials
+num_trials=50
+
+if [ -f "linux_files.txt" ]; then
+    echo "linux_files.txt exists."
+else 
+    echo "downloading linux_files.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/files/linux_files.txt -O
+fi
+
+if [ -f "wikipedia_100k.txt" ]; then
+    echo "wikipedia_100k.txt exists."
+else 
+    echo "downloading wikipedia_100k.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/wikipedia/wikipedia_100k.txt -O
+fi
+
+if [ -f "top100.txt" ]; then
+    echo "top100.txt exists."
+else 
+    echo "downloading top100.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/top100/top100.txt -O
+fi
+
+# File list to benchmark against
+files=("top100.txt" "linux_files.txt" "wikipedia_100k.txt" )
+
+# Run the programs for the specified number of trials
+for file in "${files[@]}"; do
+  echo "Benchmarking $file"
+
+  # Variables to store the sum of the Gb/s values for each program
+  sum_fastpipespeed=0
+
+  for i in $(seq 1 $num_trials); do
+      result_fastspeed=$(cat $file | ../../build/tools/adaparse --benchmark 2>&1 | tail -1 | grep -oP '\d+(\.\d+)?') 
+      sum_fastpipespeed=$(echo "$sum_fastpipespeed + $result_fastspeed" | bc)
+  done
+
+  # Compute the averages
+  avg_fastpipespeed=$(echo "scale=7; $sum_fastpipespeed / $num_trials" | bc)
+
+  # Display the results
+  echo "------------------------------"
+  echo "Finished benchmarking $file"
+  echo "Number of trials: $num_trials"
+  echo "Average Gb/s for fastpipespeed: $avg_fastpipespeed"
+
+  echo "----------------------------"
+
+
+
+  echo ""
+done
diff --git a/tools/cli/benchmark_write_to_file.sh b/tools/cli/benchmark_write_to_file.sh
new file mode 100644 (file)
index 0000000..4dacba4
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+echo "Benchmarking writing to file from pipe."
+
+# Set the number of trials
+num_trials=50
+
+if [ -f "linux_files.txt" ]; then
+    echo "linux_files.txt exists."
+else 
+    echo "downloading linux_files.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/files/linux_files.txt -O
+fi
+
+if [ -f "wikipedia_100k.txt" ]; then
+    echo "wikipedia_100k.txt exists."
+else 
+    echo "downloading wikipedia_100k.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/wikipedia/wikipedia_100k.txt -O
+fi
+
+if [ -f "top100.txt" ]; then
+    echo "top100.txt exists."
+else 
+    echo "downloading top100.txt."
+    curl https://raw.githubusercontent.com/ada-url/url-various-datasets/main/top100/top100.txt -O
+fi
+
+# File list to benchmark against
+files=("top100.txt" "linux_files.txt" "wikipedia_100k.txt" )
+
+# Run the programs for the specified number of trials
+for file in "${files[@]}"; do
+  echo "Benchmarking $file"
+
+  # Variables to store the sum of the Gb/s values for each program
+  sum_fastpipespeed=0
+
+  for i in $(seq 1 $num_trials); do
+      result_fastspeed=$(cat $file | ../../build/tools/adaparse --benchmark --output test_write_speeds.txt 2>&1 | tail -1 | grep -oP '\d+(\.\d+)?') 
+      sum_fastpipespeed=$(echo "$sum_fastpipespeed + $result_fastspeed" | bc)
+  done
+
+  # Compute the averages
+  avg_fastpipespeed=$(echo "scale=7; $sum_fastpipespeed / $num_trials" | bc)
+
+  # Display the results
+  echo "------------------------------"
+  echo "Finished benchmarking $file"
+  echo "Number of trials: $num_trials"
+  echo "Average Gb/s for fastpipespeed: $avg_fastpipespeed"
+
+  echo "----------------------------"
+
+
+
+  echo ""
+done
diff --git a/tools/cli/line_iterator.h b/tools/cli/line_iterator.h
new file mode 100644 (file)
index 0000000..a604390
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef LINE_ITERATOR_H
+#define LINE_ITERATOR_H
+
+#include <string_view>
+
+struct line_iterator {
+  std::string_view all_text{};
+  size_t next_end_of_line{0};
+  line_iterator(const char *_buffer, size_t _len) : all_text(_buffer, _len) {}
+
+  inline bool find_another_complete_line() noexcept {
+    next_end_of_line = all_text.find('\n');
+    return next_end_of_line != std::string_view::npos;
+  }
+
+  inline operator bool() const noexcept {
+    return next_end_of_line != std::string_view::npos;
+  }
+
+  inline std::string_view grab_line() noexcept {
+    auto line = all_text.substr(0, next_end_of_line);  // advance to next EOL
+    // remove anything prior to said EOL
+    all_text.remove_prefix(next_end_of_line + 1);
+    return line;
+  }
+
+  inline size_t tail() const noexcept { return all_text.size(); }
+};
+
+#endif
\ No newline at end of file
diff --git a/tools/lint_and_format.py b/tools/lint_and_format.py
new file mode 100755 (executable)
index 0000000..58573af
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Formats C++ code using clang-format.
+#
+
+import os
+import sys
+import subprocess
+import argparse
+
+parser = argparse.ArgumentParser(
+    description="Format C/C++ code using clang-format."
+)
+parser.add_argument(
+    "operation",
+    choices=["check", "format"],
+    help='Operation to perform: "check" to check for formatting errors, or "format" to fix formatting errors.',
+)
+parser.add_argument(
+    "--extensions",
+    "-e",
+    nargs="+",
+    default=[".cpp", ".cc", ".c", ".h", ".hpp"],
+    help="List of file extensions to check or format (default: .cpp .cc .c .h .hpp)",
+)
+
+args = parser.parse_args()
+
+
+ROOT_DIR = (
+    subprocess.check_output(["git", "rev-parse", "--show-toplevel"])
+    .strip()
+    .decode("utf-8")
+)
+
+exclude_dirs = ('.git', '.cache', 'build', 'dependencies', 'docs')
+file_list = [os.path.join(dirpath, filename)
+             for dirpath, _, filenames in os.walk(ROOT_DIR)
+             for filename in filenames
+             if any(filename.endswith(ext) for ext in args.extensions)
+             and not any(exclude_dir in dirpath for exclude_dir in exclude_dirs)]
+
+
+def clang_check(file_path: str) -> None:
+    try:
+        diff_output = subprocess.check_output(
+            ["clang-format", "-output-replacements-xml", "-style=file", file_path], stderr=subprocess.STDOUT,
+        )
+        if b"<replacement " in diff_output:
+            print(f"Error: {file_path} needs formatting")
+            sys.exit(1)
+
+    except subprocess.CalledProcessError as error:
+        print(f'Error: {error.output.decode("utf-8")}')
+        sys.exit(1)
+
+
+def clang_format(file_path: str) -> None:
+    diff_output = subprocess.check_output(
+        ["clang-format", "-output-replacements-xml", "-style=file", file_path], stderr=subprocess.STDOUT,
+    )
+
+    if b"<replacement " in diff_output:
+        print(f"Formatting: {file_path}")
+        try:
+            subprocess.check_call(["clang-format", "-i", "-style=file", file_path])
+        except subprocess.CalledProcessError as error:
+            print(f'Error: {error.output.decode("utf-8")}')
+            sys.exit(1)
+
+
+def clang_format_verify() -> str:
+    version_output = subprocess.check_output(
+        ["clang-format", "--version"], stderr=subprocess.STDOUT,
+    ).decode("utf-8").split(" ")
+    if "version" in version_output :
+        return version_output[version_output.index("version") + 1]
+
+    return ""
+
+
+clang_format_version = clang_format_verify()
+
+for file_path in file_list:
+    if args.operation == "check":
+        clang_check(file_path)
+    elif args.operation == "format":
+        clang_format(file_path)
+
+
+print("Done!")
diff --git a/tools/prepare-doxygen.sh b/tools/prepare-doxygen.sh
new file mode 100755 (executable)
index 0000000..b222e19
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+set -e
+
+PACKAGE_URL="https://github.com/jothepro/doxygen-awesome-css.git"
+PACKAGE_VERSION="v2.2.0"
+
+BASE_DIR=$(pwd)
+THEME_DIR="$BASE_DIR/docs/theme"
+WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp')
+
+cleanup () {
+  EXIT_CODE=$?
+  [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE"
+  exit $EXIT_CODE
+}
+
+trap cleanup INT TERM EXIT
+
+cd "$WORKSPACE"
+git clone --depth=1 --branch "$PACKAGE_VERSION" "$PACKAGE_URL" theme
+rm -rf "$THEME_DIR"
+mv "$WORKSPACE/theme" "$THEME_DIR"
diff --git a/tools/release/__init__.py b/tools/release/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/release/create_release.py b/tools/release/create_release.py
new file mode 100755 (executable)
index 0000000..70cbf93
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+import os
+from github import Github
+import lib.release as release
+
+WORK_DIR = os.path.dirname(os.path.abspath(__file__)).replace("/tools/release", "")
+
+NEXT_TAG = os.environ["NEXT_RELEASE_TAG"]
+REPO_NAME = os.environ["GITHUB_REPOSITORY"]
+TOKEN = os.environ["GITHUB_TOKEN"]
+if not NEXT_TAG or not REPO_NAME or not TOKEN:
+    raise Exception(
+        "Bad environment variables. Invalid GITHUB_REPOSITORY, GITHUB_TOKEN or NEXT_RELEASE_TAG"
+    )
+
+g = Github(TOKEN)
+repo = g.get_repo(REPO_NAME)
+
+release_notes = release.contruct_release_notes(repo, NEXT_TAG)
+
+release.create_release(repo, NEXT_TAG, release_notes)
+
+release = repo.get_release(NEXT_TAG)
+release.upload_asset("singleheader/ada.cpp")
+release.upload_asset("singleheader/ada.h")
+release.upload_asset("singleheader/ada_c.h")
+release.upload_asset("singleheader/singleheader.zip")
diff --git a/tools/release/lib/__init__.py b/tools/release/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/release/lib/release.py b/tools/release/lib/release.py
new file mode 100644 (file)
index 0000000..7116a02
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+
+import re
+from typing import Optional, List, Set, Union, Type
+from github.PullRequest import PullRequest
+from github.GitRelease import GitRelease
+from github.Repository import Repository
+
+
+def is_valid_tag(tag: str) -> bool:
+    tag_regex = r"^v\d+\.\d+\.\d+$"
+    return bool(re.match(tag_regex, tag))
+
+
+def create_release(
+    repository: Repository, tag: str, notes: str
+) -> Union[None, Type[Exception]]:
+    if not is_valid_tag(tag):
+        raise Exception(f"Invalid tag: {tag}")
+
+    try:
+        repository.create_git_release(
+            tag=tag, name=tag, message=notes, draft=False, prerelease=False
+        )
+
+    except Exception as exp:
+        raise Exception(
+            f"create_release: Error creating release/tag {tag}: {exp!s}"
+        ) from exp
+
+
+def get_sorted_merged_pulls(
+    pulls: List[PullRequest], last_release: Optional[GitRelease]
+) -> List[PullRequest]:
+    # Get merged pulls after last release
+    if not last_release:
+        return sorted(
+            (
+                pull
+                for pull in pulls
+                if pull.merged
+                and pull.base.ref == "main"
+                and not pull.title.startswith("chore: release")
+                and not pull.user.login.startswith("github-actions")
+            ),
+            key=lambda pull: pull.merged_at,
+        )
+
+    return sorted(
+        (
+            pull
+            for pull in pulls
+            if pull.merged
+            and pull.base.ref == "main"
+            and (pull.merged_at > last_release.created_at)
+            and not pull.title.startswith("chore: release")
+            and not pull.user.login.startswith("github-actions")
+        ),
+        key=lambda pull: pull.merged_at,
+    )
+
+
+def get_pr_contributors(pull_request: PullRequest) -> List[str]:
+    contributors = set()
+    for commit in pull_request.get_commits():
+        commit_message = commit.commit.message
+        if commit_message.startswith("Co-authored-by:"):
+            coauthor = commit_message.split("<")[0].split(":")[-1].strip()
+            contributors.add(coauthor)
+        else:
+            author = commit.author
+            if author:
+                contributors.add(author.login)
+    return sorted(list(contributors), key=str.lower)
+
+
+def get_old_contributors(
+    pulls: List[PullRequest], last_release: Optional[GitRelease]
+) -> Set[str]:
+    contributors = set()
+    if last_release:
+        merged_pulls = [
+            pull
+            for pull in pulls
+            if pull.merged and pull.merged_at <= last_release.created_at
+        ]
+
+        for pull in merged_pulls:
+            pr_contributors = get_pr_contributors(pull)
+            for contributor in pr_contributors:
+                contributors.add(contributor)
+
+    return contributors
+
+
+def get_new_contributors(
+    old_contributors: List[str], merged_pulls: List[PullRequest]
+) -> List[str]:
+    new_contributors = set()
+    for pull in merged_pulls:
+        pr_contributors = get_pr_contributors(pull)
+        for contributor in pr_contributors:
+            if contributor not in old_contributors:
+                new_contributors.add(contributor)
+
+    return sorted(list(new_contributors), key=str.lower)
+
+
+def get_last_release(releases: List[GitRelease]) -> Optional[GitRelease]:
+    sorted_releases = sorted(releases, key=lambda r: r.created_at, reverse=True)
+
+    if sorted_releases:
+        return sorted_releases[0]
+
+    return None
+
+
+def multiple_contributors_mention_md(contributors: List[str]) -> str:
+    contrib_by = ""
+    if len(contributors) <= 1:
+        for contrib in contributors:
+            contrib_by += f"@{contrib}"
+    else:
+        for contrib in contributors:
+            contrib_by += f"@{contrib}, "
+
+        contrib_by = contrib_by[:-2]
+        last_comma = contrib_by.rfind(", ")
+        contrib_by = (
+            contrib_by[:last_comma].strip()
+            + " and "
+            + contrib_by[last_comma + 1 :].strip()
+        )
+    return contrib_by
+
+
+def whats_changed_md(repo_full_name: str, merged_pulls: List[PullRequest]) -> List[str]:
+    whats_changed = []
+    for pull in merged_pulls:
+        contributors = get_pr_contributors(pull)
+        contrib_by = multiple_contributors_mention_md(contributors)
+
+        whats_changed.append(
+            f"* {pull.title} by {contrib_by} in https://github.com/{repo_full_name}/pull/{pull.number}"
+        )
+
+    return whats_changed
+
+
+def get_first_contribution(
+    merged_pulls: List[str], contributor: str
+) -> Optional[PullRequest]:
+    for pull in merged_pulls:
+        contrubutors = get_pr_contributors(pull)
+        if contributor in contrubutors:
+            return pull
+
+    # ? unreachable
+    return None
+
+
+def new_contributors_md(
+    repo_full_name: str, merged_pulls: List[PullRequest], new_contributors: List[str]
+) -> List[str]:
+    contributors_by_pr = {}
+    contributors_md = []
+    for contributor in new_contributors:
+        first_contrib = get_first_contribution(merged_pulls, contributor)
+
+        if not first_contrib:
+            continue
+
+        if first_contrib.number not in contributors_by_pr.keys():
+            contributors_by_pr[first_contrib.number] = [contributor]
+        else:
+            contributors_by_pr[first_contrib.number] += [contributor]
+
+    contributors_by_pr = dict(sorted(contributors_by_pr.items()))
+    for pr_number, contributors in contributors_by_pr.items():
+        contributors.sort(key=str.lower)
+        contrib_by = multiple_contributors_mention_md(contributors)
+
+        contributors_md.append(
+            f"* {contrib_by} made their first contribution in https://github.com/{repo_full_name}/pull/{pr_number}"
+        )
+
+    return contributors_md
+
+
+def full_changelog_md(
+    repository_name: str, last_tag_name: str, next_tag_name: str
+) -> Optional[str]:
+    if not last_tag_name:
+        return None
+    return f"**Full Changelog**: https://github.com/{repository_name}/compare/{last_tag_name}...{next_tag_name}"
+
+
+def contruct_release_notes(repository: Repository, next_tag_name: str) -> str:
+    repo_name = repository.full_name
+    last_release = get_last_release(repository.get_releases())
+    all_pulls = repository.get_pulls(state="closed")
+
+    sorted_merged_pulls = get_sorted_merged_pulls(all_pulls, last_release)
+    old_contributors = get_old_contributors(all_pulls, last_release)
+    new_contributors = get_new_contributors(old_contributors, sorted_merged_pulls)
+
+    whats_changed = whats_changed_md(repo_name, sorted_merged_pulls)
+
+    new_contrib_md = new_contributors_md(
+        repo_name, sorted_merged_pulls, new_contributors
+    )
+
+    notes = "## What's changed\n"
+    for changes in whats_changed:
+        notes += changes + "\n"
+
+    notes += "\n"
+
+    if new_contributors:
+        notes += "## New Contributors\n"
+        for new_contributor in new_contrib_md:
+            notes += new_contributor + "\n"
+
+        notes += "\n"
+
+    if last_release:
+        notes += full_changelog_md(
+            repository.full_name, last_release.title, next_tag_name
+        )
+
+    return notes
diff --git a/tools/release/lib/tests/__init__.py b/tools/release/lib/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/release/lib/tests/samples/ada_version_h.txt b/tools/release/lib/tests/samples/ada_version_h.txt
new file mode 100644 (file)
index 0000000..aab6a16
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * @file ada_version.h
+ * @brief Definitions for Ada's version number.
+ */
+#ifndef ADA_ADA_VERSION_H
+#define ADA_ADA_VERSION_H
+
+#define ADA_VERSION "1.0.0"
+
+namespace ada {
+
+enum {
+  ADA_VERSION_MAJOR = 1,
+  ADA_VERSION_MINOR = 0,
+  ADA_VERSION_REVISION = 0,
+};
+
+}  // namespace ada
+
+#endif  // ADA_ADA_VERSION_H
diff --git a/tools/release/lib/tests/samples/ada_version_h_expected.txt b/tools/release/lib/tests/samples/ada_version_h_expected.txt
new file mode 100644 (file)
index 0000000..96e2f86
--- /dev/null
@@ -0,0 +1,20 @@
+/**
+ * @file ada_version.h
+ * @brief Definitions for Ada's version number.
+ */
+#ifndef ADA_ADA_VERSION_H
+#define ADA_ADA_VERSION_H
+
+#define ADA_VERSION "2.0.0"
+
+namespace ada {
+
+enum {
+  ADA_VERSION_MAJOR = 2,
+  ADA_VERSION_MINOR = 0,
+  ADA_VERSION_REVISION = 0,
+};
+
+}  // namespace ada
+
+#endif  // ADA_ADA_VERSION_H
diff --git a/tools/release/lib/tests/samples/cmakelists.txt b/tools/release/lib/tests/samples/cmakelists.txt
new file mode 100644 (file)
index 0000000..35a5955
--- /dev/null
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.16)
+
+project(ada
+  DESCRIPTION "Fast spec-compliant URL parser"
+  LANGUAGES C CXX
+  VERSION 1.0.0
+)
+
+set(ADA_LIB_VERSION "1.0.0" CACHE STRING "ada library version")
+set(ADA_LIB_SOVERSION "1" CACHE STRING "ada library soversion")
diff --git a/tools/release/lib/tests/samples/cmakelists_expected.txt b/tools/release/lib/tests/samples/cmakelists_expected.txt
new file mode 100644 (file)
index 0000000..4a996d4
--- /dev/null
@@ -0,0 +1,10 @@
+cmake_minimum_required(VERSION 3.16)
+
+project(ada
+  DESCRIPTION "Fast spec-compliant URL parser"
+  LANGUAGES C CXX
+  VERSION 2.0.0
+)
+
+set(ADA_LIB_VERSION "2.0.0" CACHE STRING "ada library version")
+set(ADA_LIB_SOVERSION "2" CACHE STRING "ada library soversion")
diff --git a/tools/release/lib/tests/samples/doxygen.txt b/tools/release/lib/tests/samples/doxygen.txt
new file mode 100644 (file)
index 0000000..87bc18f
--- /dev/null
@@ -0,0 +1,82 @@
+# Doxyfile 1.9.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "Ada"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = "2.0.0"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Spec-compliant URL parser"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "docs"
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = YES
diff --git a/tools/release/lib/tests/samples/doxygen_expected.txt b/tools/release/lib/tests/samples/doxygen_expected.txt
new file mode 100644 (file)
index 0000000..87bc18f
--- /dev/null
@@ -0,0 +1,82 @@
+# Doxyfile 1.9.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "Ada"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = "2.0.0"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Spec-compliant URL parser"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "docs"
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = YES
diff --git a/tools/release/lib/tests/test_release.py b/tools/release/lib/tests/test_release.py
new file mode 100644 (file)
index 0000000..3aa3f8d
--- /dev/null
@@ -0,0 +1,522 @@
+from .. import release
+from datetime import datetime
+from collections import namedtuple
+
+Release = namedtuple("Release", ["title", "created_at"])
+User = namedtuple("User", ["login"])
+Commit = namedtuple("Commit", ["author", "commit"])
+CommitMessage = namedtuple("CommitMessage", ["message"])
+PullRequestBase = namedtuple("PullRequestBase", "ref")
+PullRequestTuple = namedtuple(
+    "PullRequest",
+    ["title", "number", "state", "base", "merged", "merged_at", "user", "commits"],
+)
+
+
+class PullRequest(PullRequestTuple):
+    def get_commits(self):
+        return self.commits
+
+
+class RepoStub:
+    def __init__(self):
+        self.created_at = datetime(2023, 1, 1)
+        self.full_name = "ada-url/ada"
+
+    @staticmethod
+    def get_releases() -> list:
+        return [
+            Release("v1.0.1", datetime(2023, 2, 1)),
+            Release("v1.0.3", datetime(2023, 4, 1)),
+            Release("v1.0.2", datetime(2023, 3, 1)),
+        ]
+
+    @staticmethod
+    def get_pulls(state="closed"):
+        return list(
+            filter(
+                lambda pull: pull.state == state,
+                [
+                    PullRequest(
+                        title="Feature 1",
+                        number=1,
+                        state="open",
+                        merged=False,
+                        base=PullRequestBase("main"),
+                        merged_at=datetime(2023, 2, 2),
+                        user=User("contributor_1"),
+                        commits=[
+                            Commit(
+                                User("contributor_1"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_1"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_1"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_1"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 2",
+                        number=2,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 2, 1),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_2"),
+                        commits=[
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage(
+                                    "Co-authored-by: old_contrib_coauthor2 <the@email>"
+                                ),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage(
+                                    "Co-authored-by: old_contrib_coauthor <the@email>"
+                                ),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 3",
+                        number=3,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 2, 2),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_3"),
+                        commits=[
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 4",
+                        number=4,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 2, 3),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_4"),
+                        commits=[
+                            Commit(
+                                User("contributor_4"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_4"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_4"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_4"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 5",
+                        number=5,
+                        state="closed",
+                        merged=False,
+                        merged_at=datetime(2023, 2, 4),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_3"),
+                        commits=[
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 6",
+                        number=12,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 2, 5),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_2"),
+                        commits=[
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_2"),
+                                CommitMessage("src: sample commit 4"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 9",
+                        number=13,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 5, 2),
+                        base=PullRequestBase("main"),
+                        user=User("new_contributor_2"),
+                        commits=[
+                            Commit(
+                                User("new_contributor_2"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("new_contributor_2"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("new_contributor_2"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("new_contributor_2"),
+                                CommitMessage("src: sample commit 4 "),
+                            ),
+                            Commit(
+                                User("new_contributor_2"),
+                                CommitMessage(
+                                    "Co-authored-by: new_contributor_coauthor1 <the@email>"
+                                ),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 7",
+                        number=14,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 5, 5),
+                        base=PullRequestBase("main"),
+                        user=User("contributor_3"),
+                        commits=[
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage("src: sample commit 4 "),
+                            ),
+                            Commit(
+                                User("contributor_3"),
+                                CommitMessage(
+                                    "Co-authored-by: new_contributor_coauthor2 <the@email>"
+                                ),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 8",
+                        number=15,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 5, 1),
+                        base=PullRequestBase("main"),
+                        user=User("new_contributor_1"),
+                        commits=[
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 3"),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage(
+                                    "Co-authored-by: new_contributor_coauthor4 <the@email>"
+                                ),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 4 "),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage(
+                                    "Co-authored-by: new_contributor_coauthor3 <the@email>"
+                                ),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="Feature 11",
+                        number=16,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 5, 10),
+                        base=PullRequestBase("another_branch"),
+                        user=User("new_contributor_1"),
+                        commits=[
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                        ],
+                    ),
+                    PullRequest(
+                        title="chore: release v10.0.0",
+                        number=17,
+                        state="closed",
+                        merged=True,
+                        merged_at=datetime(2023, 5, 9),
+                        base=PullRequestBase("main"),
+                        user=User("new_contributor_1"),
+                        commits=[
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 1"),
+                            ),
+                            Commit(
+                                User("new_contributor_1"),
+                                CommitMessage("src: sample commit 2"),
+                            ),
+                        ],
+                    ),
+                ],
+            )
+        )
+
+
+def test_get_sorted_merged_pulls():
+    pulls = RepoStub.get_pulls(state="closed")
+    last_release = None
+
+    sorted_merged_pulls = release.get_sorted_merged_pulls(pulls, last_release)
+
+    # Should return all the merged pull requests since there is no previous release
+    assert sorted_merged_pulls == sorted(
+        [
+            pull
+            for pull in pulls
+            if pull.merged
+            and pull.base.ref == "main"
+            and not pull.title.startswith("chore: release")
+            and not pull.user.login.startswith("github-actions")
+        ],
+        key=lambda pull: pull.merged_at,
+    )
+
+
+def test_get_last_release():
+    releases = RepoStub.get_releases()
+
+    # Should return the latest release
+    last_release = release.get_last_release(releases)
+    assert last_release.created_at == datetime(2023, 4, 1)
+
+    # Should return None (in case there are no releases yet)
+    last_release = release.get_last_release([])
+    assert last_release == None
+
+
+def test_get_old_contributors():
+    last_release = release.get_last_release(RepoStub.get_releases())
+
+    old_contributors = release.get_old_contributors(RepoStub.get_pulls(), last_release)
+
+    # Should return contributors until last release, including co-authors
+    assert old_contributors == {
+        "contributor_2",
+        "contributor_3",
+        "contributor_4",
+        "old_contrib_coauthor",
+        "old_contrib_coauthor2",
+    }
+
+
+def test_get_new_contributors():
+    last_release = release.get_last_release(RepoStub.get_releases())
+    all_pulls = RepoStub.get_pulls()
+
+    # merged pulls after last release
+    merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
+    old_contributors = release.get_old_contributors(all_pulls, last_release)
+
+    # Should return a List sorted in alphabetic order with only the new contributors since
+    # last release
+    new_contributors = release.get_new_contributors(old_contributors, merged_pulls)
+
+    assert new_contributors == [
+        "new_contributor_1",
+        "new_contributor_2",
+        "new_contributor_coauthor1",
+        "new_contributor_coauthor2",
+        "new_contributor_coauthor3",
+        "new_contributor_coauthor4",
+    ]
+
+
+def test_whats_changed_md():
+    repo_stub = RepoStub()
+    last_release = release.get_last_release(RepoStub.get_releases())
+    all_pulls = RepoStub.get_pulls()
+    # merged pulls after last release
+    merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
+
+    whats_changed = release.whats_changed_md(repo_stub.full_name, merged_pulls)
+
+    assert whats_changed == [
+        "* Feature 8 by @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 in https://github.com/ada-url/ada/pull/15",
+        "* Feature 9 by @new_contributor_2 and @new_contributor_coauthor1 in https://github.com/ada-url/ada/pull/13",
+        "* Feature 7 by @contributor_3 and @new_contributor_coauthor2 in https://github.com/ada-url/ada/pull/14",
+    ]
+
+
+def test_new_contributors_md():
+    repo_stub = RepoStub()
+    last_release = release.get_last_release(RepoStub.get_releases())
+    all_pulls = RepoStub.get_pulls()
+
+    merged_pulls = release.get_sorted_merged_pulls(all_pulls, last_release)
+    old_contributors = release.get_old_contributors(all_pulls, last_release)
+    new_contributors = release.get_new_contributors(old_contributors, merged_pulls)
+
+    # Should return a markdown containing the new contributors and their first contribution
+    new_contributors_md = release.new_contributors_md(
+        repo_stub.full_name, merged_pulls, new_contributors
+    )
+
+    assert new_contributors_md == [
+        "* @new_contributor_2 and @new_contributor_coauthor1 made their first contribution in https://github.com/ada-url/ada/pull/13",
+        "* @new_contributor_coauthor2 made their first contribution in https://github.com/ada-url/ada/pull/14",
+        "* @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 made their first contribution in https://github.com/ada-url/ada/pull/15",
+    ]
+
+
+def test_full_changelog_md():
+    repo_stub = RepoStub()
+    last_tag = release.get_last_release(repo_stub.get_releases())
+
+    full_changelog = release.full_changelog_md(
+        repo_stub.full_name, last_tag.title, "v3.0.0"
+    )
+    assert (
+        full_changelog
+        == "**Full Changelog**: https://github.com/ada-url/ada/compare/v1.0.3...v3.0.0"
+    )
+
+    full_changelog = release.full_changelog_md(repo_stub.full_name, None, "v3.0.0")
+    assert full_changelog is None
+
+
+def test_contruct_release_notes():
+    repo_stub = RepoStub()
+
+    notes = release.contruct_release_notes(repo_stub, "v3.0.0")
+    assert (
+        notes
+        == "## What's changed\n"
+        + "* Feature 8 by @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 in https://github.com/ada-url/ada/pull/15\n"
+        + "* Feature 9 by @new_contributor_2 and @new_contributor_coauthor1 in https://github.com/ada-url/ada/pull/13\n"
+        + "* Feature 7 by @contributor_3 and @new_contributor_coauthor2 in https://github.com/ada-url/ada/pull/14\n"
+        + "\n"
+        + "## New Contributors\n"
+        + "* @new_contributor_2 and @new_contributor_coauthor1 made their first contribution in https://github.com/ada-url/ada/pull/13\n"
+        + "* @new_contributor_coauthor2 made their first contribution in https://github.com/ada-url/ada/pull/14\n"
+        + "* @new_contributor_1, @new_contributor_coauthor3 and @new_contributor_coauthor4 made their first contribution in https://github.com/ada-url/ada/pull/15\n"
+        + "\n"
+        + "**Full Changelog**: https://github.com/ada-url/ada/compare/v1.0.3...v3.0.0"
+    )
+
+
+def test_is_valid_tag():
+    assert release.is_valid_tag("v1.0.0") is True
+    assert release.is_valid_tag("v1.1.1") is True
+
+    assert release.is_valid_tag("v0") is False
+    assert release.is_valid_tag("v1.0.0.0") is False
+    assert release.is_valid_tag("1.0.0") is False
+    assert release.is_valid_tag("1.0.1") is False
+
+
+def test_multiple_contributors_mention_md():
+    contributors = ["contrib1", "contrib2", "contrib3", "contrib4"]
+
+    md_contributors_mention = release.multiple_contributors_mention_md(contributors)
+    assert md_contributors_mention == "@contrib1, @contrib2, @contrib3 and @contrib4"
+
+    contributors = ["contrib1"]
+    md_contributors_mention = release.multiple_contributors_mention_md(contributors)
+    assert md_contributors_mention == "@contrib1"
diff --git a/tools/release/lib/tests/test_update_versions.py b/tools/release/lib/tests/test_update_versions.py
new file mode 100644 (file)
index 0000000..90f8cc8
--- /dev/null
@@ -0,0 +1,53 @@
+from .. import versions
+import os
+
+
+def test_update_cmakelists_version():
+    current_dir = os.path.dirname(os.path.abspath(__file__))
+    sample_path = f"{current_dir}/samples/cmakelists.txt"
+    sample_expected_path = f"{current_dir}/samples/cmakelists_expected.txt"
+
+    versions.update_cmakelists_version("2.0.0", sample_path)
+
+    with open(sample_path, "r") as cmake:
+        given = cmake.read()
+
+    with open(sample_expected_path, "r") as cmake_expected:
+        expected = cmake_expected.read()
+
+    assert given == expected
+    versions.update_cmakelists_version("1.0.0", sample_path)  # cleanup
+
+
+def test_update_ada_version_h():
+    current_dir = os.path.dirname(os.path.abspath(__file__))
+    sample_path = f"{current_dir}/samples/ada_version_h.txt"
+    sample_expected_path = f"{current_dir}/samples/ada_version_h_expected.txt"
+
+    versions.update_ada_version_h("2.0.0", sample_path)
+
+    with open(sample_path, "r") as ada_version_h:
+        given = ada_version_h.read()
+
+    with open(sample_expected_path, "r") as ada_version_h_expected:
+        expected = ada_version_h_expected.read()
+
+    assert given == expected
+    versions.update_ada_version_h("1.0.0", sample_path)  # cleanup
+
+
+def test_update_doxygen_version():
+    current_dir = os.path.dirname(os.path.abspath(__file__))
+    sample_path = f"{current_dir}/samples/doxygen.txt"
+    sample_expected_path = f"{current_dir}/samples/doxygen_expected.txt"
+
+    versions.update_doxygen_version("2.0.0", sample_path)
+
+    with open(sample_path, "r") as doxygen:
+        given = doxygen.read()
+
+    with open(sample_expected_path, "r") as doxygen_expected:
+        expected = doxygen_expected.read()
+
+    assert given == expected
+    versions.update_ada_version_h("1.0.0", sample_path)  # cleanup
diff --git a/tools/release/lib/versions.py b/tools/release/lib/versions.py
new file mode 100644 (file)
index 0000000..e74aca0
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+import fileinput
+import re
+
+
+def update_cmakelists_version(new_version: str, file_path: str) -> None:
+    inside_project = False
+    with fileinput.FileInput(file_path, inplace=True) as cmakelists:
+        for line in cmakelists:
+            if "set(ADA_LIB_VERSION" in line:
+                line = re.sub(r"[0-9]+\.[0-9]+\.[0-9]+", new_version, line)
+            elif "set(ADA_LIB_SOVERSION" in line:
+                line = re.sub(r"[0-9]+", new_version.split(".")[0], line)
+
+            elif "project(" in line:
+                inside_project = True
+            elif inside_project:
+                if "VERSION" in line:
+                    line = re.sub(r"[0-9]+\.[0-9]+\.[0-9]+", new_version, line)
+                    inside_project = False
+            print(line, end="")
+
+
+def update_ada_version_h(new_version: str, file_path: str) -> None:
+    new_version_list = new_version.split(".")
+    with fileinput.FileInput(file_path, inplace=True) as ada_version_h:
+        inside_enum = False
+        for line in ada_version_h:
+            if "#define ADA_VERSION" in line:
+                line = f'#define ADA_VERSION "{new_version}"\n'
+
+            elif "enum {" in line:
+                inside_enum = True
+            elif inside_enum:
+                if line.strip().startswith("ADA_VERSION_MAJOR"):
+                    line = re.sub(r"\d+", new_version_list[0], line)
+                elif line.strip().startswith("ADA_VERSION_MINOR"):
+                    line = re.sub(r"\d+", new_version_list[1], line)
+                elif line.strip().startswith("ADA_VERSION_REVISION"):
+                    line = re.sub(r"\d+", new_version_list[2], line)
+
+            print(line, end="")
+
+
+def update_doxygen_version(new_version: str, file_path: str) -> None:
+    with fileinput.FileInput(file_path, inplace=True) as doxygen:
+        for line in doxygen:
+            if line.strip().startswith("PROJECT_NUMBER         ="):
+                line = f'PROJECT_NUMBER         = "{new_version}"\n'
+
+            print(line, end="")
diff --git a/tools/release/requirements.txt b/tools/release/requirements.txt
new file mode 100644 (file)
index 0000000..512a41e
--- /dev/null
@@ -0,0 +1,2 @@
+PyGithub==2.3.0
+pytest==8.1.1
diff --git a/tools/release/update_versions.py b/tools/release/update_versions.py
new file mode 100755 (executable)
index 0000000..c9e4931
--- /dev/null
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+import os
+import lib.versions as update_versions
+from lib.release import is_valid_tag
+
+WORK_DIR = os.path.dirname(os.path.abspath(__file__)).replace("/tools/release", "")
+
+ADA_VERSION_H = f"{WORK_DIR}/include/ada/ada_version.h"
+DOXYGEN = f"{WORK_DIR}/doxygen"
+CMAKE_LISTS = f"{WORK_DIR}/CMakeLists.txt"
+
+NEXT_TAG = os.environ["NEXT_RELEASE_TAG"]
+if not NEXT_TAG or not is_valid_tag(NEXT_TAG):
+    raise Exception(f"Bad environment variables. Invalid NEXT_RELEASE_TAG {NEXT_TAG}.")
+
+NEXT_TAG = NEXT_TAG[1:]  # from v1.0.0 to 1.0.0
+
+update_versions.update_ada_version_h(NEXT_TAG, ADA_VERSION_H)
+update_versions.update_doxygen_version(NEXT_TAG, DOXYGEN)
+update_versions.update_cmakelists_version(NEXT_TAG, CMAKE_LISTS)
diff --git a/tools/run-clangcldocker.sh b/tools/run-clangcldocker.sh
new file mode 100755 (executable)
index 0000000..b7ad422
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+set -e
+COMMAND=$*
+SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
+MAINSOURCE=$SCRIPTPATH/..
+ALL_ADA_FILES=$(cd $MAINSOURCE && git ls-tree --full-tree --name-only -r HEAD | grep -e ".*\.\(c\|h\|cc\|cpp\|hh\)\$" | grep -vFf clang-format-ignore.txt)
+
+if clang-format-15 --version  2>/dev/null | grep -qF 'version 15.'; then
+  cd $MAINSOURCE; clang-format-15 --style=file --verbose -i "$@" $ALL_ADA_FILES
+  exit 0
+elif clang-format --version  2>/dev/null | grep -qF 'version 15.'; then
+  cd $MAINSOURCE; clang-format --style=file --verbose -i "$@" $ALL_ADA_FILES
+  exit 0
+fi
+echo "Trying to use docker"
+command -v docker >/dev/null 2>&1 || { echo >&2 "Please install docker. E.g., go to https://www.docker.com/products/docker-desktop Type 'docker' to diagnose the problem."; exit 1; }
+docker info >/dev/null 2>&1 || { echo >&2 "Docker server is not running? type 'docker info'."; exit 1; }
+
+if [ -t 0 ]; then DOCKER_ARGS=-it; fi
+docker pull kszonek/clang-format-15
+
+docker run --rm $DOCKER_ARGS -v "$MAINSOURCE":"$MAINSOURCE":Z  -w "$MAINSOURCE" -u "$(id -u $USER):$(id -g $USER)" kszonek/clang-format-15 --style=file --verbose -i "$@" $ALL_ADA_FILES
diff --git a/tools/update-wpt.sh b/tools/update-wpt.sh
new file mode 100755 (executable)
index 0000000..6dd13c9
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+set -e
+
+BASE_DIR=$(pwd)
+WPT_DIR="$BASE_DIR/tests/wpt"
+
+WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp')
+
+cleanup () {
+  EXIT_CODE=$?
+  [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE"
+  exit $EXIT_CODE
+}
+
+trap cleanup INT TERM EXIT
+
+cd "$WORKSPACE"
+git clone \
+  --no-checkout \
+  --depth=1 \
+  --filter=blob:none \
+  --sparse \
+  https://github.com/web-platform-tests/wpt.git wpt
+cd wpt
+git sparse-checkout add "url/resources"
+git checkout
+cp url/resources/*.json "$WPT_DIR"